diff --git a/src/Dumper.php b/src/Dumper.php index 72d579b96..6119bff90 100644 --- a/src/Dumper.php +++ b/src/Dumper.php @@ -13,12 +13,14 @@ final class Dumper private array $objects = []; private static ?ClosureExporter $closureExporter = null; + private array $excludedClasses = []; /** * @param mixed $variable Variable to dump. */ - private function __construct(private mixed $variable, private array $excludedClasses = []) + private function __construct(private mixed $variable, array $excludedClasses = []) { + $this->excludedClasses = array_reverse($excludedClasses); } /** @@ -42,7 +44,7 @@ public static function create(mixed $variable, array $excludedClasses = []): sel */ public function asJson(int $depth = 50, bool $format = false): string|bool { - return $this->asJsonInternal($this->variable, $format, $depth, 0); + return $this->asJsonInternal($this->variable, $format, $depth, 0, false, true); } /** @@ -57,7 +59,7 @@ public function asJsonObjectsMap(int $depth = 50, bool $prettyPrint = false): st { $this->buildObjectsCache($this->variable, $depth); - return $this->asJsonInternal($this->objects, $prettyPrint, $depth, 1); + return $this->asJsonInternal($this->objects, $prettyPrint, $depth, 1, true, false); } private function buildObjectsCache($variable, int $depth, int $level = 0): void @@ -66,11 +68,12 @@ private function buildObjectsCache($variable, int $depth, int $level = 0): void return; } if (is_object($variable)) { - if (in_array($variable, $this->objects, true) - || in_array($variable::class, $this->excludedClasses, true)) { + if (array_key_exists($variable::class, $this->excludedClasses) || + array_key_exists(($objectDescription = $this->getObjectDescription($variable)), $this->objects) + ) { return; } - $this->objects[] = $variable; + $this->objects[$objectDescription] = $variable; $variable = $this->getObjectProperties($variable); } if (is_array($variable)) { @@ -80,21 +83,28 @@ private function buildObjectsCache($variable, int $depth, int $level = 0): void } } - private function asJsonInternal($variable, bool $format, int $depth, int $objectCollapseLevel): string|bool + private function asJsonInternal( + $variable, + bool $format, + int $depth, + int $objectCollapseLevel, + bool $inlineObject, + bool $buildCache, + ): string|bool { $options = JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE; if ($format) { $options |= JSON_PRETTY_PRINT; } + if ($buildCache) { + $this->buildObjectsCache($variable, $depth); + } - return json_encode($this->dumpNested($variable, $depth, $objectCollapseLevel), $options); - } - - private function dumpNested($variable, int $depth, int $objectCollapseLevel): mixed - { - $this->buildObjectsCache($variable, $depth); - return $this->dumpNestedInternal($variable, $depth, 0, $objectCollapseLevel); + return json_encode( + $this->dumpNestedInternal($variable, $depth, 0, $objectCollapseLevel, $inlineObject), + $options, + ); } private function getObjectProperties(object $var): array @@ -106,7 +116,7 @@ private function getObjectProperties(object $var): array return (array)$var; } - private function dumpNestedInternal($var, int $depth, int $level, int $objectCollapseLevel = 0): mixed + private function dumpNestedInternal($var, int $depth, int $level, int $objectCollapseLevel, bool $inlineObject): mixed { $output = $var; @@ -119,13 +129,13 @@ private function dumpNestedInternal($var, int $depth, int $level, int $objectCol $output = []; foreach ($var as $key => $value) { $keyDisplay = str_replace("\0", '::', trim((string)$key)); - $output[$keyDisplay] = $this->dumpNestedInternal($value, $depth, $level + 1, $objectCollapseLevel); + $output[$keyDisplay] = $this->dumpNestedInternal($value, $depth, $level + 1, $objectCollapseLevel, $inlineObject); } break; case 'object': $objectDescription = $this->getObjectDescription($var); - if ($depth <= $level || in_array($var::class, $this->excludedClasses, true)) { + if ($depth <= $level || array_key_exists($var::class, $this->excludedClasses)) { $output = $objectDescription . ' (...)'; break; } @@ -135,7 +145,7 @@ private function dumpNestedInternal($var, int $depth, int $level, int $objectCol break; } - if ($objectCollapseLevel < $level && in_array($var, $this->objects, true)) { + if ($objectCollapseLevel < $level && array_key_exists($objectDescription, $this->objects)) { $output = 'object@' . $objectDescription; break; } @@ -155,9 +165,13 @@ private function dumpNestedInternal($var, int $depth, int $level, int $objectCol $value, $depth, $level + 1, - $objectCollapseLevel + $objectCollapseLevel, + $inlineObject, ); } + if ($inlineObject) { + $output = $output[$objectDescription]; + } break; case 'resource': case 'resource (closed)': diff --git a/src/Storage/FileStorage.php b/src/Storage/FileStorage.php index 22809ba71..146c215fe 100644 --- a/src/Storage/FileStorage.php +++ b/src/Storage/FileStorage.php @@ -79,12 +79,8 @@ public function flush(): void try { FileHelper::ensureDirectory($basePath); $dumper = Dumper::create($this->getData(), $this->excludedClasses); - $jsonData = $dumper->asJson(); - file_put_contents($basePath . self::TYPE_DATA . '.json', $jsonData); - - $jsonObjects = Json::decode($dumper->asJsonObjectsMap()); - $jsonObjects = $this->reindexObjects($jsonObjects); - file_put_contents($basePath . self::TYPE_OBJECTS . '.json', Dumper::create($jsonObjects)->asJson()); + file_put_contents($basePath . self::TYPE_DATA . '.json', $dumper->asJson(30)); + file_put_contents($basePath . self::TYPE_OBJECTS . '.json', $dumper->asJsonObjectsMap(30)); $summaryData = Dumper::create($this->collectSummaryData())->asJson(); file_put_contents($basePath . self::TYPE_SUMMARY . '.json', $summaryData); diff --git a/tests/Unit/DumperTest.php b/tests/Unit/DumperTest.php index 1ae7fd72d..f89425f0e 100644 --- a/tests/Unit/DumperTest.php +++ b/tests/Unit/DumperTest.php @@ -14,10 +14,6 @@ final class DumperTest extends TestCase { /** * @dataProvider asJsonObjectMapDataProvider - * - * @param string $expectedResult - * - * @group JOM */ public function testAsJsonObjectsMap(mixed $var, $expectedResult): void { @@ -40,13 +36,13 @@ public function asJsonObjectMapDataProvider(): array [ $user, <<read(StorageInterface::TYPE_DATA); - $encodedResult = \json_decode(Dumper::create($result)->asJson(), true, 512, JSON_THROW_ON_ERROR); + $dumper = Dumper::create($result); + $encodedResult = \json_decode($dumper->asJson(), true, 512, JSON_THROW_ON_ERROR); $this->assertEquals([$idGenerator->getId() => $encodedExpectedData], $encodedResult); } @@ -69,14 +70,13 @@ abstract public function getStorage(DebuggerIdGenerator $idGenerator): StorageIn public static function dataProvider(): iterable { - yield [[1, 2, 3]]; - yield [['string']]; - yield [[[['', 0, false]]]]; - yield [['test']]; - yield [[false]]; - yield [[null]]; - yield [[0]]; - yield [[new stdClass()]]; + yield 'integers' => [[1, 2, 3]]; + yield 'string' => [['string']]; + yield 'empty values' => [[[['', 0, false]]]]; + yield 'false' => [[false]]; + yield 'null' => [[null]]; + yield 'zero' => [[0]]; + yield 'stdClass' => [[new stdClass()]]; } protected function createFakeCollector(array $data)