From a33a3813b2b8546e5c614451f707c0a07ae1cefa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Paris?= Date: Thu, 12 Oct 2023 13:09:49 +0200 Subject: [PATCH] Copy Debug class from doctrine/common This reduces our dependency to this shared library that now holds very little code we use. The class has not been copied verbatim: - Unused parameters and methods have been removed. - The class is final and internal. - Coding standards have been enforced, including enabling strict_types, which lead to casting a variable to string before feeding it to explode(). - A bug found by static analysis has been addressed, where an INI setting obtained with ini_get() was compared with true, which is never returned by that function. - Tests are improved to run on all PHP versions --- .../Tools/Console/Command/RunDqlCommand.php | 4 +- lib/Doctrine/ORM/Tools/Debug.php | 168 +++++++++++++++++ phpcs.xml.dist | 5 + psalm-baseline.xml | 7 +- psalm.xml | 5 + tests/Doctrine/Tests/ORM/Tools/DebugTest.php | 174 ++++++++++++++++++ .../Tests/ORM/Tools/TestAsset/ChildClass.php | 15 ++ .../ChildWithSameAttributesClass.php | 15 ++ .../Tests/ORM/Tools/TestAsset/ParentClass.php | 15 ++ 9 files changed, 400 insertions(+), 8 deletions(-) create mode 100644 lib/Doctrine/ORM/Tools/Debug.php create mode 100644 tests/Doctrine/Tests/ORM/Tools/DebugTest.php create mode 100644 tests/Doctrine/Tests/ORM/Tools/TestAsset/ChildClass.php create mode 100644 tests/Doctrine/Tests/ORM/Tools/TestAsset/ChildWithSameAttributesClass.php create mode 100644 tests/Doctrine/Tests/ORM/Tools/TestAsset/ParentClass.php diff --git a/lib/Doctrine/ORM/Tools/Console/Command/RunDqlCommand.php b/lib/Doctrine/ORM/Tools/Console/Command/RunDqlCommand.php index f8611da0b1..61f1eef78f 100644 --- a/lib/Doctrine/ORM/Tools/Console/Command/RunDqlCommand.php +++ b/lib/Doctrine/ORM/Tools/Console/Command/RunDqlCommand.php @@ -4,8 +4,8 @@ namespace Doctrine\ORM\Tools\Console\Command; -use Doctrine\Common\Util\Debug; use Doctrine\ORM\Tools\Console\CommandCompatibility; +use Doctrine\ORM\Tools\Debug; use LogicException; use RuntimeException; use Symfony\Component\Console\Input\InputArgument; @@ -116,7 +116,7 @@ private function doExecute(InputInterface $input, OutputInterface $output): int $resultSet = $query->execute([], constant($hydrationMode)); - $ui->text(Debug::dump($resultSet, (int) $input->getOption('depth'), true, false)); + $ui->text(Debug::dump($resultSet, (int) $input->getOption('depth'))); return 0; } diff --git a/lib/Doctrine/ORM/Tools/Debug.php b/lib/Doctrine/ORM/Tools/Debug.php new file mode 100644 index 0000000000..153abac4a4 --- /dev/null +++ b/lib/Doctrine/ORM/Tools/Debug.php @@ -0,0 +1,168 @@ +toArray(); + } + + if (! $maxDepth) { + return is_object($var) ? get_class($var) + : (is_array($var) ? 'Array(' . count($var) . ')' : $var); + } + + if (is_array($var)) { + $return = []; + + foreach ($var as $k => $v) { + $return[$k] = self::export($v, $maxDepth - 1); + } + + return $return; + } + + if (! is_object($var)) { + return $var; + } + + $return = new stdClass(); + if ($var instanceof DateTimeInterface) { + $return->__CLASS__ = get_class($var); + $return->date = $var->format('c'); + $return->timezone = $var->getTimezone()->getName(); + + return $return; + } + + $return->__CLASS__ = DefaultProxyClassNameResolver::getClass($var); + + if ($var instanceof Proxy) { + $return->__IS_PROXY__ = true; + $return->__PROXY_INITIALIZED__ = $var->__isInitialized(); + } + + if ($var instanceof ArrayObject || $var instanceof ArrayIterator) { + $return->__STORAGE__ = self::export($var->getArrayCopy(), $maxDepth - 1); + } + + return self::fillReturnWithClassAttributes($var, $return, $maxDepth); + } + + /** + * Fill the $return variable with class attributes + * Based on obj2array function from {@see https://secure.php.net/manual/en/function.get-object-vars.php#47075} + * + * @param object $var + * + * @return mixed + */ + private static function fillReturnWithClassAttributes($var, stdClass $return, int $maxDepth) + { + $clone = (array) $var; + + foreach (array_keys($clone) as $key) { + $aux = explode("\0", (string) $key); + $name = end($aux); + if ($aux[0] === '') { + $name .= ':' . ($aux[1] === '*' ? 'protected' : $aux[1] . ':private'); + } + + $return->$name = self::export($clone[$key], $maxDepth - 1); + } + + return $return; + } +} diff --git a/phpcs.xml.dist b/phpcs.xml.dist index b3c2ab6149..9f7dba8605 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -59,6 +59,11 @@ tests/* + + lib/Doctrine/ORM/Tools/Debug.php + tests/Doctrine/Tests/ORM/Tools/DebugTest.php + + lib/Doctrine/ORM/Events.php lib/Doctrine/ORM/Tools/ToolEvents.php diff --git a/psalm-baseline.xml b/psalm-baseline.xml index c9c79e0f4a..72069f0765 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -536,8 +536,8 @@ $class $class - $platformFamily + new UuidGenerator() @@ -2415,11 +2415,6 @@ getAllClassNames - - - getOption('depth'), true, false)]]> - - int diff --git a/psalm.xml b/psalm.xml index 81d8fe8476..8905a3c408 100644 --- a/psalm.xml +++ b/psalm.xml @@ -125,6 +125,11 @@ + + + + + diff --git a/tests/Doctrine/Tests/ORM/Tools/DebugTest.php b/tests/Doctrine/Tests/ORM/Tools/DebugTest.php new file mode 100644 index 0000000000..413c577fa6 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Tools/DebugTest.php @@ -0,0 +1,174 @@ +foo = 'bar'; + $obj->bar = 1234; + + $var = Debug::export($obj, 2); + self::assertEquals('stdClass', $var->__CLASS__); + } + + public function testExportObjectWithReference(): void + { + $foo = 'bar'; + $bar = ['foo' => & $foo]; + $baz = (object) $bar; + + $var = Debug::export($baz, 2); + $baz->foo = 'tab'; + + self::assertEquals('bar', $var->foo); + self::assertEquals('tab', $bar['foo']); + } + + public function testExportArray(): void + { + $array = ['a' => 'b', 'b' => ['c', 'd' => ['e', 'f']]]; + $var = Debug::export($array, 2); + $expected = $array; + $expected['b']['d'] = 'Array(2)'; + self::assertEquals($expected, $var); + } + + public function testExportDateTime(): void + { + $obj = new DateTime('2010-10-10 10:10:10', new DateTimeZone('UTC')); + + $var = Debug::export($obj, 2); + self::assertEquals('DateTime', $var->__CLASS__); + self::assertEquals('2010-10-10T10:10:10+00:00', $var->date); + } + + public function testExportDateTimeImmutable(): void + { + $obj = new DateTimeImmutable('2010-10-10 10:10:10', new DateTimeZone('UTC')); + + $var = Debug::export($obj, 2); + self::assertEquals('DateTimeImmutable', $var->__CLASS__); + self::assertEquals('2010-10-10T10:10:10+00:00', $var->date); + } + + public function testExportDateTimeZone(): void + { + $obj = new DateTimeImmutable('2010-10-10 12:34:56', new DateTimeZone('Europe/Rome')); + + $var = Debug::export($obj, 2); + self::assertEquals('DateTimeImmutable', $var->__CLASS__); + self::assertEquals('2010-10-10T12:34:56+02:00', $var->date); + } + + public function testExportArrayTraversable(): void + { + $obj = new ArrayObject(['foobar']); + + $var = Debug::export($obj, 2); + self::assertContains('foobar', $var->__STORAGE__); + + $it = new ArrayIterator(['foobar']); + + $var = Debug::export($it, 5); + self::assertContains('foobar', $var->__STORAGE__); + } + + /** + * @param array $expected + * + * @dataProvider provideAttributesCases + */ + public function testExportParentAttributes(TestAsset\ParentClass $class, array $expected): void + { + $actualRepresentation = print_r($class, true); + $expectedRepresentation = print_r($expected, true); + + $actualRepresentation = substr($actualRepresentation, strpos($actualRepresentation, '(')); + $expectedRepresentation = substr($expectedRepresentation, strpos($expectedRepresentation, '(')); + + self::assertSame($expectedRepresentation, $actualRepresentation); + + $var = Debug::export($class, 3); + $var = (array) $var; + unset($var['__CLASS__']); + + self::assertSame($expected, $var); + } + + public function testCollectionsAreCastIntoArrays(): void + { + $collection = new ArrayCollection(); + $collection->add('foo'); + $collection->add('bar'); + + $var = Debug::export($collection, 2); + self::assertEquals(['foo', 'bar'], $var); + } + + /** + * @psalm-return array + */ + public function provideAttributesCases(): iterable + { + return [ + 'different-attributes' => [ + new TestAsset\ChildClass(), + version_compare(PHP_VERSION, '8.1', '<') ? + [ + 'childPublicAttribute' => 4, + 'childProtectedAttribute:protected' => 5, + 'childPrivateAttribute:Doctrine\Tests\ORM\Tools\TestAsset\ChildClass:private' => 6, + 'parentPublicAttribute' => 1, + 'parentProtectedAttribute:protected' => 2, + 'parentPrivateAttribute:Doctrine\Tests\ORM\Tools\TestAsset\ParentClass:private' => 3, + ] : + [ + 'parentPublicAttribute' => 1, + 'parentProtectedAttribute:protected' => 2, + 'parentPrivateAttribute:Doctrine\Tests\ORM\Tools\TestAsset\ParentClass:private' => 3, + 'childPublicAttribute' => 4, + 'childProtectedAttribute:protected' => 5, + 'childPrivateAttribute:Doctrine\Tests\ORM\Tools\TestAsset\ChildClass:private' => 6, + ], + ], + 'same-attributes' => [ + new TestAsset\ChildWithSameAttributesClass(), + version_compare(PHP_VERSION, '8.1', '<') ? + [ + 'parentPublicAttribute' => 4, + 'parentProtectedAttribute:protected' => 5, + 'parentPrivateAttribute:Doctrine\Tests\ORM\Tools\TestAsset\ChildWithSameAttributesClass:private' => 6, + 'parentPrivateAttribute:Doctrine\Tests\ORM\Tools\TestAsset\ParentClass:private' => 3, + ] : + [ + 'parentPublicAttribute' => 4, + 'parentProtectedAttribute:protected' => 5, + 'parentPrivateAttribute:Doctrine\Tests\ORM\Tools\TestAsset\ParentClass:private' => 3, + 'parentPrivateAttribute:Doctrine\Tests\ORM\Tools\TestAsset\ChildWithSameAttributesClass:private' => 6, + ], + ], + ]; + } +} diff --git a/tests/Doctrine/Tests/ORM/Tools/TestAsset/ChildClass.php b/tests/Doctrine/Tests/ORM/Tools/TestAsset/ChildClass.php new file mode 100644 index 0000000000..35e11456da --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Tools/TestAsset/ChildClass.php @@ -0,0 +1,15 @@ +