diff --git a/src/Tracy/Dumper/Exposer.php b/src/Tracy/Dumper/Exposer.php index d80d9b130..9bd9e0796 100644 --- a/src/Tracy/Dumper/Exposer.php +++ b/src/Tracy/Dumper/Exposer.php @@ -18,27 +18,64 @@ final class Exposer { public static function exposeObject(object $obj, Value $value, Describer $describer): void { - $defaults = get_class_vars(get_class($obj)); - $arr = (array) $obj; - $tmp = $arr; // bug #79477, PHP < 7.4.6 - foreach ($tmp as $k => $v) { - $refId = $describer->getReferenceId($tmp, $k); - $type = Value::PROP_PUBLIC; - if (isset($k[0]) && $k[0] === "\x00") { - $info = explode("\00", $k); - $k = end($info); - $type = $info[1] === '*' ? Value::PROP_PROTECTED : $info[1]; + $tmp = (array) $obj; + $values = $tmp; // bug #79477, PHP < 7.4.6 + $props = self::getProperties(get_class($obj)); + + foreach (array_diff_key($values, $props) as $k => $v) { + $describer->addPropertyTo( + $value, + (string) $k, + $v, + Value::PROP_DYNAMIC, + $describer->getReferenceId($values, $k) + ); + } + + foreach ($props as $k => [$name, $type]) { + if (array_key_exists($k, $values)) { + $describer->addPropertyTo( + $value, + $name, + $values[$k], + $type, + $describer->getReferenceId($values, $k) + ); } else { - $type = array_key_exists($k, $defaults) - ? Value::PROP_PUBLIC - : Value::PROP_DYNAMIC; - $k = (string) $k; + $value->items[] = [$name, new Value(Value::TYPE_TEXT, 'unset'), $type]; } - $describer->addPropertyTo($value, $k, $v, $type, $refId); } } + private static function getProperties($class): array + { + static $cache; + if (isset($cache[$class])) { + return $cache[$class]; + } + $rc = new \ReflectionClass($class); + $parentProps = $rc->getParentClass() ? self::getProperties($rc->getParentClass()->getName()) : []; + $props = []; + + foreach ($rc->getProperties() as $prop) { + $name = $prop->getName(); + if ($prop->isStatic()) { + // nothing + } elseif ($prop->isPrivate()) { + $props["\x00" . $class . "\x00" . $name] = [$name, $class]; + } elseif ($prop->isProtected()) { + $props["\x00*\x00" . $name] = [$name, Value::PROP_PROTECTED]; + } else { + $props[$name] = [$name, Value::PROP_PUBLIC]; + unset($parentProps["\x00*\x00" . $name]); + } + } + + return $cache[$class] = $props + $parentProps; + } + + public static function exposeClosure(\Closure $obj, Value $value, Describer $describer): void { $rc = new \ReflectionFunction($obj); diff --git a/tests/Tracy/Dumper.toHtml().phpt b/tests/Tracy/Dumper.toHtml().phpt index df6ac0573..f4846568f 100644 --- a/tests/Tracy/Dumper.toHtml().phpt +++ b/tests/Tracy/Dumper.toHtml().phpt @@ -103,20 +103,55 @@ $obj->{"a\xA0"} = 12; Assert::match(<<<'XX'
Child #%d%
-
x: 1 - y: 2 - z: 3 - x2: 4 - y2: 5 - z2: 6 - y: 'hello' - new: 7 +
new: 7 0: 8 1: 9 '': 10 'a\x00\n ': 11 'a\xA0': 12 + x: 1 + y: 2 + z: 3 + x2: 4 + y2: 5 + z2: 6 + y: 'hello'
XX , Dumper::toHtml($obj)); + + +if (PHP_VERSION_ID >= 70400) { + require __DIR__ . '/fixtures/DumpClass.74.php'; + + Assert::match(<<<'XX' +
Test74 #%d%
+
x: 1 + y: unset + z: unset +
+XX + , Dumper::toHtml(new Test74)); + + + $obj = new Child74; + $obj->new = 7; + unset($obj->unset1); + unset($obj->unset2); + + Assert::match(<<<'XX' +
Child74 #%d%
+
new: 7 + x: 2 + y: unset + z: unset + unset1: unset + unset2: unset + y: unset +
+XX + , Dumper::toHtml($obj)); +} diff --git a/tests/Tracy/Dumper.toTerminal().phpt b/tests/Tracy/Dumper.toTerminal().phpt index b6c04c23f..9734975b3 100644 --- a/tests/Tracy/Dumper.toTerminal().phpt +++ b/tests/Tracy/Dumper.toTerminal().phpt @@ -36,7 +36,11 @@ $obj->{1} = 9; $obj->{''} = 10; Assert::match(<<{''} = 10; Assert::match(<<<'XX' Child #%d% + new: 7 + 0: 8 + 1: 9 + '': 10 x: 1 y: 2 z: 3 @@ -110,9 +114,36 @@ Child #%d% y2: 5 z2: 6 y: 'hello' +XX +, Dumper::toText($obj)); + + +if (PHP_VERSION_ID >= 70400) { + require __DIR__ . '/fixtures/DumpClass.74.php'; + + Assert::match(<<<'XX' +Test74 #%d% + x: 1 + y: unset + z: unset +XX + , Dumper::toText(new Test74)); + + + $obj = new Child74; + $obj->new = 7; + unset($obj->unset1); + unset($obj->unset2); + + Assert::match(<<<'XX' +Child74 #%d% new: 7 - 0: 8 - 1: 9 - '': 10 + x: 2 + y: unset + z: unset + unset1: unset + unset2: unset + y: unset XX , Dumper::toText($obj)); +}