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..8aa232c386
--- /dev/null
+++ b/lib/Doctrine/ORM/Tools/Debug.php
@@ -0,0 +1,164 @@
+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 (! $isObj) {
+ 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__ = ClassUtils::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 420e6f91c1..147b39dfb8 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 38a2972861..9d649f6def 100644
--- a/psalm-baseline.xml
+++ b/psalm-baseline.xml
@@ -536,8 +536,8 @@
$class
$class
-
$platformFamily
+
new UuidGenerator()
@@ -2406,11 +2406,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..0c3c879c83
--- /dev/null
+++ b/tests/Doctrine/Tests/ORM/Tools/DebugTest.php
@@ -0,0 +1,148 @@
+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);
+ }
+
+ /**
+ * @psalm-return array
+ */
+ public function provideAttributesCases(): iterable
+ {
+ $differentAttributes = [
+ '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,
+ ];
+ $sameAttributes = [
+ 'parentPublicAttribute' => 4,
+ 'parentProtectedAttribute:protected' => 5,
+ 'parentPrivateAttribute:Doctrine\Tests\ORM\Tools\TestAsset\ParentClass:private' => 3,
+ 'parentPrivateAttribute:Doctrine\Tests\ORM\Tools\TestAsset\ChildWithSameAttributesClass:private' => 6,
+ ];
+ if (version_compare(PHP_VERSION, '8.1', '<')) {
+ ksort($differentAttributes);
+ ksort($sameAttributes);
+ }
+
+ yield 'different-attributes' => [new TestAsset\ChildClass(), $differentAttributes];
+
+ yield 'same-attributes' => [new TestAsset\ChildWithSameAttributesClass(), $sameAttributes];
+ }
+}
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 @@
+