Skip to content

Commit dc04b8c

Browse files
bug #44295 [Serializer] fix support for lazy/unset properties (nicolas-grekas)
This PR was merged into the 4.4 branch. Discussion ---------- [Serializer] fix support for lazy/unset properties | Q | A | ------------- | --- | Branch? | 4.4 | Bug fix? | yes | New feature? | no | Deprecations? | no | Tickets | Fix #44273 #44283 | License | MIT | Doc PR | - This basically backports #43469 into 4.4, which is the way to go to fix #44273. The code that exists to handle uninitialized properties is broken anyway (it was before the recent changes.) Commits ------- db043aa [Serializer] fix support for lazy/unset properties
2 parents 0796087 + db043aa commit dc04b8c

File tree

5 files changed

+55
-19
lines changed

5 files changed

+55
-19
lines changed

src/Symfony/Component/PropertyAccess/PropertyAccessor.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,7 @@ private function readProperty(array $zval, string $property, bool $ignoreInvalid
385385

386386
$result = self::RESULT_PROTO;
387387
$object = $zval[self::VALUE];
388+
$class = \get_class($object);
388389
$access = $this->getReadAccessInfo(\get_class($object), $property);
389390

390391
try {
@@ -406,6 +407,11 @@ private function readProperty(array $zval, string $property, bool $ignoreInvalid
406407
throw $e;
407408
}
408409
} elseif (self::ACCESS_TYPE_PROPERTY === $access[self::ACCESS_TYPE]) {
410+
$name = $access[self::ACCESS_NAME];
411+
if (!method_exists($object, '__get') && !isset($object->$name) && !\array_key_exists($name, (array) $object) && (\PHP_VERSION_ID < 70400 || !(new \ReflectionProperty($class, $name))->hasType())) {
412+
throw new AccessException(sprintf('The property "%s::$%s" is not initialized.', $class, $name));
413+
}
414+
409415
$result[self::VALUE] = $object->{$access[self::ACCESS_NAME]};
410416

411417
if ($access[self::ACCESS_REF] && isset($zval[self::REF])) {

src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\Component\Serializer\Normalizer;
1313

14+
use Symfony\Component\PropertyAccess\Exception\AccessException;
1415
use Symfony\Component\PropertyAccess\Exception\InvalidArgumentException;
1516
use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException;
1617
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
@@ -181,7 +182,23 @@ public function normalize($object, $format = null, array $context = [])
181182
continue;
182183
}
183184

184-
$attributeValue = $this->getAttributeValue($object, $attribute, $format, $context);
185+
try {
186+
$attributeValue = $this->getAttributeValue($object, $attribute, $format, $context);
187+
} catch (AccessException $e) {
188+
if (sprintf('The property "%s::$%s" is not initialized.', \get_class($object), $attribute) === $e->getMessage()) {
189+
continue;
190+
}
191+
if (($p = $e->getPrevious()) && 'Error' === \get_class($p) && $this->isUninitializedValueError($p)) {
192+
continue;
193+
}
194+
throw $e;
195+
} catch (\Error $e) {
196+
if ($this->isUninitializedValueError($e)) {
197+
continue;
198+
}
199+
throw $e;
200+
}
201+
185202
if ($maxDepthReached) {
186203
$attributeValue = $maxDepthHandler($attributeValue, $object, $attribute, $format, $context);
187204
}
@@ -637,4 +654,15 @@ private function getCacheKey(?string $format, array $context)
637654
return false;
638655
}
639656
}
657+
658+
/**
659+
* This error may occur when specific object normalizer implementation gets attribute value
660+
* by accessing a public uninitialized property or by calling a method accessing such property.
661+
*/
662+
private function isUninitializedValueError(\Error $e): bool
663+
{
664+
return \PHP_VERSION_ID >= 70400
665+
&& str_starts_with($e->getMessage(), 'Typed property')
666+
&& str_ends_with($e->getMessage(), 'must not be accessed before initialization');
667+
}
640668
}

