From 5f9dc382bff11d789c813d42f211735019611751 Mon Sep 17 00:00:00 2001 From: Sebastian Bergmann Date: Thu, 30 Jul 2026 07:56:57 +0200 Subject: [PATCH] Ask custom object exporter for representation before consulting recursion context --- src/Exporter.php | 13 +++++++++---- tests/ExporterTest.php | 29 +++++++++++++++++++++++++++-- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/src/Exporter.php b/src/Exporter.php index 579c571..cb08299 100644 --- a/src/Exporter.php +++ b/src/Exporter.php @@ -518,6 +518,15 @@ final readonly class Exporter private function exportObject(object $value, RecursionContext $processed, int $indentation): string { + // A custom object exporter is responsible for the entire + // representation of the object it handles. Therefore, it is asked for + // that representation before the recursion context is consulted: every + // occurrence of such an object is exported the same way instead of + // repeated occurrences being replaced with a reference to the object. + if ($this->objectExporter !== null && $this->objectExporter->handles($value)) { + return $this->objectExporter->export($value, $this, $indentation); + } + $class = $value::class; if ($processed->contains($value) !== false) { @@ -526,10 +535,6 @@ final readonly class Exporter $processed->add($value); - if ($this->objectExporter !== null && $this->objectExporter->handles($value)) { - return $this->objectExporter->export($value, $this, $indentation); - } - $array = $this->toArray($value); $buffer = ''; diff --git a/tests/ExporterTest.php b/tests/ExporterTest.php index 2fee85c..46a2c9a 100644 --- a/tests/ExporterTest.php +++ b/tests/ExporterTest.php @@ -716,7 +716,7 @@ EOT, ); } - public function testObjectHandledByCustomObjectExporterIsOnlyExportedOnce(): void + public function testEveryOccurrenceOfObjectHandledByCustomObjectExporterIsExportedByThatObjectExporter(): void { $exporter = new Exporter( 0, @@ -730,10 +730,35 @@ EOT, $object = new stdClass; - $this->assertStringMatchesFormat( + $this->assertSame( <<<'EOT' Array &0 [ 0 => stdClass (indentation: 1), + 1 => stdClass (indentation: 1), +] +EOT, + $exporter->export([$object, $object]), + ); + } + + public function testRepeatedOccurrenceOfObjectNotHandledByCustomObjectExporterIsReplacedWithReferenceToObject(): void + { + $exporter = new Exporter( + 0, + 40, + new ObjectExporterChain( + [ + new ObjectExporterThatHandlesNoObject, + ], + ), + ); + + $object = new stdClass; + + $this->assertStringMatchesFormat( + <<<'EOT' +Array &0 [ + 0 => stdClass Object #%d (), 1 => stdClass Object #%d, ] EOT,