From de58759a37fc1e268c91bc56da2e77459cb8f2aa Mon Sep 17 00:00:00 2001 From: Mathias Arlaud Date: Tue, 29 Aug 2023 16:10:08 +0200 Subject: [PATCH] [Serializer] Add default groups --- .../Extractor/SerializerExtractor.php | 6 +- .../Extractor/SerializerExtractorTest.php | 2 + .../Tests/Fixtures/IgnorePropertyDummy.php | 2 +- src/Symfony/Component/Serializer/CHANGELOG.md | 1 + .../MetadataAwareNameConverter.php | 7 +- .../Normalizer/AbstractNormalizer.php | 12 +++- .../Tests/Fixtures/Attributes/GroupDummy.php | 24 +++++++ .../Mapping/TestClassMetadataFactory.php | 8 +++ .../Normalizer/Features/GroupsTestTrait.php | 65 +++++++++++++++++-- .../Normalizer/GetSetMethodNormalizerTest.php | 2 + .../Tests/Normalizer/ObjectNormalizerTest.php | 2 + .../Normalizer/PropertyNormalizerTest.php | 15 ++++- 12 files changed, 131 insertions(+), 15 deletions(-) diff --git a/src/Symfony/Component/PropertyInfo/Extractor/SerializerExtractor.php b/src/Symfony/Component/PropertyInfo/Extractor/SerializerExtractor.php index 7ef020cefef23..620032f5b161b 100644 --- a/src/Symfony/Component/PropertyInfo/Extractor/SerializerExtractor.php +++ b/src/Symfony/Component/PropertyInfo/Extractor/SerializerExtractor.php @@ -38,11 +38,15 @@ public function getProperties(string $class, array $context = []): ?array return null; } + $groups = $context['serializer_groups'] ?? []; + $groupsHasBeenDefined = [] !== $groups; + $groups = array_merge($groups, ['Default', (false !== $nsSep = strrpos($class, '\\')) ? substr($class, $nsSep + 1) : $class]); + $properties = []; $serializerClassMetadata = $this->classMetadataFactory->getMetadataFor($class); foreach ($serializerClassMetadata->getAttributesMetadata() as $serializerAttributeMetadata) { - if (!$serializerAttributeMetadata->isIgnored() && (null === $context['serializer_groups'] || array_intersect($context['serializer_groups'], $serializerAttributeMetadata->getGroups()))) { + if (!$serializerAttributeMetadata->isIgnored() && (!$groupsHasBeenDefined || array_intersect(array_merge($serializerAttributeMetadata->getGroups(), ['*']), $groups))) { $properties[] = $serializerAttributeMetadata->getName(); } } diff --git a/src/Symfony/Component/PropertyInfo/Tests/Extractor/SerializerExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/Extractor/SerializerExtractorTest.php index ec3f949bbeb69..48d3ec957d167 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/Extractor/SerializerExtractorTest.php +++ b/src/Symfony/Component/PropertyInfo/Tests/Extractor/SerializerExtractorTest.php @@ -43,6 +43,8 @@ public function testGetProperties() public function testGetPropertiesWithIgnoredProperties() { $this->assertSame(['visibleProperty'], $this->extractor->getProperties(IgnorePropertyDummy::class, ['serializer_groups' => ['a']])); + $this->assertSame(['visibleProperty'], $this->extractor->getProperties(IgnorePropertyDummy::class, ['serializer_groups' => ['Default']])); + $this->assertSame(['visibleProperty'], $this->extractor->getProperties(IgnorePropertyDummy::class, ['serializer_groups' => ['IgnorePropertyDummy']])); } public function testGetPropertiesWithAnyGroup() diff --git a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/IgnorePropertyDummy.php b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/IgnorePropertyDummy.php index 9216ff801b27d..0cc4606241f20 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/IgnorePropertyDummy.php +++ b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/IgnorePropertyDummy.php @@ -19,7 +19,7 @@ */ class IgnorePropertyDummy { - #[Groups(['a'])] + #[Groups(['a', 'Default', 'IgnorePropertyDummy'])] public $visibleProperty; #[Groups(['a']), Ignore] diff --git a/src/Symfony/Component/Serializer/CHANGELOG.md b/src/Symfony/Component/Serializer/CHANGELOG.md index 13de5123f6c40..a5cb2e777acc2 100644 --- a/src/Symfony/Component/Serializer/CHANGELOG.md +++ b/src/Symfony/Component/Serializer/CHANGELOG.md @@ -5,6 +5,7 @@ CHANGELOG --- * Add `DateTimeNormalizer::CAST_KEY` context option + * Add `Default` and "class name" default groups 7.0 --- diff --git a/src/Symfony/Component/Serializer/NameConverter/MetadataAwareNameConverter.php b/src/Symfony/Component/Serializer/NameConverter/MetadataAwareNameConverter.php index 445ad7422ba92..327d92dc1b1c3 100644 --- a/src/Symfony/Component/Serializer/NameConverter/MetadataAwareNameConverter.php +++ b/src/Symfony/Component/Serializer/NameConverter/MetadataAwareNameConverter.php @@ -128,13 +128,16 @@ private function getCacheValueForAttributesMetadata(string $class, array $contex } $metadataGroups = $metadata->getGroups(); + $contextGroups = (array) ($context[AbstractNormalizer::GROUPS] ?? []); + $contextGroupsHasBeenDefined = [] !== $contextGroups; + $contextGroups = array_merge($contextGroups, ['Default', (false !== $nsSep = strrpos($class, '\\')) ? substr($class, $nsSep + 1) : $class]); - if ($contextGroups && !$metadataGroups) { + if ($contextGroupsHasBeenDefined && !$metadataGroups) { continue; } - if ($metadataGroups && !array_intersect($metadataGroups, $contextGroups) && !\in_array('*', $contextGroups, true)) { + if ($metadataGroups && !array_intersect(array_merge($metadataGroups, ['*']), $contextGroups)) { continue; } diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php index 776b00fc0a93c..8fbc850428c09 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php @@ -213,11 +213,17 @@ protected function getAllowedAttributes(string|object $classOrObject, array $con return false; } + $classMetadata = $this->classMetadataFactory->getMetadataFor($classOrObject); + $class = $classMetadata->getName(); + $groups = $this->getGroups($context); + $groupsHasBeenDefined = [] !== $groups; + $groups = array_merge($groups, ['Default', (false !== $nsSep = strrpos($class, '\\')) ? substr($class, $nsSep + 1) : $class]); $allowedAttributes = []; $ignoreUsed = false; - foreach ($this->classMetadataFactory->getMetadataFor($classOrObject)->getAttributesMetadata() as $attributeMetadata) { + + foreach ($classMetadata->getAttributesMetadata() as $attributeMetadata) { if ($ignore = $attributeMetadata->isIgnored()) { $ignoreUsed = true; } @@ -225,14 +231,14 @@ protected function getAllowedAttributes(string|object $classOrObject, array $con // If you update this check, update accordingly the one in Symfony\Component\PropertyInfo\Extractor\SerializerExtractor::getProperties() if ( !$ignore - && ([] === $groups || array_intersect(array_merge($attributeMetadata->getGroups(), ['*']), $groups)) + && (!$groupsHasBeenDefined || array_intersect(array_merge($attributeMetadata->getGroups(), ['*']), $groups)) && $this->isAllowedAttribute($classOrObject, $name = $attributeMetadata->getName(), null, $context) ) { $allowedAttributes[] = $attributesAsString ? $name : $attributeMetadata; } } - if (!$ignoreUsed && [] === $groups && $allowExtraAttributes) { + if (!$ignoreUsed && !$groupsHasBeenDefined && $allowExtraAttributes) { // Backward Compatibility with the code using this method written before the introduction of @Ignore return false; } diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/Attributes/GroupDummy.php b/src/Symfony/Component/Serializer/Tests/Fixtures/Attributes/GroupDummy.php index 749e841a5c05d..5c34c95a40a69 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/Attributes/GroupDummy.php +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/Attributes/GroupDummy.php @@ -27,6 +27,10 @@ class GroupDummy extends GroupDummyParent implements GroupDummyInterface protected $quux; private $fooBar; private $symfony; + #[Groups(['Default'])] + private $default; + #[Groups(['GroupDummy'])] + private $className; #[Groups(['b'])] public function setBar($bar) @@ -80,4 +84,24 @@ public function setQuux($quux): void { $this->quux = $quux; } + + public function setDefault($default) + { + $this->default = $default; + } + + public function getDefault() + { + return $this->default; + } + + public function setClassName($className) + { + $this->className = $className; + } + + public function getClassName() + { + return $this->className; + } } diff --git a/src/Symfony/Component/Serializer/Tests/Mapping/TestClassMetadataFactory.php b/src/Symfony/Component/Serializer/Tests/Mapping/TestClassMetadataFactory.php index 61147316a68ed..d617ffaebf86c 100644 --- a/src/Symfony/Component/Serializer/Tests/Mapping/TestClassMetadataFactory.php +++ b/src/Symfony/Component/Serializer/Tests/Mapping/TestClassMetadataFactory.php @@ -63,6 +63,14 @@ public static function createClassMetadata(string $namespace, bool $withParent = $symfony->addGroup('name_converter'); } + $default = new AttributeMetadata('default'); + $default->addGroup('Default'); + $expected->addAttributeMetadata($default); + + $className = new AttributeMetadata('className'); + $className->addGroup('GroupDummy'); + $expected->addAttributeMetadata($className); + // load reflection class so that the comparison passes $expected->getReflectionClass(); diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/Features/GroupsTestTrait.php b/src/Symfony/Component/Serializer/Tests/Normalizer/Features/GroupsTestTrait.php index 08d5e065a6440..ba4d7632385c2 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/Features/GroupsTestTrait.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/Features/GroupsTestTrait.php @@ -31,13 +31,18 @@ public function testGroupsNormalize() $obj = new GroupDummy(); $obj->setFoo('foo'); $obj->setBar('bar'); + $obj->setQuux('quux'); $obj->setFooBar('fooBar'); $obj->setSymfony('symfony'); $obj->setKevin('kevin'); $obj->setCoopTilleuls('coopTilleuls'); + $obj->setDefault('default'); + $obj->setClassName('className'); $this->assertEquals([ 'bar' => 'bar', + 'default' => 'default', + 'className' => 'className', ], $normalizer->normalize($obj, null, ['groups' => ['c']])); $this->assertEquals([ @@ -47,7 +52,26 @@ public function testGroupsNormalize() 'bar' => 'bar', 'kevin' => 'kevin', 'coopTilleuls' => 'coopTilleuls', + 'default' => 'default', + 'className' => 'className', ], $normalizer->normalize($obj, null, ['groups' => ['a', 'c']])); + + $this->assertEquals([ + 'default' => 'default', + 'className' => 'className', + ], $normalizer->normalize($obj, null, ['groups' => ['unknown']])); + + $this->assertEquals([ + 'quux' => 'quux', + 'symfony' => 'symfony', + 'foo' => 'foo', + 'fooBar' => 'fooBar', + 'bar' => 'bar', + 'kevin' => 'kevin', + 'coopTilleuls' => 'coopTilleuls', + 'default' => 'default', + 'className' => 'className', + ], $normalizer->normalize($obj)); } public function testGroupsDenormalize() @@ -55,27 +79,49 @@ public function testGroupsDenormalize() $normalizer = $this->getDenormalizerForGroups(); $obj = new GroupDummy(); - $obj->setFoo('foo'); + $obj->setDefault('default'); + $obj->setClassName('className'); - $data = ['foo' => 'foo', 'bar' => 'bar']; + $data = [ + 'foo' => 'foo', + 'bar' => 'bar', + 'quux' => 'quux', + 'default' => 'default', + 'className' => 'className', + ]; - $normalized = $normalizer->denormalize( + $denormalized = $normalizer->denormalize( + $data, + GroupDummy::class, + null, + ['groups' => ['unknown']] + ); + $this->assertEquals($obj, $denormalized); + + $obj->setFoo('foo'); + + $denormalized = $normalizer->denormalize( $data, GroupDummy::class, null, ['groups' => ['a']] ); - $this->assertEquals($obj, $normalized); + $this->assertEquals($obj, $denormalized); $obj->setBar('bar'); - $normalized = $normalizer->denormalize( + $denormalized = $normalizer->denormalize( $data, GroupDummy::class, null, ['groups' => ['a', 'b']] ); - $this->assertEquals($obj, $normalized); + $this->assertEquals($obj, $denormalized); + + $obj->setQuux('quux'); + + $denormalized = $normalizer->denormalize($data, GroupDummy::class); + $this->assertEquals($obj, $denormalized); } public function testNormalizeNoPropertyInGroup() @@ -84,7 +130,12 @@ public function testNormalizeNoPropertyInGroup() $obj = new GroupDummy(); $obj->setFoo('foo'); + $obj->setDefault('default'); + $obj->setClassName('className'); - $this->assertEquals([], $normalizer->normalize($obj, null, ['groups' => ['notExist']])); + $this->assertEquals([ + 'default' => 'default', + 'className' => 'className', + ], $normalizer->normalize($obj, null, ['groups' => ['notExist']])); } } diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/GetSetMethodNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/GetSetMethodNormalizerTest.php index 8f28999feb006..8000dea19fe0f 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/GetSetMethodNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/GetSetMethodNormalizerTest.php @@ -295,6 +295,8 @@ public function testGroupsNormalizeWithNameConverter() 'bar' => null, 'foo_bar' => '@dunglas', 'symfony' => '@coopTilleuls', + 'default' => null, + 'class_name' => null, ], $this->normalizer->normalize($obj, null, [GetSetMethodNormalizer::GROUPS => ['name_converter']]) ); diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php index 7350e71bf64f0..fa47995da7882 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php @@ -482,6 +482,8 @@ public function testGroupsNormalizeWithNameConverter() 'bar' => null, 'foo_bar' => '@dunglas', 'symfony' => '@coopTilleuls', + 'default' => null, + 'class_name' => null, ], $this->normalizer->normalize($obj, null, [ObjectNormalizer::GROUPS => ['name_converter']]) ); diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/PropertyNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/PropertyNormalizerTest.php index 7ba3d95eb46e7..04a9afaf8bde0 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/PropertyNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/PropertyNormalizerTest.php @@ -184,7 +184,18 @@ public function testNormalizeWithParentClass() $group->setKevin('Kevin'); $group->setCoopTilleuls('coop'); $this->assertEquals( - ['foo' => 'foo', 'bar' => 'bar', 'quux' => 'quux', 'kevin' => 'Kevin', 'coopTilleuls' => 'coop', 'fooBar' => null, 'symfony' => null, 'baz' => 'baz'], + [ + 'foo' => 'foo', + 'bar' => 'bar', + 'quux' => 'quux', + 'kevin' => 'Kevin', + 'coopTilleuls' => 'coop', + 'fooBar' => null, + 'symfony' => null, + 'baz' => 'baz', + 'default' => null, + 'className' => null, + ], $this->normalizer->normalize($group, 'any') ); } @@ -303,6 +314,8 @@ public function testGroupsNormalizeWithNameConverter() 'bar' => null, 'foo_bar' => '@dunglas', 'symfony' => '@coopTilleuls', + 'default' => null, + 'class_name' => null, ], $this->normalizer->normalize($obj, null, [PropertyNormalizer::GROUPS => ['name_converter']]) );