From b532c445ba09d52dfbe878303b00b9cfc8b7acc1 Mon Sep 17 00:00:00 2001 From: olegbaturin Date: Sun, 24 Nov 2024 00:55:10 +0700 Subject: [PATCH] 282 discover all nested objects (#287) * add tests * update dumper * fix styles * improve + tests * fix phpdoc * fix * fix --------- Co-authored-by: Sergei Predvoditelev --- src/Dumper.php | 48 +++---- tests/Unit/DumperTest.php | 255 +++++++++++++++++++++++++++++++++++++- 2 files changed, 280 insertions(+), 23 deletions(-) diff --git a/src/Dumper.php b/src/Dumper.php index e76b1aca..17c88238 100644 --- a/src/Dumper.php +++ b/src/Dumper.php @@ -9,6 +9,10 @@ use ReflectionException; use Yiisoft\VarDumper\ClosureExporter; +use function array_key_exists; +use function is_array; +use function is_object; + final class Dumper { private array $objects = []; @@ -47,23 +51,21 @@ public function asJson(int $depth = 50, bool $format = false): string /** * Export variable as JSON summary of topmost items. + * Dumper goes into the variable full depth to search all objects. * - * @param int $depth Maximum depth that the dumper should go into the variable. + * @param int $depth Maximum depth that the dumper should print out arrays. * @param bool $prettyPrint Whatever to format exported code. * * @return string JSON string containing summary. */ public function asJsonObjectsMap(int $depth = 50, bool $prettyPrint = false): string { - $this->buildObjectsCache($this->variable, $depth); - return $this->asJsonInternal($this->objects, $prettyPrint, $depth, 1, true); + $this->buildObjectsCache($this->variable); + return $this->asJsonInternal($this->objects, $prettyPrint, $depth + 2, 1, true); } - private function buildObjectsCache(mixed $variable, int $depth, int $level = 0): void + private function buildObjectsCache(mixed $variable, ?int $depth = null, int $level = 0): void { - if ($depth <= $level) { - return; - } if (is_object($variable)) { if (array_key_exists($variable::class, $this->excludedClasses) || array_key_exists($objectDescription = $this->getObjectDescription($variable), $this->objects) @@ -71,18 +73,22 @@ private function buildObjectsCache(mixed $variable, int $depth, int $level = 0): return; } $this->objects[$objectDescription] = $variable; - $variable = $this->getObjectProperties($variable); + } + $nextLevel = $level + 1; + if ($depth !== null && $depth <= $nextLevel) { + return; + } + + if (is_object($variable)) { + $variable = $this->getObjectProperties($variable); foreach ($variable as $value) { - $this->buildObjectsCache($value, $depth, 0); + $this->buildObjectsCache($value, $depth, $nextLevel); } return; } + if (is_array($variable)) { - $nextLevel = $level + 1; - if ($depth <= $nextLevel) { - return; - } foreach ($variable as $value) { $this->buildObjectsCache($value, $depth, $nextLevel); } @@ -149,10 +155,6 @@ private function dumpNestedInternal( break; case 'object': $objectDescription = $this->getObjectDescription($variable); - if ($depth <= $level || array_key_exists($variable::class, $this->excludedClasses)) { - $output = $objectDescription . ' (...)'; - break; - } if ($variable instanceof Closure) { $output = $inlineObject @@ -161,13 +163,17 @@ private function dumpNestedInternal( break; } - if (!array_key_exists($objectDescription, $this->objects)) { + if ($objectCollapseLevel < $level && array_key_exists($objectDescription, $this->objects)) { $output = 'object@' . $objectDescription; - $this->objects[$objectDescription] = $variable; break; } - if ($objectCollapseLevel < $level) { - $output = 'object@' . $objectDescription; + + if ( + $depth <= $level + || array_key_exists($variable::class, $this->excludedClasses) + || !array_key_exists($objectDescription, $this->objects) + ) { + $output = $objectDescription . ' (...)'; break; } diff --git a/tests/Unit/DumperTest.php b/tests/Unit/DumperTest.php index 85c81f25..05fd8e31 100644 --- a/tests/Unit/DumperTest.php +++ b/tests/Unit/DumperTest.php @@ -19,6 +19,256 @@ final class DumperTest extends TestCase { + public function testAsJsonObjectsMapLevelOne(): void + { + $object = new stdClass(); + $object->var = 'test'; + $objectId = spl_object_id($object); + + $this->assertSame( + <<asJsonObjectsMap(1, true) + ); + } + + public function testAsJsonObjectsMapNestedObject(): void + { + $nested2 = new stdClass(); + $nested2->name = 'nested2'; + $nested2Id = spl_object_id($nested2); + + $nested1 = new stdClass(); + $nested1->name = 'nested1'; + $nested1->var = $nested2; + $nested1Id = spl_object_id($nested1); + + $object = new stdClass(); + $object->name = 'root'; + $object->var = $nested1; + $objectId = spl_object_id($object); + + $this->assertSame( + <<asJsonObjectsMap(1, true) + ); + } + + public function testAsJsonObjectsMapArrayWithObject(): void + { + $nested2 = new stdClass(); + $nested2->name = 'nested2'; + $nested2Id = spl_object_id($nested2); + + $nested1 = new stdClass(); + $nested1->name = 'nested1'; + $nested1->var = [$nested2]; + $nested1Id = spl_object_id($nested1); + + $object = new stdClass(); + $object->name = 'root'; + $object->var = $nested1; + $objectId = spl_object_id($object); + + $this->assertSame( + <<asJsonObjectsMap(0, true) + ); + } + + /** + * @dataProvider loopAsJsonObjectMapDataProvider + */ + public function testLoopAsJsonObjectsMap(mixed $var, int $depth, $expectedResult): void + { + $exportResult = Dumper::create($var)->asJsonObjectsMap($depth, true); + $this->assertEquals($expectedResult, $exportResult); + } + + public static function loopAsJsonObjectMapDataProvider(): iterable + { + // parent->child->parent structure + $nested1 = new stdClass(); + $nested1->id = 'nested1'; + $nested2 = new stdClass(); + $nested2->id = 'nested2'; + $nested2->nested1 = $nested1; + $nested1->nested2 = $nested2; + + $nested1Id = spl_object_id($nested1); + $nested2Id = spl_object_id($nested2); + + // 5 is a min level to reproduce buggy dumping of parent->child->parent structure + [$object1, $ids1] = self::getNested(5, $nested1); + yield 'nested loop - object' => [ + $object1, + 5, + << [ + $object2, + 6, + <<id = 'lvl0'; + $object3->lv11 = [ + 'id' => 'lvl1', + 'loop' => $nested1, + ]; + $object3Id = spl_object_id($object3); + + yield 'nested loop to object->array' => [ + $object3, + 3, + <<id = 'lvl0'; + + for ($i = 1; $i < $depth; $i++) { + $nested = new stdClass(); + $nested->id = 'lvl' . $i; + $lvl->{'lvl' . $i} = $nested; + $lvl = $nested; + $objectIds[] = spl_object_id($nested); + } + $lvl->{'lvl' . $i} = $data; + + return [$head, $objectIds]; + } + public function testObjectExpanding(): void { $var = $this->createNested(10, [[[[[[[[['key' => 'end']]]]]]]]]); @@ -93,7 +343,7 @@ public function testObjectExpanding(): void } JSON; - $actualResult = Dumper::create($var)->asJsonObjectsMap(4, true); + $actualResult = Dumper::create($var)->asJsonObjectsMap(2, true); $this->assertEquals($expectedResult, $actualResult); } @@ -178,6 +428,7 @@ public function testCacheDoesNotCoversObjectOutOfDumpDepth(): void $object1 = new stdClass(); $object1Id = spl_object_id($object1); $object2 = new stdClass(); + $object2Id = spl_object_id($object2); $variable = [$object1, [[$object2]]]; $expectedResult = sprintf('["object@stdClass#%d",["array (1 item) [...]"]]', $object1Id); @@ -189,7 +440,7 @@ public function testCacheDoesNotCoversObjectOutOfDumpDepth(): void $map = $dumper->asJsonObjectsMap(2); $this->assertEqualsWithoutLE( <<