Skip to content

Commit

Permalink
Copy Debug class from doctrine/common
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
greg0ire committed Oct 12, 2023
1 parent c5137da commit d15fbed
Show file tree
Hide file tree
Showing 9 changed files with 368 additions and 8 deletions.
4 changes: 2 additions & 2 deletions lib/Doctrine/ORM/Tools/Console/Command/RunDqlCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down
164 changes: 164 additions & 0 deletions lib/Doctrine/ORM/Tools/Debug.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
<?php

declare(strict_types=1);

namespace Doctrine\ORM\Tools;

use ArrayIterator;
use ArrayObject;
use DateTimeInterface;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Util\ClassUtils;
use Doctrine\Persistence\Proxy;
use stdClass;

use function array_keys;
use function count;
use function end;
use function explode;
use function extension_loaded;
use function get_class;
use function html_entity_decode;
use function ini_get;
use function ini_set;
use function is_array;
use function is_object;
use function ob_end_clean;
use function ob_get_contents;
use function ob_start;
use function strip_tags;
use function var_dump;

/**
* Static class containing most used debug methods.
*
* @internal
*
* @link www.doctrine-project.org
*/
final class Debug
{
/**
* Private constructor (prevents instantiation).
*/
private function __construct()

Check warning on line 44 in lib/Doctrine/ORM/Tools/Debug.php

View check run for this annotation

Codecov / codecov/patch

lib/Doctrine/ORM/Tools/Debug.php#L44

Added line #L44 was not covered by tests
{
}

Check warning on line 46 in lib/Doctrine/ORM/Tools/Debug.php

View check run for this annotation

Codecov / codecov/patch

lib/Doctrine/ORM/Tools/Debug.php#L46

Added line #L46 was not covered by tests

/**
* Prints a dump of the public, protected and private properties of $var.
*
* @link https://xdebug.org/
*
* @param mixed $var The variable to dump.
* @param int $maxDepth The maximum nesting level for object properties.
*/
public static function dump($var, int $maxDepth = 2): string

Check warning on line 56 in lib/Doctrine/ORM/Tools/Debug.php

View check run for this annotation

Codecov / codecov/patch

lib/Doctrine/ORM/Tools/Debug.php#L56

Added line #L56 was not covered by tests
{
$html = ini_get('html_errors');

Check warning on line 58 in lib/Doctrine/ORM/Tools/Debug.php

View check run for this annotation

Codecov / codecov/patch

lib/Doctrine/ORM/Tools/Debug.php#L58

Added line #L58 was not covered by tests

if ($html !== '1') {
ini_set('html_errors', 'on');

Check warning on line 61 in lib/Doctrine/ORM/Tools/Debug.php

View check run for this annotation

Codecov / codecov/patch

lib/Doctrine/ORM/Tools/Debug.php#L60-L61

Added lines #L60 - L61 were not covered by tests
}

if (extension_loaded('xdebug')) {
ini_set('xdebug.var_display_max_depth', (string) $maxDepth);

Check warning on line 65 in lib/Doctrine/ORM/Tools/Debug.php

View check run for this annotation

Codecov / codecov/patch

lib/Doctrine/ORM/Tools/Debug.php#L64-L65

Added lines #L64 - L65 were not covered by tests
}

$var = self::export($var, $maxDepth);

Check warning on line 68 in lib/Doctrine/ORM/Tools/Debug.php

View check run for this annotation

Codecov / codecov/patch

lib/Doctrine/ORM/Tools/Debug.php#L68

Added line #L68 was not covered by tests

ob_start();
var_dump($var);

Check warning on line 71 in lib/Doctrine/ORM/Tools/Debug.php

View check run for this annotation

Codecov / codecov/patch

lib/Doctrine/ORM/Tools/Debug.php#L70-L71

Added lines #L70 - L71 were not covered by tests

$dump = ob_get_contents();

Check warning on line 73 in lib/Doctrine/ORM/Tools/Debug.php

View check run for this annotation

Codecov / codecov/patch

lib/Doctrine/ORM/Tools/Debug.php#L73

Added line #L73 was not covered by tests

ob_end_clean();

Check warning on line 75 in lib/Doctrine/ORM/Tools/Debug.php

View check run for this annotation

Codecov / codecov/patch

lib/Doctrine/ORM/Tools/Debug.php#L75

Added line #L75 was not covered by tests

$dumpText = strip_tags(html_entity_decode($dump));

Check warning on line 77 in lib/Doctrine/ORM/Tools/Debug.php

View check run for this annotation

Codecov / codecov/patch

lib/Doctrine/ORM/Tools/Debug.php#L77

Added line #L77 was not covered by tests

ini_set('html_errors', $html);

Check warning on line 79 in lib/Doctrine/ORM/Tools/Debug.php

View check run for this annotation

Codecov / codecov/patch

lib/Doctrine/ORM/Tools/Debug.php#L79

Added line #L79 was not covered by tests

return $dumpText;

Check warning on line 81 in lib/Doctrine/ORM/Tools/Debug.php

View check run for this annotation

Codecov / codecov/patch

lib/Doctrine/ORM/Tools/Debug.php#L81

Added line #L81 was not covered by tests
}

/**
* @param mixed $var
*
* @return mixed
*/
public static function export($var, int $maxDepth)
{
$return = null;
$isObj = is_object($var);

if ($var instanceof Collection) {
$var = $var->toArray();

Check warning on line 95 in lib/Doctrine/ORM/Tools/Debug.php

View check run for this annotation

Codecov / codecov/patch

lib/Doctrine/ORM/Tools/Debug.php#L95

Added line #L95 was not covered by tests
}

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();

Check warning on line 130 in lib/Doctrine/ORM/Tools/Debug.php

View check run for this annotation

Codecov / codecov/patch

lib/Doctrine/ORM/Tools/Debug.php#L129-L130

Added lines #L129 - L130 were not covered by tests
}

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;
}
}
5 changes: 5 additions & 0 deletions phpcs.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@
<exclude-pattern>tests/*</exclude-pattern>
</rule>

<rule ref="Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps">
<exclude-pattern>lib/Doctrine/ORM/Tools/Debug.php</exclude-pattern>
<exclude-pattern>tests/Doctrine/Tests/ORM/Tools/DebugTest.php</exclude-pattern>
</rule>

<rule ref="Generic.NamingConventions.UpperCaseConstantName.ClassConstantNotUpperCase">
<exclude-pattern>lib/Doctrine/ORM/Events.php</exclude-pattern>
<exclude-pattern>lib/Doctrine/ORM/Tools/ToolEvents.php</exclude-pattern>
Expand Down
7 changes: 1 addition & 6 deletions psalm-baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -536,8 +536,8 @@
<ArgumentTypeCoercion>
<code>$class</code>
<code>$class</code>
<code><![CDATA[new $definition['class']()]]></code>
<code>$platformFamily</code>
<code><![CDATA[new $definition['class']()]]></code>
</ArgumentTypeCoercion>
<DeprecatedClass>
<code>new UuidGenerator()</code>
Expand Down Expand Up @@ -2406,11 +2406,6 @@
<code>getAllClassNames</code>
</PossiblyNullReference>
</file>
<file src="lib/Doctrine/ORM/Tools/Console/Command/RunDqlCommand.php">
<DeprecatedClass>
<code><![CDATA[Debug::dump($resultSet, (int) $input->getOption('depth'), true, false)]]></code>
</DeprecatedClass>
</file>
<file src="lib/Doctrine/ORM/Tools/Console/Command/SchemaTool/AbstractCommand.php">
<InvalidNullableReturnType>
<code>int</code>
Expand Down
5 changes: 5 additions & 0 deletions psalm.xml
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,11 @@
<file name="lib/Doctrine/ORM/Tools/Console/Helper/EntityManagerHelper.php"/>
</errorLevel>
</DuplicateClass>
<ForbiddenCode>
<errorLevel type="suppress">
<file name="lib/Doctrine/ORM/Tools/Debug.php"/>
</errorLevel>
</ForbiddenCode>
<InvalidArgument>
<errorLevel type="suppress">
<!-- Argument type changes in DBAL 3.2 -->
Expand Down
146 changes: 146 additions & 0 deletions tests/Doctrine/Tests/ORM/Tools/DebugTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
<?php

declare(strict_types=1);

namespace Doctrine\Tests\ORM\Tools;

use ArrayIterator;
use ArrayObject;
use DateTime;
use DateTimeImmutable;
use DateTimeZone;
use Doctrine\ORM\Tools\Debug;
use Doctrine\Tests\DoctrineTestCase;
use stdClass;

use function print_r;
use function strpos;
use function substr;

class DebugTest extends DoctrineTestCase
{
public function testExportObject(): void
{
$obj = new stdClass();
$obj->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<string, int> $expected
*
* @dataProvider provideAttributesCases
*
* @requires PHP < 8.1.0
*/
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($actualRepresentation, $expectedRepresentation);

