From 096cddc11a8e823f9ce45fdbb59c073280b9fee2 Mon Sep 17 00:00:00 2001 From: Neil Peyssard Date: Mon, 4 Mar 2024 13:33:38 +0100 Subject: [PATCH] [Serializer] Fix object normalizer when properties has the same name as their accessor --- Mapping/Loader/AnnotationLoader.php | 2 +- Normalizer/ObjectNormalizer.php | 16 +++-- Tests/Fixtures/SamePropertyAsMethodDummy.php | 48 ++++++++++++++ ...yAsMethodWithMethodSerializedNameDummy.php | 62 ++++++++++++++++++ ...sMethodWithPropertySerializedNameDummy.php | 65 +++++++++++++++++++ Tests/Normalizer/ObjectNormalizerTest.php | 50 ++++++++++++++ 6 files changed, 238 insertions(+), 5 deletions(-) create mode 100644 Tests/Fixtures/SamePropertyAsMethodDummy.php create mode 100644 Tests/Fixtures/SamePropertyAsMethodWithMethodSerializedNameDummy.php create mode 100644 Tests/Fixtures/SamePropertyAsMethodWithPropertySerializedNameDummy.php diff --git a/Mapping/Loader/AnnotationLoader.php b/Mapping/Loader/AnnotationLoader.php index 4d0e1768..9082a3cc 100644 --- a/Mapping/Loader/AnnotationLoader.php +++ b/Mapping/Loader/AnnotationLoader.php @@ -106,7 +106,7 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata) $accessorOrMutator = preg_match('/^(get|is|has|set)(.+)$/i', $method->name, $matches); if ($accessorOrMutator) { - $attributeName = lcfirst($matches[2]); + $attributeName = $reflectionClass->hasProperty($method->name) ? $method->name : lcfirst($matches[2]); if (isset($attributesMetadata[$attributeName])) { $attributeMetadata = $attributesMetadata[$attributeName]; diff --git a/Normalizer/ObjectNormalizer.php b/Normalizer/ObjectNormalizer.php index cbbade54..0dafaa7b 100644 --- a/Normalizer/ObjectNormalizer.php +++ b/Normalizer/ObjectNormalizer.php @@ -86,17 +86,25 @@ protected function extractAttributes(object $object, ?string $format = null, arr if (str_starts_with($name, 'get') || str_starts_with($name, 'has')) { // getters and hassers - $attributeName = substr($name, 3); + $attributeName = $name; if (!$reflClass->hasProperty($attributeName)) { - $attributeName = lcfirst($attributeName); + $attributeName = substr($attributeName, 3); + + if (!$reflClass->hasProperty($attributeName)) { + $attributeName = lcfirst($attributeName); + } } } elseif (str_starts_with($name, 'is')) { // issers - $attributeName = substr($name, 2); + $attributeName = $name; if (!$reflClass->hasProperty($attributeName)) { - $attributeName = lcfirst($attributeName); + $attributeName = substr($attributeName, 2); + + if (!$reflClass->hasProperty($attributeName)) { + $attributeName = lcfirst($attributeName); + } } } diff --git a/Tests/Fixtures/SamePropertyAsMethodDummy.php b/Tests/Fixtures/SamePropertyAsMethodDummy.php new file mode 100644 index 00000000..89c8fcb9 --- /dev/null +++ b/Tests/Fixtures/SamePropertyAsMethodDummy.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Fixtures; + +class SamePropertyAsMethodDummy +{ + private $freeTrial; + private $hasSubscribe; + private $getReady; + private $isActive; + + public function __construct($freeTrial, $hasSubscribe, $getReady, $isActive) + { + $this->freeTrial = $freeTrial; + $this->hasSubscribe = $hasSubscribe; + $this->getReady = $getReady; + $this->isActive = $isActive; + } + + public function getFreeTrial() + { + return $this->freeTrial; + } + + public function hasSubscribe() + { + return $this->hasSubscribe; + } + + public function getReady() + { + return $this->getReady; + } + + public function isActive() + { + return $this->isActive; + } +} diff --git a/Tests/Fixtures/SamePropertyAsMethodWithMethodSerializedNameDummy.php b/Tests/Fixtures/SamePropertyAsMethodWithMethodSerializedNameDummy.php new file mode 100644 index 00000000..b4cf205f --- /dev/null +++ b/Tests/Fixtures/SamePropertyAsMethodWithMethodSerializedNameDummy.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Fixtures; + +use Symfony\Component\Serializer\Annotation\SerializedName; + +class SamePropertyAsMethodWithMethodSerializedNameDummy +{ + private $freeTrial; + private $hasSubscribe; + private $getReady; + private $isActive; + + public function __construct($freeTrial, $hasSubscribe, $getReady, $isActive) + { + $this->freeTrial = $freeTrial; + $this->hasSubscribe = $hasSubscribe; + $this->getReady = $getReady; + $this->isActive = $isActive; + } + + /** + * @SerializedName("free_trial_method") + */ + public function getFreeTrial() + { + return $this->freeTrial; + } + + /** + * @SerializedName("has_subscribe_method") + */ + public function hasSubscribe() + { + return $this->hasSubscribe; + } + + /** + * @SerializedName("get_ready_method") + */ + public function getReady() + { + return $this->getReady; + } + + /** + * @SerializedName("is_active_method") + */ + public function isActive() + { + return $this->isActive; + } +} diff --git a/Tests/Fixtures/SamePropertyAsMethodWithPropertySerializedNameDummy.php b/Tests/Fixtures/SamePropertyAsMethodWithPropertySerializedNameDummy.php new file mode 100644 index 00000000..04dc64a3 --- /dev/null +++ b/Tests/Fixtures/SamePropertyAsMethodWithPropertySerializedNameDummy.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Fixtures; + +use Symfony\Component\Serializer\Annotation\SerializedName; + +class SamePropertyAsMethodWithPropertySerializedNameDummy +{ + /** + * @SerializedName("free_trial_property") + */ + private $freeTrial; + + /** + * @SerializedName("has_subscribe_property") + */ + private $hasSubscribe; + + /** + * @SerializedName("get_ready_property") + */ + private $getReady; + + /** + * @SerializedName("is_active_property") + */ + private $isActive; + + public function __construct($freeTrial, $hasSubscribe, $getReady, $isActive) + { + $this->freeTrial = $freeTrial; + $this->hasSubscribe = $hasSubscribe; + $this->getReady = $getReady; + $this->isActive = $isActive; + } + + public function getFreeTrial() + { + return $this->freeTrial; + } + + public function hasSubscribe() + { + return $this->hasSubscribe; + } + + public function getReady() + { + return $this->getReady; + } + + public function isActive() + { + return $this->isActive; + } +} diff --git a/Tests/Normalizer/ObjectNormalizerTest.php b/Tests/Normalizer/ObjectNormalizerTest.php index 36957ac5..01c3b785 100644 --- a/Tests/Normalizer/ObjectNormalizerTest.php +++ b/Tests/Normalizer/ObjectNormalizerTest.php @@ -40,6 +40,9 @@ use Symfony\Component\Serializer\Tests\Fixtures\Php74Dummy; use Symfony\Component\Serializer\Tests\Fixtures\Php74DummyPrivate; use Symfony\Component\Serializer\Tests\Fixtures\Php80Dummy; +use Symfony\Component\Serializer\Tests\Fixtures\SamePropertyAsMethodDummy; +use Symfony\Component\Serializer\Tests\Fixtures\SamePropertyAsMethodWithMethodSerializedNameDummy; +use Symfony\Component\Serializer\Tests\Fixtures\SamePropertyAsMethodWithPropertySerializedNameDummy; use Symfony\Component\Serializer\Tests\Fixtures\SiblingHolder; use Symfony\Component\Serializer\Tests\Normalizer\Features\AttributesTestTrait; use Symfony\Component\Serializer\Tests\Normalizer\Features\CacheableObjectAttributesTestTrait; @@ -869,6 +872,53 @@ public function testNormalizeStdClass() $this->assertSame(['baz' => 'baz'], $this->normalizer->normalize($o2)); } + + public function testSamePropertyAsMethod() + { + $object = new SamePropertyAsMethodDummy('free_trial', 'has_subscribe', 'get_ready', 'is_active'); + $expected = [ + 'freeTrial' => 'free_trial', + 'hasSubscribe' => 'has_subscribe', + 'getReady' => 'get_ready', + 'isActive' => 'is_active', + ]; + + $this->assertSame($expected, $this->normalizer->normalize($object)); + } + + public function testSamePropertyAsMethodWithPropertySerializedName() + { + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $this->normalizer = new ObjectNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory)); + $this->normalizer->setSerializer($this->serializer); + + $object = new SamePropertyAsMethodWithPropertySerializedNameDummy('free_trial', 'has_subscribe', 'get_ready', 'is_active'); + $expected = [ + 'free_trial_property' => 'free_trial', + 'has_subscribe_property' => 'has_subscribe', + 'get_ready_property' => 'get_ready', + 'is_active_property' => 'is_active', + ]; + + $this->assertSame($expected, $this->normalizer->normalize($object)); + } + + public function testSamePropertyAsMethodWithMethodSerializedName() + { + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $this->normalizer = new ObjectNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory)); + $this->normalizer->setSerializer($this->serializer); + + $object = new SamePropertyAsMethodWithMethodSerializedNameDummy('free_trial', 'has_subscribe', 'get_ready', 'is_active'); + $expected = [ + 'free_trial_method' => 'free_trial', + 'has_subscribe_method' => 'has_subscribe', + 'get_ready_method' => 'get_ready', + 'is_active_method' => 'is_active', + ]; + + $this->assertSame($expected, $this->normalizer->normalize($object)); + } } class ProxyObjectDummy extends ObjectDummy