diff --git a/src/Serializer/AbstractItemNormalizer.php b/src/Serializer/AbstractItemNormalizer.php index b8c5eb8551c..1e81673f390 100644 --- a/src/Serializer/AbstractItemNormalizer.php +++ b/src/Serializer/AbstractItemNormalizer.php @@ -371,6 +371,14 @@ protected function getAllowedAttributes($classOrObject, array $context, $attribu } } + // Gets allowed attributes using serialization groups + $groupAllowedAttributes = parent::getAllowedAttributes($classOrObject, $context, $attributesAsString); + + if (\is_array($groupAllowedAttributes)) { + // Attributes need to be allowed by both API Resource and serialization groups at the same time + return array_intersect($allowedAttributes, $groupAllowedAttributes); + } + return $allowedAttributes; } diff --git a/tests/Serializer/AbstractItemNormalizerTest.php b/tests/Serializer/AbstractItemNormalizerTest.php index bee96b1e203..8916e584b1c 100644 --- a/tests/Serializer/AbstractItemNormalizerTest.php +++ b/tests/Serializer/AbstractItemNormalizerTest.php @@ -44,6 +44,9 @@ use Symfony\Component\PropertyAccess\PropertyAccessorInterface; use Symfony\Component\PropertyInfo\Type; use Symfony\Component\Serializer\Exception\UnexpectedValueException; +use Symfony\Component\Serializer\Mapping\AttributeMetadata; +use Symfony\Component\Serializer\Mapping\ClassMetadata; +use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; use Symfony\Component\Serializer\NameConverter\AdvancedNameConverterInterface; use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; @@ -266,6 +269,79 @@ public function testNormalizeWithSecuredProperty() ])); } + public function testNormalizeWithSerializationGroup() + { + $dummy = new Dummy(); + $dummy->setName('myName'); // Available only in group "Default" (Symfony's default group name) + $dummy->setDescription('myDescription'); // Available only in group "dummy:write" + + $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); + $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['name', 'description'])); + + $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); + $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), '', true)); + $propertyMetadataFactoryProphecy->create(Dummy::class, 'description', Argument::type('array'))->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), '', true)); + + $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); + $iriConverterProphecy->getIriFromItem($dummy)->willReturn('/secured_dummies/1'); + + $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class); + $propertyAccessorProphecy->getValue($dummy, 'name')->willReturn($dummy->getName()); + $propertyAccessorProphecy->getValue($dummy, 'description')->willReturn($dummy->getDescription()); + + // Class metadata + $titleAttributeMetadata = new AttributeMetadata('name'); + $titleAttributeMetadata->addGroup('Default'); + $descriptionAttributeMetadata = new AttributeMetadata('description'); + $descriptionAttributeMetadata->addGroup('dummy:write'); + $classMetadata = new ClassMetadata(Dummy::class); + $classMetadata->addAttributeMetadata($titleAttributeMetadata); + $classMetadata->addAttributeMetadata($descriptionAttributeMetadata); + + $classMetadataFactoryProphecy = $this->prophesize(ClassMetadataFactoryInterface::class); + $classMetadataFactoryProphecy->getMetadataFor($dummy)->willReturn($classMetadata); + $classMetadataFactoryProphecy->getMetadataFor(Dummy::class)->willReturn($classMetadata); + + $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); + $resourceClassResolverProphecy->getResourceClass($dummy, null)->willReturn(Dummy::class); + + $resourceAccessChecker = $this->prophesize(ResourceAccessCheckerInterface::class); + + $serializerProphecy = $this->prophesize(SerializerInterface::class); + $serializerProphecy->willImplement(NormalizerInterface::class); + $serializerProphecy->normalize('myName', null, Argument::type('array'))->willReturn('myName'); + $serializerProphecy->normalize('myDescription', null, Argument::type('array'))->willReturn('myDescription'); + + $normalizer = $this->getMockForAbstractClass(AbstractItemNormalizer::class, [ + $propertyNameCollectionFactoryProphecy->reveal(), + $propertyMetadataFactoryProphecy->reveal(), + $iriConverterProphecy->reveal(), + $resourceClassResolverProphecy->reveal(), + $propertyAccessorProphecy->reveal(), + null, + $classMetadataFactoryProphecy->reveal(), + null, + false, + [], + [], + null, + $resourceAccessChecker->reveal(), + ]); + $normalizer->setSerializer($serializerProphecy->reveal()); + + if (!interface_exists(AdvancedNameConverterInterface::class) && method_exists($normalizer, 'setIgnoredAttributes')) { + $normalizer->setIgnoredAttributes(['alias']); + } + + $expected = [ + 'name' => 'myName', + ]; + $this->assertEquals($expected, $normalizer->normalize($dummy, null, [ + 'resources' => [], + 'groups' => ['Default'], + ])); + } + public function testNormalizeReadableLinks() { $relatedDummy = new RelatedDummy();