$var = Debug::export($class, 3);
$var = (array) $var;
unset($var['__CLASS__']);

self::assertSame($expected, $var);
}

/**
* @psalm-return array<string, array{TestAsset\ParentClass, mixed[]}>
*/
public function provideAttributesCases(): iterable
{
return [
'different-attributes' => [
new TestAsset\ChildClass(),
[
'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,
],
],
'same-attributes' => [
new TestAsset\ChildWithSameAttributesClass(),
[
'parentPublicAttribute' => 4,
'parentProtectedAttribute:protected' => 5,
'parentPrivateAttribute:Doctrine\Tests\ORM\Tools\TestAsset\ChildWithSameAttributesClass:private' => 6,
'parentPrivateAttribute:Doctrine\Tests\ORM\Tools\TestAsset\ParentClass:private' => 3,
],
],
];
}
}
15 changes: 15 additions & 0 deletions tests/Doctrine/Tests/ORM/Tools/TestAsset/ChildClass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace Doctrine\Tests\ORM\Tools\TestAsset;

final class ChildClass extends ParentClass
{
/** @var int */
public $childPublicAttribute = 4;
/** @var int */
protected $childProtectedAttribute = 5;
/** @var int */
private $childPrivateAttribute = 6;
}
Loading

0 comments on commit d15fbed

Please sign in to comment.