diff --git a/src/Symfony/Component/VarDumper/CHANGELOG.md b/src/Symfony/Component/VarDumper/CHANGELOG.md index 9329875010537..7481cff1a4cde 100644 --- a/src/Symfony/Component/VarDumper/CHANGELOG.md +++ b/src/Symfony/Component/VarDumper/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +6.4 +--- + + * Dump uninitialized properties + 6.3 --- diff --git a/src/Symfony/Component/VarDumper/Caster/Caster.php b/src/Symfony/Component/VarDumper/Caster/Caster.php index 32c69ee04dc43..e79ee735fb54b 100644 --- a/src/Symfony/Component/VarDumper/Caster/Caster.php +++ b/src/Symfony/Component/VarDumper/Caster/Caster.php @@ -32,6 +32,7 @@ class Caster public const EXCLUDE_EMPTY = 128; public const EXCLUDE_NOT_IMPORTANT = 256; public const EXCLUDE_STRICT = 512; + public const EXCLUDE_UNINITIALIZED = 1024; public const PREFIX_VIRTUAL = "\0~\0"; public const PREFIX_DYNAMIC = "\0+\0"; @@ -39,6 +40,8 @@ class Caster // usage: sprintf(Caster::PATTERN_PRIVATE, $class, $property) public const PATTERN_PRIVATE = "\0%s\0%s"; + private static array $classProperties = []; + /** * Casts objects to arrays and adds the dynamic property prefix. * @@ -61,20 +64,17 @@ public static function castObject(object $obj, string $class, bool $hasDebugInfo return $a; } + $classProperties = self::$classProperties[$class] ??= self::getClassProperties(new \ReflectionClass($class)); + $a = array_replace($classProperties, $a); + if ($a) { - static $publicProperties = []; $debugClass ??= get_debug_type($obj); $i = 0; $prefixedKeys = []; foreach ($a as $k => $v) { if ("\0" !== ($k[0] ?? '')) { - if (!isset($publicProperties[$class])) { - foreach ((new \ReflectionClass($class))->getProperties(\ReflectionProperty::IS_PUBLIC) as $prop) { - $publicProperties[$class][$prop->name] = true; - } - } - if (!isset($publicProperties[$class][$k])) { + if (!isset($classProperties[$k])) { $prefixedKeys[$i] = self::PREFIX_DYNAMIC.$k; } } elseif ($debugClass !== $class && 1 === strpos($k, $class)) { @@ -131,6 +131,8 @@ public static function filter(array $a, int $filter, array $listedProperties = [ $type |= self::EXCLUDE_EMPTY & $filter; } elseif (false === $v || '' === $v || '0' === $v || 0 === $v || 0.0 === $v || [] === $v) { $type |= self::EXCLUDE_EMPTY & $filter; + } elseif ($v instanceof UninitializedStub) { + $type |= self::EXCLUDE_UNINITIALIZED & $filter; } if ((self::EXCLUDE_NOT_IMPORTANT & $filter) && !\in_array($k, $listedProperties, true)) { $type |= self::EXCLUDE_NOT_IMPORTANT; @@ -169,4 +171,28 @@ public static function castPhpIncompleteClass(\__PHP_Incomplete_Class $c, array return $a; } + + private static function getClassProperties(\ReflectionClass $class): array + { + $classProperties = []; + $className = $class->name; + + foreach ($class->getProperties() as $p) { + if ($p->isStatic()) { + continue; + } + + $classProperties[match (true) { + $p->isPublic() => $p->name, + $p->isProtected() => self::PREFIX_PROTECTED.$p->name, + default => "\0".$className."\0".$p->name, + }] = new UninitializedStub($p); + } + + if ($parent = $class->getParentClass()) { + $classProperties += self::$classProperties[$parent->name] ??= self::getClassProperties($parent); + } + + return $classProperties; + } } diff --git a/src/Symfony/Component/VarDumper/Caster/UninitializedStub.php b/src/Symfony/Component/VarDumper/Caster/UninitializedStub.php new file mode 100644 index 0000000000000..a9bdd9b8130bf --- /dev/null +++ b/src/Symfony/Component/VarDumper/Caster/UninitializedStub.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +/** + * Represents an uninitialized property. + * + * @author Nicolas Grekas
+ */
+class UninitializedStub extends ConstStub
+{
+ public function __construct(\ReflectionProperty $property)
+ {
+ parent::__construct('?'.($property->hasType() ? ' '.$property->getType() : ''), 'Uninitialized property');
+ }
+}
diff --git a/src/Symfony/Component/VarDumper/Caster/XmlReaderCaster.php b/src/Symfony/Component/VarDumper/Caster/XmlReaderCaster.php
index d802bbf2a107f..1cfcf4dd8610e 100644
--- a/src/Symfony/Component/VarDumper/Caster/XmlReaderCaster.php
+++ b/src/Symfony/Component/VarDumper/Caster/XmlReaderCaster.php
@@ -85,6 +85,7 @@ public static function castXmlReader(\XMLReader $reader, array $a, Stub $stub, b
$info[$props]->cut = $count;
}
+ $a = Caster::filter($a, Caster::EXCLUDE_UNINITIALIZED, [], $count);
$info = Caster::filter($info, Caster::EXCLUDE_EMPTY, [], $count);
// +2 because hasValue and hasAttributes are always filtered
$stub->cut += $count + 2;
diff --git a/src/Symfony/Component/VarDumper/Tests/Caster/ExceptionCasterTest.php b/src/Symfony/Component/VarDumper/Tests/Caster/ExceptionCasterTest.php
index 66708944c63d6..f8fe43d8ddcee 100644
--- a/src/Symfony/Component/VarDumper/Tests/Caster/ExceptionCasterTest.php
+++ b/src/Symfony/Component/VarDumper/Tests/Caster/ExceptionCasterTest.php
@@ -378,6 +378,7 @@ public function testFlattenException()
-file: "%sExceptionCasterTest.php"
-line: %d
-asString: null
+ -dataRepresentation: ? Symfony\Component\VarDumper\Cloner\Data
}
]
EODUMP;
diff --git a/src/Symfony/Component/VarDumper/Tests/Caster/MysqliCasterTest.php b/src/Symfony/Component/VarDumper/Tests/Caster/MysqliCasterTest.php
index 983f541a3f786..4eba406efd325 100644
--- a/src/Symfony/Component/VarDumper/Tests/Caster/MysqliCasterTest.php
+++ b/src/Symfony/Component/VarDumper/Tests/Caster/MysqliCasterTest.php
@@ -30,7 +30,6 @@ public function testNotConnected()
$xCast = <<