From 18e3906099903ea18f0e0e71f3e315234811a5fb Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Mon, 12 Feb 2024 11:10:17 +0100 Subject: [PATCH] [VarDumper] Fix serialization of stubs with null or uninitialized values --- Cloner/Internal/NoDefault.php | 25 +++++++++++++++++ Cloner/Stub.php | 15 +++++++--- Tests/Cloner/StubTest.php | 53 +++++++++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+), 4 deletions(-) create mode 100644 Cloner/Internal/NoDefault.php create mode 100644 Tests/Cloner/StubTest.php diff --git a/Cloner/Internal/NoDefault.php b/Cloner/Internal/NoDefault.php new file mode 100644 index 00000000..ed9db988 --- /dev/null +++ b/Cloner/Internal/NoDefault.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\Cloner\Internal; + +/** + * Flags a typed property that has no default value. + * + * This dummy object is used to distinguish a property with a default value of null + * from a property that is uninitialized by default. + * + * @internal + */ +enum NoDefault +{ + case NoDefault; +} diff --git a/Cloner/Stub.php b/Cloner/Stub.php index 0c2a4b9d..a377d2b9 100644 --- a/Cloner/Stub.php +++ b/Cloner/Stub.php @@ -11,6 +11,8 @@ namespace Symfony\Component\VarDumper\Cloner; +use Symfony\Component\VarDumper\Cloner\Internal\NoDefault; + /** * Represents the main properties of a PHP variable. * @@ -50,15 +52,20 @@ public function __sleep(): array $properties = []; if (!isset(self::$defaultProperties[$c = static::class])) { - self::$defaultProperties[$c] = get_class_vars($c); + $reflection = new \ReflectionClass($c); + self::$defaultProperties[$c] = []; + + foreach ($reflection->getProperties() as $p) { + if ($p->isStatic()) { + continue; + } - foreach ((new \ReflectionClass($c))->getStaticProperties() as $k => $v) { - unset(self::$defaultProperties[$c][$k]); + self::$defaultProperties[$c][$p->name] = $p->hasDefaultValue() ? $p->getDefaultValue() : ($p->hasType() ? NoDefault::NoDefault : null); } } foreach (self::$defaultProperties[$c] as $k => $v) { - if ($this->$k !== $v) { + if (NoDefault::NoDefault === $v || $this->$k !== $v) { $properties[] = $k; } } diff --git a/Tests/Cloner/StubTest.php b/Tests/Cloner/StubTest.php new file mode 100644 index 00000000..dd44aba3 --- /dev/null +++ b/Tests/Cloner/StubTest.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Tests\Cloner; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\VarDumper\Cloner\Stub; + +final class StubTest extends TestCase +{ + public function testUnserializeNullValue() + { + $stub = new Stub(); + $stub->value = null; + + $stub = unserialize(serialize($stub)); + + self::assertNull($stub->value); + } + + public function testUnserializeNullInTypedProperty() + { + $stub = new MyStub(); + $stub->myProp = null; + + $stub = unserialize(serialize($stub)); + + self::assertNull($stub->myProp); + } + + public function testUninitializedStubPropertiesAreLeftUninitialized() + { + $stub = new MyStub(); + + $stub = unserialize(serialize($stub)); + + $r = new \ReflectionProperty(MyStub::class, 'myProp'); + self::assertFalse($r->isInitialized($stub)); + } +} + +final class MyStub extends Stub +{ + public mixed $myProp; +}