Skip to content

Commit 8ccc0e4

Browse files
er1znicolas-grekas
authored andcommitted
[Serializer] fixed object normalizer for a class with cancel method
1 parent 0b6d0e1 commit 8ccc0e4

File tree

7 files changed

+194
-6
lines changed

7 files changed

+194
-6
lines changed

src/Symfony/Component/Serializer/Mapping/Loader/AttributeLoader.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool
129129
}
130130

131131
$accessorOrMutator = preg_match('/^(get|is|has|set)(.+)$/i', $method->name, $matches);
132-
if ($accessorOrMutator) {
132+
if ($accessorOrMutator && !ctype_lower($matches[2][0])) {
133133
$attributeName = lcfirst($matches[2]);
134134

135135
if (isset($attributesMetadata[$attributeName])) {

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,8 @@ private function isGetMethod(\ReflectionMethod $method): bool
105105
return !$method->isStatic()
106106
&& !($method->getAttributes(Ignore::class) || $method->getAttributes(LegacyIgnore::class))
107107
&& !$method->getNumberOfRequiredParameters()
108-
&& ((2 < ($methodLength = \strlen($method->name)) && str_starts_with($method->name, 'is'))
109-
|| (3 < $methodLength && (str_starts_with($method->name, 'has') || str_starts_with($method->name, 'get')))
108+
&& ((2 < ($methodLength = \strlen($method->name)) && str_starts_with($method->name, 'is') && !ctype_lower($method->name[2]))
109+
|| (3 < $methodLength && (str_starts_with($method->name, 'has') || str_starts_with($method->name, 'get')) && !ctype_lower($method->name[3]))
110110
);
111111
}
112112

@@ -118,7 +118,9 @@ private function isSetMethod(\ReflectionMethod $method): bool
118118
return !$method->isStatic()
119119
&& !$method->getAttributes(Ignore::class)
120120
&& 0 < $method->getNumberOfParameters()
121-
&& str_starts_with($method->name, 'set');
121+
&& str_starts_with($method->name, 'set')
122+
&& !ctype_lower($method->name[3])
123+
;
122124
}
123125

124126
protected function extractAttributes(object $object, ?string $format = null, array $context = []): array

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,8 @@ protected function extractAttributes(object $object, ?string $format = null, arr
100100
$name = $reflMethod->name;
101101
$attributeName = null;
102102

103-
if (3 < \strlen($name) && match ($name[0]) {
103+
// ctype_lower check to find out if method looks like accessor but actually is not, e.g. hash, cancel
104+
if (3 < \strlen($name) && !ctype_lower($name[3]) && match ($name[0]) {
104105
'g' => str_starts_with($name, 'get'),
105106
'h' => str_starts_with($name, 'has'),
106107
'c' => str_starts_with($name, 'can'),
@@ -112,7 +113,7 @@ protected function extractAttributes(object $object, ?string $format = null, arr
112113
if (!$reflClass->hasProperty($attributeName)) {
113114
$attributeName = lcfirst($attributeName);
114115
}
115-
} elseif ('is' !== $name && str_starts_with($name, 'is')) {
116+
} elseif ('is' !== $name && str_starts_with($name, 'is') && !ctype_lower($name[2])) {
116117
// issers
117118
$attributeName = substr($name, 2);
118119

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Serializer\Tests\Fixtures\Attributes;
13+
14+
class AccessorishGetters
15+
{
16+
public function hash(): void
17+
{
18+
}
19+
20+
public function cancel()
21+
{
22+
}
23+
24+
public function getField1()
25+
{
26+
}
27+
28+
public function isField2()
29+
{
30+
}
31+
32+
public function hasField3()
33+
{
34+
}
35+
36+
public function setField4()
37+
{
38+
}
39+
}

src/Symfony/Component/Serializer/Tests/Mapping/Loader/AttributeLoaderTestCase.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader;
2222
use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader;
2323
use Symfony\Component\Serializer\Mapping\Loader\LoaderInterface;
24+
use Symfony\Component\Serializer\Tests\Fixtures\Attributes\AccessorishGetters;
2425
use Symfony\Component\Serializer\Tests\Mapping\Loader\Features\ContextMappingTestTrait;
2526
use Symfony\Component\Serializer\Tests\Mapping\TestClassMetadataFactory;
2627

@@ -212,6 +213,22 @@ public function testLoadGroupsOnClass()
212213
self::assertSame(['a'], $attributesMetadata['baz']->getGroups());
213214
}
214215

216+
public function testIgnoresAccessorishGetters()
217+
{
218+
$classMetadata = new ClassMetadata(AccessorishGetters::class);
219+
$this->loader->loadClassMetadata($classMetadata);
220+
221+
$attributesMetadata = $classMetadata->getAttributesMetadata();
222+
223+
self::assertCount(4, $classMetadata->getAttributesMetadata());
224+
225+
self::assertArrayHasKey('field1', $attributesMetadata);
226+
self::assertArrayHasKey('field2', $attributesMetadata);
227+
self::assertArrayHasKey('field3', $attributesMetadata);
228+
self::assertArrayHasKey('field4', $attributesMetadata);
229+
self::assertArrayNotHasKey('h', $attributesMetadata);
230+
}
231+
215232
/**
216233
* @group legacy
217234
*/

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

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,14 @@ public function testNormalizeWithDiscriminator()
515515
$this->assertSame(['type' => 'one', 'url' => 'URL_ONE'], $normalizer->normalize(new GetSetMethodDiscriminatedDummyOne()));
516516
}
517517

518+
public function testNormalizeWithMethodNamesSimilarToAccessors()
519+
{
520+
$classMetadataFactory = new ClassMetadataFactory(new AttributeLoader());
521+
$normalizer = new GetSetMethodNormalizer($classMetadataFactory);
522+
523+
$this->assertSame(['class' => 'class', 123 => 123], $normalizer->normalize(new GetSetWithAccessorishMethod()));
524+
}
525+
518526
public function testDenormalizeWithDiscriminator()
519527
{
520528
$classMetadataFactory = new ClassMetadataFactory(new AttributeLoader());
@@ -902,3 +910,46 @@ public function setBar($bar = null, $other = true)
902910
$this->bar = $bar;
903911
}
904912
}
913+
914+
class GetSetWithAccessorishMethod
915+
{
916+
public function cancel()
917+
{
918+
return 'cancel';
919+
}
920+
921+
public function hash()
922+
{
923+
return 'hash';
924+
}
925+
926+
public function getClass()
927+
{
928+
return 'class';
929+
}
930+
931+
public function setClass()
932+
{
933+
}
934+
935+
public function get123()
936+
{
937+
return 123;
938+
}
939+
940+
public function set123()
941+
{
942+
}
943+
944+
public function gettings()
945+
{
946+
}
947+
948+
public function settings()
949+
{
950+
}
951+
952+
public function isolate()
953+
{
954+
}
955+
}

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

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -937,6 +937,24 @@ public function testObjectNormalizerWithAttributeLoaderAndObjectHasStaticPropert
937937
$normalizer = new ObjectNormalizer(new ClassMetadataFactory(new AttributeLoader()));
938938
$this->assertSame([], $normalizer->normalize($class));
939939
}
940+
941+
public function testNormalizeWithMethodNamesSimilarToAccessors()
942+
{
943+
$classMetadataFactory = new ClassMetadataFactory(new AttributeLoader());
944+
$normalizer = new ObjectNormalizer($classMetadataFactory);
945+
946+
$object = new ObjectWithAccessorishMethods();
947+
$normalized = $normalizer->normalize($object);
948+
949+
$this->assertFalse($object->isAccessorishCalled());
950+
$this->assertSame([
951+
'accessorishCalled' => false,
952+
'tell' => true,
953+
'class' => true,
954+
'responsibility' => true,
955+
123 => 321
956+
], $normalized);
957+
}
940958
}
941959

942960
class ProxyObjectDummy extends ObjectDummy
@@ -1219,3 +1237,63 @@ class ObjectDummyWithIgnoreAttributeAndPrivateProperty
12191237

12201238
private $private = 'private';
12211239
}
1240+
1241+
class ObjectWithAccessorishMethods
1242+
{
1243+
private $accessorishCalled = false;
1244+
1245+
public function isAccessorishCalled()
1246+
{
1247+
return $this->accessorishCalled;
1248+
}
1249+
1250+
public function cancel()
1251+
{
1252+
$this->accessorishCalled = true;
1253+
}
1254+
1255+
public function hash()
1256+
{
1257+
$this->accessorishCalled = true;
1258+
}
1259+
1260+
public function canTell()
1261+
{
1262+
return true;
1263+
}
1264+
1265+
public function getClass()
1266+
{
1267+
return true;
1268+
}
1269+
1270+
public function hasResponsibility()
1271+
{
1272+
return true;
1273+
}
1274+
1275+
public function get_foo()
1276+
{
1277+
return 'bar';
1278+
}
1279+
1280+
public function get123()
1281+
{
1282+
return 321;
1283+
}
1284+
1285+
public function gettings()
1286+
{
1287+
$this->accessorishCalled = true;
1288+
}
1289+
1290+
public function settings()
1291+
{
1292+
$this->accessorishCalled = true;
1293+
}
1294+
1295+
public function isolate()
1296+
{
1297+
$this->accessorishCalled = true;
1298+
}
1299+
}

0 commit comments

Comments
 (0)