diff --git a/src/ObjectExporter.php b/src/ObjectExporter.php new file mode 100644 index 0000000..56b0ae7 --- /dev/null +++ b/src/ObjectExporter.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Exporter; + +interface ObjectExporter +{ + public function handles(object $object): bool; + + public function export(object $object): string; +} diff --git a/src/ObjectExporterChain.php b/src/ObjectExporterChain.php new file mode 100644 index 0000000..783ee5c --- /dev/null +++ b/src/ObjectExporterChain.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Exporter; + +final class ObjectExporterChain implements ObjectExporter +{ + /** + * @psalm-var non-empty-list + */ + private array $exporter; + + /** + * @psalm-param non-empty-list $exporter + */ + public function __construct(array $exporter) + { + $this->exporter = $exporter; + } + + public function handles(object $object): bool + { + foreach ($this->exporter as $exporter) { + if ($exporter->handles($object)) { + return true; + } + } + + return false; + } + + /** + * @throws ObjectNotSupportedException + */ + public function export(object $object): string + { + foreach ($this->exporter as $exporter) { + if ($exporter->handles($object)) { + return $exporter->export($object); + } + } + + throw new ObjectNotSupportedException; + } +} diff --git a/src/exception/Exception.php b/src/exception/Exception.php new file mode 100644 index 0000000..279613b --- /dev/null +++ b/src/exception/Exception.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Exporter; + +use Throwable; + +interface Exception extends Throwable +{ +} diff --git a/src/exception/ObjectNotSupportedException.php b/src/exception/ObjectNotSupportedException.php new file mode 100644 index 0000000..4e0bf96 --- /dev/null +++ b/src/exception/ObjectNotSupportedException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Exporter; + +use RuntimeException; + +final class ObjectNotSupportedException extends RuntimeException implements Exception +{ +} diff --git a/tests/ObjectExporterChainTest.php b/tests/ObjectExporterChainTest.php new file mode 100644 index 0000000..b06a949 --- /dev/null +++ b/tests/ObjectExporterChainTest.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\Exporter; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; +use stdClass; + +#[CoversClass(ObjectExporterChain::class)] +#[Small] +final class ObjectExporterChainTest extends TestCase +{ + public function testCanBeQueriedWhetherChainedExporterHandlesAnObject(): void + { + $firstExporter = $this->createStub(ObjectExporter::class); + $firstExporter->method('handles')->willReturn(false); + + $secondExporter = $this->createStub(ObjectExporter::class); + $secondExporter->method('handles')->willReturn(true); + + $chain = new ObjectExporterChain([$firstExporter]); + $this->assertFalse($chain->handles(new stdClass)); + + $chain = new ObjectExporterChain([$firstExporter, $secondExporter]); + $this->assertTrue($chain->handles(new stdClass)); + } + + public function testDelegatesExportingToFirstExporterThatHandlesAnObject(): void + { + $firstExporter = $this->createStub(ObjectExporter::class); + $firstExporter->method('handles')->willReturn(false); + $firstExporter->method('export')->willThrowException(new ObjectNotSupportedException); + + $secondExporter = $this->createStub(ObjectExporter::class); + $secondExporter->method('handles')->willReturn(true); + $secondExporter->method('export')->willReturn('string'); + + $chain = new ObjectExporterChain([$firstExporter, $secondExporter]); + + $this->assertSame('string', $chain->export(new stdClass)); + } + + public function testCannotExportObjectWhenNoExporterHandlesIt(): void + { + $firstExporter = $this->createStub(ObjectExporter::class); + $firstExporter->method('handles')->willReturn(false); + + $chain = new ObjectExporterChain([$firstExporter]); + + $this->expectException(ObjectNotSupportedException::class); + + $this->assertSame('string', $chain->export(new stdClass)); + } +}