Skip to content

Commit c88f7fb

Browse files
Link1515nicolas-grekas
authored andcommitted
[Serializer] fix Inherited properties normalization
1 parent a7d76a1 commit c88f7fb

File tree

2 files changed

+77
-2
lines changed

2 files changed

+77
-2
lines changed

Normalizer/ObjectNormalizer.php

Lines changed: 14 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 ($this->hasProperty($reflMethod, $name)) {
111111
$attributeName = $name;
112112
} else {
113113
$attributeName = substr($name, $i);
@@ -139,6 +139,19 @@ protected function extractAttributes(object $object, ?string $format = null, arr
139139
return array_keys($attributes);
140140
}
141141

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

Tests/Normalizer/ObjectNormalizerTest.php

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1017,7 +1017,7 @@ public function testNormalizeObjectWithPropertyAndAccessorMethodsWithSameName()
10171017
$this->assertSame('getFoo', $denormalized->getFoo());
10181018

10191019
// On the initial object the value was 'foo', but the normalizer prefers the accessor method 'getFoo'
1020-
// Thus on the denoramilzed object the value is 'getFoo'
1020+
// Thus on the denormalized object the value is 'getFoo'
10211021
$this->assertSame('foo', $object->foo);
10221022
$this->assertSame('getFoo', $denormalized->foo);
10231023

@@ -1026,6 +1026,56 @@ public function testNormalizeObjectWithPropertyAndAccessorMethodsWithSameName()
10261026
$this->assertSame('isFoo', $denormalized->isFoo());
10271027
}
10281028

1029+
public function testNormalizeChildExtendsObjectWithPropertyAndAccessorSameName()
1030+
{
1031+
// This test follows the same logic used in testNormalizeObjectWithPropertyAndAccessorMethodsWithSameName()
1032+
$normalizer = $this->getNormalizerForAccessors();
1033+
1034+
$object = new ChildExtendsObjectWithPropertyAndAccessorSameName(
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, ChildExtendsObjectWithPropertyAndAccessorSameName::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 denormalized 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+
1066+
public function testNormalizeChildWithPropertySameAsParentMethod()
1067+
{
1068+
$normalizer = $this->getNormalizerForAccessors();
1069+
1070+
$object = new ChildWithPropertySameAsParentMethod('foo');
1071+
$normalized = $normalizer->normalize($object);
1072+
1073+
$this->assertSame([
1074+
'foo' => 'foo',
1075+
],
1076+
$normalized);
1077+
}
1078+
10291079
/**
10301080
* Priority of accessor methods is defined by the PropertyReadInfoExtractorInterface passed to the PropertyAccessor
10311081
* component. By default ReflectionExtractor::$defaultAccessorPrefixes are used.
@@ -1501,6 +1551,18 @@ public function isFoo()
15011551
}
15021552
}
15031553

1554+
class ChildExtendsObjectWithPropertyAndAccessorSameName extends ObjectWithPropertyAndAccessorSameName
1555+
{
1556+
}
1557+
1558+
class ChildWithPropertySameAsParentMethod extends ObjectWithPropertyAndAllAccessorMethods
1559+
{
1560+
private $canFoo;
1561+
private $getFoo;
1562+
private $hasFoo;
1563+
private $isFoo;
1564+
}
1565+
15041566
class ObjectWithPropertyHasserAndIsser
15051567
{
15061568
public function __construct(

0 commit comments

Comments
 (0)