src/Symfony/Component/Serializer/Normalizer/ObjectNormalizer.php

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -104,16 +104,8 @@ protected function extractAttributes($object, $format = null, array $context = [
104104
}
105105

106106
// properties
107-
$propertyValues = !method_exists($object, '__get') ? (array) $object : null;
108107
foreach ($reflClass->getProperties() as $reflProperty) {
109-
if (null !== $propertyValues && !\array_key_exists($reflProperty->name, $propertyValues)) {
110-
if ($reflProperty->isPublic()
111-
|| ($reflProperty->isProtected() && !\array_key_exists("\0*\0{$reflProperty->name}", $propertyValues))
112-
|| ($reflProperty->isPrivate() && !\array_key_exists("\0{$reflProperty->class}\0{$reflProperty->name}", $propertyValues))
113-
) {
114-
unset($attributes[$reflProperty->name]);
115-
}
116-
108+
if (!$reflProperty->isPublic()) {
117109
continue;
118110
}
119111

src/Symfony/Component/Serializer/Normalizer/PropertyNormalizer.php

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
namespace Symfony\Component\Serializer\Normalizer;
1313

14+
use Symfony\Component\PropertyAccess\Exception\AccessException;
15+
1416
/**
1517
* Converts between objects and arrays by mapping properties.
1618
*
@@ -101,17 +103,10 @@ protected function extractAttributes($object, $format = null, array $context = [
101103
{
102104
$reflectionObject = new \ReflectionObject($object);
103105
$attributes = [];
104-
$propertyValues = !method_exists($object, '__get') ? (array) $object : null;
105106

106107
do {
107108
foreach ($reflectionObject->getProperties() as $property) {
108-
if ((null !== $propertyValues && (
109-
($property->isPublic() && !\array_key_exists($property->name, $propertyValues))
110-
|| ($property->isProtected() && !\array_key_exists("\0*\0{$property->name}", $propertyValues))
111-
|| ($property->isPrivate() && !\array_key_exists("\0{$property->class}\0{$property->name}", $propertyValues))
112-
))
113-
|| !$this->isAllowedAttribute($reflectionObject->getName(), $property->name, $format, $context)
114-
) {
109+
if (!$this->isAllowedAttribute($reflectionObject->getName(), $property->name, $format, $context)) {
115110
continue;
116111
}
117112

@@ -138,6 +133,21 @@ protected function getAttributeValue($object, $attribute, $format = null, array
138133
$reflectionProperty->setAccessible(true);
139134
}
140135

136+
if (\PHP_VERSION_ID >= 70400 && $reflectionProperty->hasType()) {
137+
return $reflectionProperty->getValue($object);
138+
}
139+
140+
if (!method_exists($object, '__get') && !isset($object->$attribute)) {
141+
$propertyValues = (array) $object;
142+
143+
if (($reflectionProperty->isPublic() && !\array_key_exists($reflectionProperty->name, $propertyValues))
144+
|| ($reflectionProperty->isProtected() && !\array_key_exists("\0*\0{$reflectionProperty->name}", $propertyValues))
145+
|| ($reflectionProperty->isPrivate() && !\array_key_exists("\0{$reflectionProperty->class}\0{$reflectionProperty->name}", $propertyValues))
146+
) {
147+
throw new AccessException(sprintf('The property "%s::$%s" is not initialized.', \get_class($object), $reflectionProperty->name));
148+
}
149+
}
150+
141151
return $reflectionProperty->getValue($object);
142152
}
143153

src/Symfony/Component/Serializer/composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
"symfony/error-handler": "^4.4|^5.0",
3030
"symfony/http-foundation": "^3.4|^4.0|^5.0",
3131
"symfony/mime": "^4.4|^5.0",
32-
"symfony/property-access": "^3.4.41|^4.4.9|^5.0.9",
32+
"symfony/property-access": "^4.4.36|^5.3.13",
3333
"symfony/property-info": "^3.4.13|~4.0|^5.0",
3434
"symfony/validator": "^3.4|^4.0|^5.0",
3535
"symfony/yaml": "^3.4|^4.0|^5.0"

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy