diff --git a/lib/Doctrine/DBAL/Tools/Console/Command/RunSqlCommand.php b/lib/Doctrine/DBAL/Tools/Console/Command/RunSqlCommand.php
index 27982d08950..f65b0de0f7e 100644
--- a/lib/Doctrine/DBAL/Tools/Console/Command/RunSqlCommand.php
+++ b/lib/Doctrine/DBAL/Tools/Console/Command/RunSqlCommand.php
@@ -19,14 +19,13 @@
namespace Doctrine\DBAL\Tools\Console\Command;
+use Doctrine\DBAL\Tools\Dumper;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputOption;
use function is_numeric;
-use function ob_get_clean;
-use function ob_start;
use function stripos;
/**
@@ -84,10 +83,6 @@ protected function execute(InputInterface $input, OutputInterface $output)
$resultSet = $conn->executeUpdate($sql);
}
- ob_start();
- \Doctrine\Common\Util\Debug::dump($resultSet, (int) $depth);
- $message = ob_get_clean();
-
- $output->write($message);
+ $output->write(Dumper::dump($resultSet, (int) $depth));
}
}
diff --git a/lib/Doctrine/DBAL/Tools/Dumper.php b/lib/Doctrine/DBAL/Tools/Dumper.php
new file mode 100644
index 00000000000..6c7bbcf7e47
--- /dev/null
+++ b/lib/Doctrine/DBAL/Tools/Dumper.php
@@ -0,0 +1,177 @@
+toArray();
+ }
+
+ if ($maxDepth === 0) {
+ 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__ = self::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", $key);
+ $name = end($aux);
+ if ($aux[0] === '') {
+ $name .= ':' . ($aux[1] === '*' ? 'protected' : $aux[1] . ':private');
+ }
+ $return->$name = self::export($clone[$key], $maxDepth - 1);
+ ;
+ }
+
+ return $return;
+ }
+
+ /**
+ * @param object $object
+ */
+ private static function getClass($object) : string
+ {
+ $class = get_class($object);
+
+ if (! class_exists(Proxy::class)) {
+ return $class;
+ }
+
+ $pos = strrpos($class, '\\' . Proxy::MARKER . '\\');
+
+ if ($pos === false) {
+ return $class;
+ }
+
+ return substr($class, $pos + Proxy::MARKER_LENGTH + 2);
+ }
+}
diff --git a/phpcs.xml.dist b/phpcs.xml.dist
index 84033c9a71e..7385edd063d 100644
--- a/phpcs.xml.dist
+++ b/phpcs.xml.dist
@@ -31,4 +31,8 @@
lib/Doctrine/DBAL/Events.php
+
+
+ tests/Doctrine/Tests/DBAL/Tools/TestAsset/*
+
diff --git a/tests/Doctrine/Tests/DBAL/Tools/DumperTest.php b/tests/Doctrine/Tests/DBAL/Tools/DumperTest.php
new file mode 100644
index 00000000000..cf4fe4d560a
--- /dev/null
+++ b/tests/Doctrine/Tests/DBAL/Tools/DumperTest.php
@@ -0,0 +1,137 @@
+foo = 'bar';
+ $obj->bar = 1234;
+
+ $var = Dumper::export($obj, 2);
+ self::assertEquals('stdClass', $var->__CLASS__);
+ }
+
+ public function testExportObjectWithReference()
+ {
+ $foo = 'bar';
+ $bar = ['foo' => & $foo];
+ $baz = (object) $bar;
+
+ $var = Dumper::export($baz, 2);
+ $baz->foo = 'tab';
+
+ self::assertEquals('bar', $var->foo);
+ self::assertEquals('tab', $bar['foo']);
+ }
+
+ public function testExportArray()
+ {
+ $array = ['a' => 'b', 'b' => ['c', 'd' => ['e', 'f']]];
+ $var = Dumper::export($array, 2);
+ $expected = $array;
+ $expected['b']['d'] = 'Array(2)';
+ self::assertEquals($expected, $var);
+ }
+
+ public function testExportDateTime()
+ {
+ $obj = new DateTime('2010-10-10 10:10:10', new DateTimeZone('UTC'));
+
+ $var = Dumper::export($obj, 2);
+ self::assertEquals('DateTime', $var->__CLASS__);
+ self::assertEquals('2010-10-10T10:10:10+00:00', $var->date);
+ }
+
+ public function testExportDateTimeImmutable()
+ {
+ $obj = new DateTimeImmutable('2010-10-10 10:10:10', new DateTimeZone('UTC'));
+
+ $var = Dumper::export($obj, 2);
+ self::assertEquals('DateTimeImmutable', $var->__CLASS__);
+ self::assertEquals('2010-10-10T10:10:10+00:00', $var->date);
+ }
+
+ public function testExportDateTimeZone()
+ {
+ $obj = new DateTimeImmutable('2010-10-10 12:34:56', new DateTimeZone('Europe/Rome'));
+
+ $var = Dumper::export($obj, 2);
+ self::assertEquals('DateTimeImmutable', $var->__CLASS__);
+ self::assertEquals('2010-10-10T12:34:56+02:00', $var->date);
+ }
+
+ public function testExportArrayTraversable()
+ {
+ $obj = new ArrayObject(['foobar']);
+
+ $var = Dumper::export($obj, 2);
+ self::assertContains('foobar', $var->__STORAGE__);
+
+ $it = new ArrayIterator(['foobar']);
+
+ $var = Dumper::export($it, 5);
+ self::assertContains('foobar', $var->__STORAGE__);
+ }
+
+ /**
+ * @param string[] $expected
+ *
+ * @dataProvider provideAttributesCases
+ */
+ public function testExportParentAttributes(TestAsset\ParentClass $class, array $expected)
+ {
+ $print_r_class = print_r($class, true);
+ $print_r_expected = print_r($expected, true);
+
+ $print_r_class = substr($print_r_class, strpos($print_r_class, '('));
+ $print_r_expected = substr($print_r_expected, strpos($print_r_expected, '('));
+
+ self::assertSame($print_r_class, $print_r_expected);
+
+ $var = Dumper::export($class, 3);
+ $var = (array) $var;
+ unset($var['__CLASS__']);
+
+ self::assertSame($expected, $var);
+ }
+
+ public function provideAttributesCases()
+ {
+ return [
+ 'different-attributes' => [
+ new TestAsset\ChildClass(),
+ [
+ 'childPublicAttribute' => 4,
+ 'childProtectedAttribute:protected' => 5,
+ 'childPrivateAttribute:Doctrine\Tests\DBAL\Tools\TestAsset\ChildClass:private' => 6,
+ 'parentPublicAttribute' => 1,
+ 'parentProtectedAttribute:protected' => 2,
+ 'parentPrivateAttribute:Doctrine\Tests\DBAL\Tools\TestAsset\ParentClass:private' => 3,
+ ],
+ ],
+ 'same-attributes' => [
+ new TestAsset\ChildWithSameAttributesClass(),
+ [
+ 'parentPublicAttribute' => 4,
+ 'parentProtectedAttribute:protected' => 5,
+ 'parentPrivateAttribute:Doctrine\Tests\DBAL\Tools\TestAsset\ChildWithSameAttributesClass:private' => 6,
+ 'parentPrivateAttribute:Doctrine\Tests\DBAL\Tools\TestAsset\ParentClass:private' => 3,
+ ],
+ ],
+ ];
+ }
+}
diff --git a/tests/Doctrine/Tests/DBAL/Tools/TestAsset/ChildClass.php b/tests/Doctrine/Tests/DBAL/Tools/TestAsset/ChildClass.php
new file mode 100644
index 00000000000..19e7020c340
--- /dev/null
+++ b/tests/Doctrine/Tests/DBAL/Tools/TestAsset/ChildClass.php
@@ -0,0 +1,15 @@
+