Skip to content

Commit

Permalink
Revert "Revert #56"
Browse files Browse the repository at this point in the history
This reverts commit d0d99db.
  • Loading branch information
sebastianbergmann committed Jun 17, 2024
1 parent d0d99db commit 3992763
Show file tree
Hide file tree
Showing 8 changed files with 212 additions and 2 deletions.
7 changes: 7 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

All notable changes are documented in this file using the [Keep a CHANGELOG](https://keepachangelog.com/) principles.

## [6.1.0] - 2024-MM-DD

### Added

* [#56](https://github.com/sebastianbergmann/exporter/pull/56): The export of objects can now be customized using `ObjectExporter` objects

## [6.0.1] - 2024-03-02

### Changed
Expand Down Expand Up @@ -112,6 +118,7 @@ All notable changes are documented in this file using the [Keep a CHANGELOG](htt

* Remove HHVM-specific code that is no longer needed

[6.1.0]: https://github.com/sebastianbergmann/exporter/compare/6.0.1...main
[6.0.1]: https://github.com/sebastianbergmann/exporter/compare/6.0.0...6.0.1
[6.0.0]: https://github.com/sebastianbergmann/exporter/compare/5.1...6.0.0
[5.1.2]: https://github.com/sebastianbergmann/exporter/compare/5.1.1...5.1.2
Expand Down
22 changes: 20 additions & 2 deletions src/Exporter.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@

final readonly class Exporter
{
private ?ObjectExporter $objectExporter;

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

/**
* Exports a value as a string.
*
Expand Down Expand Up @@ -334,7 +341,18 @@ private function exportObject(object $value, RecursionContext $processed, int $i

$processed->add($value);

$array = $this->toArray($value);
if ($this->objectExporter !== null && $this->objectExporter->handles($value)) {
$buffer = $this->objectExporter->export($value, $this, $indentation);
} else {
$buffer = $this->defaultObjectExport($value, $processed, $indentation);
}

return $class . ' Object #' . spl_object_id($value) . ' (' . $buffer . ')';
}

private function defaultObjectExport(object $object, RecursionContext $processed, int $indentation): string
{
$array = $this->toArray($object);
$buffer = '';
$whitespace = str_repeat(' ', 4 * $indentation);

Expand All @@ -352,6 +370,6 @@ private function exportObject(object $value, RecursionContext $processed, int $i
$buffer = "\n" . $buffer . $whitespace;
}

return $class . ' Object #' . spl_object_id($value) . ' (' . $buffer . ')';
return $buffer;
}
}
17 changes: 17 additions & 0 deletions src/ObjectExporter.php
Original file line number Diff line number Diff line change
@@ -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, Exporter $exporter, int $indentation): string;
}
51 changes: 51 additions & 0 deletions src/ObjectExporterChain.php
Original file line number Diff line number Diff line change
@@ -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, Exporter $exporter, int $indentation): string
{
foreach ($this->exporter as $objectExporter) {
if ($objectExporter->handles($object)) {
return $objectExporter->export($object, $exporter, $indentation);
}
}

throw new ObjectNotSupportedException;
}
}
16 changes: 16 additions & 0 deletions src/exception/Exception.php
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
{
}
21 changes: 21 additions & 0 deletions tests/ExporterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,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->assertStringMatchesFormat(
<<<'EOT'
Array &0 [
0 => stdClass Object #%d (custom object export),
1 => stdClass Object #%d (custom object export),
]
EOT
,
$exporter->export([new stdClass, new stdClass]),
);

}

private function trimNewline(string $string): string
{
return preg_replace('/[ ]*\n/', "\n", $string);
Expand Down
64 changes: 64 additions & 0 deletions tests/ObjectExporterChainTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?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\Attributes\UsesClass;
use PHPUnit\Framework\TestCase;
use stdClass;

#[CoversClass(ObjectExporterChain::class)]
#[UsesClass(Exporter::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, new Exporter, 0));
}

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, new Exporter, 0));
}
}

0 comments on commit 3992763

Please sign in to comment.