Skip to content

Commit ce1c53e

Browse files
committed
[Serializer] fix Inherited properties normalization
1 parent dd2aaad commit ce1c53e

File tree

2 files changed

+54
-1
lines changed

2 files changed

+54
-1
lines changed

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

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ protected function extractAttributes(object $object, ?string $format = null, arr
107107
'i' => str_starts_with($name, 'is') && isset($name[$i = 2]),
108108
default => false,
109109
} && !ctype_lower($name[$i])) {
110-
if ($reflClass->hasProperty($name)) {
110+
if ($reflClass->hasProperty($name) || $this->hasParentProperty($reflClass, $name)) {
111111
$attributeName = $name;
112112
} else {
113113
$attributeName = substr($name, $i);
@@ -139,6 +139,18 @@ protected function extractAttributes(object $object, ?string $format = null, arr
139139
return array_keys($attributes);
140140
}
141141

142+
private function hasParentProperty(\ReflectionClass $class, string $propName): bool
143+
{
144+
while ($class) {
145+
if ($class->hasProperty($propName)) {
146+
return true;
147+
}
148+
$class = $class->getParentClass();
149+
}
150+
151+
return false;
152+
}
153+
142154
protected function getAttributeValue(object $object, string $attribute, ?string $format = null, array $context = []): mixed
143155
{
144156
$mapping = $this->classDiscriminatorResolver?->getMappingForMappedObject($object);

src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1026,6 +1026,43 @@ public function testNormalizeObjectWithPropertyAndAccessorMethodsWithSameName()
10261026
$this->assertSame('isFoo', $denormalized->isFoo());
10271027
}
10281028

1029+
public function testNormalizeChildObjectWithPropertyAndAccessorMethodsWithSameName()
1030+
{
1031+
// This test follows the same logic used in testNormalizeObjectWithPropertyAndAccessorMethodsWithSameName()
1032+
$normalizer = $this->getNormalizerForAccessors();
1033+
1034+
$object = new ChildObjectWithPropertyAndAccessorSameName(
1035+
'foo',
1036+
'getFoo',
1037+
'canFoo',
1038+
'hasFoo',
1039+
'isFoo'
1040+
);
1041+
$normalized = $normalizer->normalize($object);
1042+
1043+
$this->assertSame([
1044+
'getFoo' => 'getFoo',
1045+
'canFoo' => 'canFoo',
1046+
'hasFoo' => 'hasFoo',
1047+
'isFoo' => 'isFoo',
1048+
// The getFoo accessor method is used for foo, thus it's also 'getFoo' instead of 'foo'
1049+
'foo' => 'getFoo',
1050+
], $normalized);
1051+
1052+
$denormalized = $this->normalizer->denormalize($normalized, ChildObjectWithPropertyAndAccessorSameName::class);
1053+
1054+
$this->assertSame('getFoo', $denormalized->getFoo());
1055+
1056+
// On the initial object the value was 'foo', but the normalizer prefers the accessor method 'getFoo'
1057+
// Thus on the denoramilzed object the value is 'getFoo'
1058+
$this->assertSame('foo', $object->foo);
1059+
$this->assertSame('getFoo', $denormalized->foo);
1060+
1061+
$this->assertSame('hasFoo', $denormalized->hasFoo());
1062+
$this->assertSame('canFoo', $denormalized->canFoo());
1063+
$this->assertSame('isFoo', $denormalized->isFoo());
1064+
}
1065+
10291066
/**
10301067
* Priority of accessor methods is defined by the PropertyReadInfoExtractorInterface passed to the PropertyAccessor
10311068
* component. By default ReflectionExtractor::$defaultAccessorPrefixes are used.
@@ -1501,6 +1538,10 @@ public function isFoo()
15011538
}
15021539
}
15031540

1541+
class ChildObjectWithPropertyAndAccessorSameName extends ObjectWithPropertyAndAccessorSameName
1542+
{
1543+
}
1544+
15041545
class ObjectWithPropertyHasserAndIsser
15051546
{
15061547
public function __construct(

0 commit comments

Comments
 (0)