Skip to content

Commit

Permalink
Initial work on making export of objects customizable
Browse files Browse the repository at this point in the history
  • Loading branch information
sebastianbergmann committed Mar 29, 2024
1 parent 92f21fd commit ac6d770
Show file tree
Hide file tree
Showing 7 changed files with 196 additions and 0 deletions.
11 changes: 11 additions & 0 deletions src/Exporter.php
Expand Up @@ -36,6 +36,13 @@

final readonly class Exporter
{
private ?ObjectExporterChain $objectExporter;

public function __construct(?ObjectExporterChain $objectExporter = null)
{
$this->objectExporter = $objectExporter;
}

/**
* Exports a value as a string.
*
Expand Down Expand Up @@ -337,6 +344,10 @@ private function exportObject(mixed $value, RecursionContext $processed, string

$processed->add($value);

if ($this->objectExporter !== null && $this->objectExporter->handles($value)) {
return $this->objectExporter->export($value);
}

$values = '';
$array = $this->toArray($value);

Expand Down
17 changes: 17 additions & 0 deletions src/ObjectExporter.php
@@ -0,0 +1,17 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/exporter.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* 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;
}
51 changes: 51 additions & 0 deletions src/ObjectExporterChain.php
@@ -0,0 +1,51 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/exporter.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* 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<ObjectExporter>
*/
private array $exporter;

/**
* @psalm-param non-empty-list<ObjectExporter> $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;
}
}
16 changes: 16 additions & 0 deletions src/exception/Exception.php
@@ -0,0 +1,16 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/exporter.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* 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
{
}
16 changes: 16 additions & 0 deletions src/exception/ObjectNotSupportedException.php
@@ -0,0 +1,16 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/exporter.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* 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
{
}
23 changes: 23 additions & 0 deletions tests/ExporterTest.php
Expand Up @@ -26,12 +26,14 @@
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\RequiresPhpExtension;
use PHPUnit\Framework\Attributes\Small;
use PHPUnit\Framework\Attributes\UsesClass;
use PHPUnit\Framework\TestCase;
use SebastianBergmann\RecursionContext\Context;
use SplObjectStorage;
use stdClass;

#[CoversClass(Exporter::class)]
#[UsesClass(ObjectExporterChain::class)]
#[Small]
final class ExporterTest extends TestCase
{
Expand Down Expand Up @@ -486,6 +488,27 @@ public function testShortenedRecursiveOccurredRecursion(): void
$this->assertEquals('*RECURSION*', (new Exporter)->shortenedRecursiveExport($value, $context));
}

public function testExportOfObjectsCanBeCustomized(): void
{
$objectExporter = $this->createStub(ObjectExporter::class);
$objectExporter->method('handles')->willReturn(true);
$objectExporter->method('export')->willReturn('custom object export');

$exporter = new Exporter(new ObjectExporterChain([$objectExporter]));

$this->assertSame(
<<<'EOT'
Array &0 [
0 => custom object export,
1 => custom object export,
]
EOT
,
$exporter->export([new stdClass, new stdClass]),
);

}

private function trimNewline(string $string): string
{
return preg_replace('/[ ]*\n/', "\n", $string);
Expand Down
62 changes: 62 additions & 0 deletions tests/ObjectExporterChainTest.php
@@ -0,0 +1,62 @@
<?php declare(strict_types=1);
/*
* This file is part of sebastian/exporter.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* 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));
}
}

0 comments on commit ac6d770

Please sign in to comment.