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 @@ +