bug #27773 [Serializer] Class discriminator and serialization groups (sroze)

This PR was merged into the 4.1 branch.

Discussion
----------

[Serializer] Class discriminator and serialization groups

| Q             | A
| ------------- | ---
| Branch?       | 4.1
| Bug fix?      | yes
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #27641
| License       | MIT
| Doc PR        | ø

It turns out the discriminator mapping does not work well with the serialization groups. This is fixing it (+ a little bit of cleaning in the tests).

Commits
-------

c91b7afe35 Ensure the class discriminator mechanism works with serialization groups as well
This commit is contained in:
Fabien Potencier 2018-06-30 11:09:26 +02:00
commit 18aec2dba8
5 changed files with 79 additions and 34 deletions

View File

@ -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;
}
}

View File

@ -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 <samuel.roze@gmail.com>

View File

@ -11,10 +11,17 @@
namespace Symfony\Component\Serializer\Tests\Fixtures;
use Symfony\Component\Serializer\Annotation\Groups;
/**
* @author Samuel Roze <samuel.roze@gmail.com>
*/
class DummyMessageNumberOne implements DummyMessageInterface
{
public $one;
/**
* @Groups({"two"})
*/
public $two;
}

View File

@ -0,0 +1,19 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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 <samuel.roze@gmail.com>
*/
class DummyMessageNumberTwo implements DummyMessageInterface
{
}

View File

@ -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()));
}
}