From c91b7afe35eee53234463871dc97e9cd23f67dbb Mon Sep 17 00:00:00 2001 From: Samuel ROZE Date: Fri, 29 Jun 2018 12:43:55 +0100 Subject: [PATCH] Ensure the class discriminator mechanism works with serialization groups as well --- .../Normalizer/ObjectNormalizer.php | 21 +++++++ .../Tests/Fixtures/DummyMessageInterface.php | 4 +- .../Tests/Fixtures/DummyMessageNumberOne.php | 7 +++ .../Tests/Fixtures/DummyMessageNumberTwo.php | 19 ++++++ .../Serializer/Tests/SerializerTest.php | 62 +++++++++---------- 5 files changed, 79 insertions(+), 34 deletions(-) create mode 100644 src/Symfony/Component/Serializer/Tests/Fixtures/DummyMessageNumberTwo.php diff --git a/src/Symfony/Component/Serializer/Normalizer/ObjectNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/ObjectNormalizer.php index bf463fe545..6c9098528b 100644 --- a/src/Symfony/Component/Serializer/Normalizer/ObjectNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/ObjectNormalizer.php @@ -16,6 +16,7 @@ use Symfony\Component\PropertyAccess\PropertyAccess; use Symfony\Component\PropertyAccess\PropertyAccessorInterface; use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface; use Symfony\Component\Serializer\Exception\RuntimeException; +use Symfony\Component\Serializer\Mapping\AttributeMetadata; use Symfony\Component\Serializer\Mapping\ClassDiscriminatorResolverInterface; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; use Symfony\Component\Serializer\NameConverter\NameConverterInterface; @@ -131,4 +132,24 @@ class ObjectNormalizer extends AbstractObjectNormalizer // Properties not found are ignored } } + + /** + * {@inheritdoc} + */ + protected function getAllowedAttributes($classOrObject, array $context, $attributesAsString = false) + { + if (false === $allowedAttributes = parent::getAllowedAttributes($classOrObject, $context, $attributesAsString)) { + return false; + } + + if (null !== $this->classDiscriminatorResolver && null !== $discriminatorMapping = $this->classDiscriminatorResolver->getMappingForMappedObject($classOrObject)) { + $allowedAttributes[] = $attributesAsString ? $discriminatorMapping->getTypeProperty() : new AttributeMetadata($discriminatorMapping->getTypeProperty()); + + foreach ($discriminatorMapping->getTypesMapping() as $class) { + $allowedAttributes = array_merge($allowedAttributes, parent::getAllowedAttributes($class, $context, $attributesAsString)); + } + } + + return $allowedAttributes; + } } diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/DummyMessageInterface.php b/src/Symfony/Component/Serializer/Tests/Fixtures/DummyMessageInterface.php index f0b4c4d128..55bb00bc8e 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/DummyMessageInterface.php +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/DummyMessageInterface.php @@ -15,8 +15,8 @@ use Symfony\Component\Serializer\Annotation\DiscriminatorMap; /** * @DiscriminatorMap(typeProperty="type", mapping={ - * "first"="Symfony\Component\Serializer\Tests\Fixtures\AbstractDummyFirstChild", - * "second"="Symfony\Component\Serializer\Tests\Fixtures\AbstractDummySecondChild" + * "one"="Symfony\Component\Serializer\Tests\Fixtures\DummyMessageNumberOne", + * "two"="Symfony\Component\Serializer\Tests\Fixtures\DummyMessageNumberTwo" * }) * * @author Samuel Roze diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/DummyMessageNumberOne.php b/src/Symfony/Component/Serializer/Tests/Fixtures/DummyMessageNumberOne.php index 381f7f8a6c..200476b542 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/DummyMessageNumberOne.php +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/DummyMessageNumberOne.php @@ -11,10 +11,17 @@ namespace Symfony\Component\Serializer\Tests\Fixtures; +use Symfony\Component\Serializer\Annotation\Groups; + /** * @author Samuel Roze */ class DummyMessageNumberOne implements DummyMessageInterface { public $one; + + /** + * @Groups({"two"}) + */ + public $two; } diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/DummyMessageNumberTwo.php b/src/Symfony/Component/Serializer/Tests/Fixtures/DummyMessageNumberTwo.php new file mode 100644 index 0000000000..060c10dd79 --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/DummyMessageNumberTwo.php @@ -0,0 +1,19 @@ + + * + * 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; + +/** + * @author Samuel Roze + */ +class DummyMessageNumberTwo implements DummyMessageInterface +{ +} diff --git a/src/Symfony/Component/Serializer/Tests/SerializerTest.php b/src/Symfony/Component/Serializer/Tests/SerializerTest.php index 7550b3c9b7..25fbbf38ce 100644 --- a/src/Symfony/Component/Serializer/Tests/SerializerTest.php +++ b/src/Symfony/Component/Serializer/Tests/SerializerTest.php @@ -11,11 +11,14 @@ namespace Symfony\Component\Serializer\Tests; +use Doctrine\Common\Annotations\AnnotationReader; use PHPUnit\Framework\TestCase; use Symfony\Component\Serializer\Mapping\ClassDiscriminatorFromClassMetadata; use Symfony\Component\Serializer\Mapping\ClassDiscriminatorMapping; use Symfony\Component\Serializer\Mapping\ClassMetadata; +use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; +use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer; use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; @@ -398,11 +401,9 @@ class SerializerTest extends TestCase $example = new DummyMessageNumberOne(); $example->one = 1; - $jsonData = '{"message-type":"one","one":1}'; - - $discriminatorResolver = new ClassDiscriminatorFromClassMetadata($this->metadataFactoryMockForDummyInterface()); - $serializer = new Serializer(array(new ObjectNormalizer(null, null, null, null, $discriminatorResolver)), array('json' => new JsonEncoder())); + $jsonData = '{"type":"one","one":1,"two":null}'; + $serializer = $this->serializerWithClassDiscriminator(); $deserialized = $serializer->deserialize($jsonData, DummyMessageInterface::class, 'json'); $this->assertEquals($example, $deserialized); @@ -410,51 +411,48 @@ class SerializerTest extends TestCase $this->assertEquals($jsonData, $serialized); } + public function testDeserializeAndSerializeInterfacedObjectsWithTheClassMetadataDiscriminatorResolverAndGroups() + { + $example = new DummyMessageNumberOne(); + $example->two = 2; + + $serializer = $this->serializerWithClassDiscriminator(); + $deserialized = $serializer->deserialize('{"type":"one","one":1,"two":2}', DummyMessageInterface::class, 'json', array( + 'groups' => array('two'), + )); + + $this->assertEquals($example, $deserialized); + + $serialized = $serializer->serialize($deserialized, 'json', array( + 'groups' => array('two'), + )); + + $this->assertEquals('{"two":2,"type":"one"}', $serialized); + } + /** * @expectedException \Symfony\Component\Serializer\Exception\RuntimeException * @expectedExceptionMessage The type "second" has no mapped class for the abstract object "Symfony\Component\Serializer\Tests\Fixtures\DummyMessageInterface" */ public function testExceptionWhenTypeIsNotKnownInDiscriminator() { - $discriminatorResolver = new ClassDiscriminatorFromClassMetadata($this->metadataFactoryMockForDummyInterface()); - $serializer = new Serializer(array(new ObjectNormalizer(null, null, null, null, $discriminatorResolver)), array('json' => new JsonEncoder())); - $serializer->deserialize('{"message-type":"second","one":1}', DummyMessageInterface::class, 'json'); + $this->serializerWithClassDiscriminator()->deserialize('{"type":"second","one":1}', DummyMessageInterface::class, 'json'); } /** * @expectedException \Symfony\Component\Serializer\Exception\RuntimeException - * @expectedExceptionMessage Type property "message-type" not found for the abstract object "Symfony\Component\Serializer\Tests\Fixtures\DummyMessageInterface" + * @expectedExceptionMessage Type property "type" not found for the abstract object "Symfony\Component\Serializer\Tests\Fixtures\DummyMessageInterface" */ public function testExceptionWhenTypeIsNotInTheBodyToDeserialiaze() { - $discriminatorResolver = new ClassDiscriminatorFromClassMetadata($this->metadataFactoryMockForDummyInterface()); - $serializer = new Serializer(array(new ObjectNormalizer(null, null, null, null, $discriminatorResolver)), array('json' => new JsonEncoder())); - $serializer->deserialize('{"one":1}', DummyMessageInterface::class, 'json'); + $this->serializerWithClassDiscriminator()->deserialize('{"one":1}', DummyMessageInterface::class, 'json'); } - private function metadataFactoryMockForDummyInterface() + private function serializerWithClassDiscriminator() { - $factoryMock = $this->getMockBuilder(ClassMetadataFactoryInterface::class)->getMock(); - $factoryMock->method('hasMetadataFor')->will($this->returnValueMap(array( - array( - DummyMessageInterface::class, - true, - ), - ))); + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); - $factoryMock->method('getMetadataFor')->will($this->returnValueMap(array( - array( - DummyMessageInterface::class, - new ClassMetadata( - DummyMessageInterface::class, - new ClassDiscriminatorMapping('message-type', array( - 'one' => DummyMessageNumberOne::class, - )) - ), - ), - ))); - - return $factoryMock; + return new Serializer(array(new ObjectNormalizer($classMetadataFactory, null, null, null, new ClassDiscriminatorFromClassMetadata($classMetadataFactory))), array('json' => new JsonEncoder())); } }