From 46ca03e59d52ec8f1d26340ded8a5f6b92448eb1 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 5 Jul 2022 10:32:33 +0200 Subject: [PATCH] [VarExporter] Fix accessing readonly properties by reference --- .../Component/VarExporter/Hydrator.php | 8 ++--- .../VarExporter/Internal/Hydrator.php | 36 ++++++++++++++----- 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/src/Symfony/Component/VarExporter/Hydrator.php b/src/Symfony/Component/VarExporter/Hydrator.php index 0827481778b1e..b568e43502844 100644 --- a/src/Symfony/Component/VarExporter/Hydrator.php +++ b/src/Symfony/Component/VarExporter/Hydrator.php @@ -61,15 +61,15 @@ public static function hydrate(object $instance, array $properties = [], array $ $propertyScopes = InternalHydrator::$propertyScopes[$class] ??= InternalHydrator::getPropertyScopes($class); foreach ($properties as $name => &$value) { - [$scope, $name] = $propertyScopes[$name] ?? [$class, $name]; - $scopedProperties[$scope][$name] = &$value; + [$scope, $name, $readonlyScope] = $propertyScopes[$name] ?? [$class, $name, $class]; + $scopedProperties[$readonlyScope ?? $scope][$name] = &$value; } unset($value); } - foreach ($scopedProperties as $class => $properties) { + foreach ($scopedProperties as $scope => $properties) { if ($properties) { - (InternalHydrator::$simpleHydrators[$class] ??= InternalHydrator::getSimpleHydrator($class))($properties, $instance); + (InternalHydrator::$simpleHydrators[$scope] ??= InternalHydrator::getSimpleHydrator($scope))($properties, $instance); } } diff --git a/src/Symfony/Component/VarExporter/Internal/Hydrator.php b/src/Symfony/Component/VarExporter/Internal/Hydrator.php index 221d04f234664..a1eac3d9e15a8 100644 --- a/src/Symfony/Component/VarExporter/Internal/Hydrator.php +++ b/src/Symfony/Component/VarExporter/Internal/Hydrator.php @@ -155,28 +155,35 @@ public static function getHydrator($class) public static function getSimpleHydrator($class) { - $baseHydrator = self::$simpleHydrators['stdClass'] ??= static function ($properties, $object) { + $baseHydrator = self::$simpleHydrators['stdClass'] ??= (function ($properties, $object) { + $readonly = (array) $this; + foreach ($properties as $name => &$value) { - $object->$name = &$value; + $object->$name = $value; + + if (!($readonly[$name] ?? false)) { + $object->$name = &$value; + } } - }; + })->bindTo(new \stdClass()); switch ($class) { case 'stdClass': return $baseHydrator; case 'ErrorException': - return $baseHydrator->bindTo(null, new class() extends \ErrorException { + return $baseHydrator->bindTo(new \stdClass(), new class() extends \ErrorException { }); case 'TypeError': - return $baseHydrator->bindTo(null, new class() extends \Error { + return $baseHydrator->bindTo(new \stdClass(), new class() extends \Error { }); case 'SplObjectStorage': return static function ($properties, $object) { foreach ($properties as $name => &$value) { if ("\0" !== $name) { + $object->$name = $value; $object->$name = &$value; continue; } @@ -202,6 +209,7 @@ public static function getSimpleHydrator($class) if ("\0" === $name) { $constructor($object, $value); } else { + $object->$name = $value; $object->$name = &$value; } } @@ -209,7 +217,14 @@ public static function getSimpleHydrator($class) } if (!$classReflector->isInternal()) { - return $baseHydrator->bindTo(null, $class); + $readonly = new \stdClass(); + foreach ($classReflector->getProperties(\ReflectionProperty::IS_READONLY) as $propertyReflector) { + if ($class === $propertyReflector->class) { + $readonly->{$propertyReflector->name} = true; + } + } + + return $baseHydrator->bindTo($readonly, $class); } if ($classReflector->name !== $class) { @@ -232,6 +247,7 @@ public static function getSimpleHydrator($class) if ($setValue = $propertySetters[$name] ?? null) { $setValue($object, $value); } else { + $object->$name = $value; $object->$name = &$value; } } @@ -252,10 +268,10 @@ public static function getPropertyScopes($class) $name = $property->name; if (\ReflectionProperty::IS_PRIVATE & $flags) { - $propertyScopes["\0$class\0$name"] = $propertyScopes[$name] = [$class, $name]; + $propertyScopes["\0$class\0$name"] = $propertyScopes[$name] = [$class, $name, $flags & \ReflectionProperty::IS_READONLY ? $class : null]; continue; } - $propertyScopes[$name] = [$flags & \ReflectionProperty::IS_READONLY ? $property->class : $class, $name]; + $propertyScopes[$name] = [$class, $name, $flags & \ReflectionProperty::IS_READONLY ? $property->class : null]; if (\ReflectionProperty::IS_PROTECTED & $flags) { $propertyScopes["\0*\0$name"] = $propertyScopes[$name]; @@ -268,7 +284,9 @@ public static function getPropertyScopes($class) foreach ($r->getProperties(\ReflectionProperty::IS_PRIVATE) as $property) { if (!$property->isStatic()) { $name = $property->name; - $propertyScopes["\0$class\0$name"] = [$class, $name]; + $readonlyScope = $property->isReadOnly() ? $class : null; + $propertyScopes["\0$class\0$name"] = [$class, $name, $readonlyScope]; + $propertyScopes[$name] ??= [$class, $name, $readonlyScope]; } } }