[Serializer]: AbstractObjectNormalizer ignores the property types of discriminated classes

Add tests

Remove test group

Allow null

Add quux null attribute

Add quux value to serialize test
This commit is contained in:
Sander 2019-07-05 07:45:32 +02:00
parent e30800df2c
commit 9fc56c7e28
7 changed files with 138 additions and 6 deletions

View File

@ -281,13 +281,14 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer
$reflectionClass = new \ReflectionClass($class);
$object = $this->instantiateObject($normalizedData, $class, $context, $reflectionClass, $allowedAttributes, $format);
$resolvedClass = $this->objectClassResolver ? ($this->objectClassResolver)($object) : \get_class($object);
foreach ($normalizedData as $attribute => $value) {
if ($this->nameConverter) {
$attribute = $this->nameConverter->denormalize($attribute, $class, $format, $context);
$attribute = $this->nameConverter->denormalize($attribute, $resolvedClass, $format, $context);
}
if ((false !== $allowedAttributes && !\in_array($attribute, $allowedAttributes)) || !$this->isAllowedAttribute($class, $attribute, $format, $context)) {
if ((false !== $allowedAttributes && !\in_array($attribute, $allowedAttributes)) || !$this->isAllowedAttribute($resolvedClass, $attribute, $format, $context)) {
if (!($context[self::ALLOW_EXTRA_ATTRIBUTES] ?? $this->defaultContext[self::ALLOW_EXTRA_ATTRIBUTES])) {
$extraAttributes[] = $attribute;
}
@ -295,7 +296,7 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer
continue;
}
$value = $this->validateAndDenormalize($class, $attribute, $value, $format, $context);
$value = $this->validateAndDenormalize($resolvedClass, $attribute, $value, $format, $context);
try {
$this->setAttributeValue($object, $attribute, $value, $format, $context);
} catch (InvalidArgumentException $e) {

View File

@ -15,10 +15,23 @@ class AbstractDummyFirstChild extends AbstractDummy
{
public $bar;
/** @var DummyFirstChildQuux|null */
public $quux;
public function __construct($foo = null, $bar = null)
{
parent::__construct($foo);
$this->bar = $bar;
}
public function getQuux(): ?DummyFirstChildQuux
{
return $this->quux;
}
public function setQuux(DummyFirstChildQuux $quux): void
{
$this->quux = $quux;
}
}

View File

@ -15,10 +15,23 @@ class AbstractDummySecondChild extends AbstractDummy
{
public $baz;
/** @var DummySecondChildQuux|null */
public $quux;
public function __construct($foo = null, $baz = null)
{
parent::__construct($foo);
$this->baz = $baz;
}
public function getQuux(): ?DummySecondChildQuux
{
return $this->quux;
}
public function setQuux(DummySecondChildQuux $quux): void
{
$this->quux = $quux;
}
}

View File

@ -0,0 +1,30 @@
<?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;
class DummyFirstChildQuux
{
/**
* @var string
*/
private $value;
public function __construct(string $value)
{
$this->value = $value;
}
public function getValue(): string
{
return $this->value;
}
}

View File

@ -0,0 +1,30 @@
<?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;
class DummySecondChildQuux
{
/**
* @var string
*/
private $value;
public function __construct(string $value)
{
$this->value = $value;
}
public function getValue(): string
{
return $this->value;
}
}

View File

@ -16,13 +16,22 @@ use PHPUnit\Framework\TestCase;
use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor;
use Symfony\Component\PropertyInfo\Type;
use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
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\AbstractObjectNormalizer;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Serializer\SerializerAwareInterface;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Component\Serializer\Tests\Fixtures\AbstractDummy;
use Symfony\Component\Serializer\Tests\Fixtures\AbstractDummyFirstChild;
use Symfony\Component\Serializer\Tests\Fixtures\AbstractDummySecondChild;
use Symfony\Component\Serializer\Tests\Fixtures\DummySecondChildQuux;
class AbstractObjectNormalizerTest extends TestCase
{
@ -147,6 +156,39 @@ class AbstractObjectNormalizerTest extends TestCase
return $denormalizer;
}
public function testDenormalizeWithDiscriminatorMapUsesCorrectClassname()
{
$factory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
$loaderMock = $this->getMockBuilder(ClassMetadataFactoryInterface::class)->getMock();
$loaderMock->method('hasMetadataFor')->willReturnMap([
[
AbstractDummy::class,
true,
],
]);
$loaderMock->method('getMetadataFor')->willReturnMap([
[
AbstractDummy::class,
new ClassMetadata(
AbstractDummy::class,
new ClassDiscriminatorMapping('type', [
'first' => AbstractDummyFirstChild::class,
'second' => AbstractDummySecondChild::class,
])
),
],
]);
$discriminatorResolver = new ClassDiscriminatorFromClassMetadata($loaderMock);
$normalizer = new AbstractObjectNormalizerDummy($factory, null, new PhpDocExtractor(), $discriminatorResolver);
$serializer = new Serializer([$normalizer]);
$normalizer->setSerializer($serializer);
$normalizedData = $normalizer->denormalize(['foo' => 'foo', 'baz' => 'baz', 'quux' => ['value' => 'quux'], 'type' => 'second'], AbstractDummy::class);
$this->assertInstanceOf(DummySecondChildQuux::class, $normalizedData->quux);
}
/**
* Test that additional attributes throw an exception if no metadata factory is specified.
*
@ -190,7 +232,7 @@ class AbstractObjectNormalizerDummy extends AbstractObjectNormalizer
protected function isAllowedAttribute($classOrObject, $attribute, $format = null, array $context = [])
{
return \in_array($attribute, ['foo', 'baz']);
return \in_array($attribute, ['foo', 'baz', 'quux', 'value']);
}
public function instantiateObject(array &$data, $class, array &$context, \ReflectionClass $reflectionClass, $allowedAttributes, string $format = null)

View File

@ -13,6 +13,7 @@ namespace Symfony\Component\Serializer\Tests;
use Doctrine\Common\Annotations\AnnotationReader;
use PHPUnit\Framework\TestCase;
use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor;
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Mapping\ClassDiscriminatorFromClassMetadata;
@ -34,6 +35,7 @@ use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Serializer\Tests\Fixtures\AbstractDummy;
use Symfony\Component\Serializer\Tests\Fixtures\AbstractDummyFirstChild;
use Symfony\Component\Serializer\Tests\Fixtures\AbstractDummySecondChild;
use Symfony\Component\Serializer\Tests\Fixtures\DummyFirstChildQuux;
use Symfony\Component\Serializer\Tests\Fixtures\DummyMessageInterface;
use Symfony\Component\Serializer\Tests\Fixtures\DummyMessageNumberOne;
use Symfony\Component\Serializer\Tests\Fixtures\DummyMessageNumberTwo;
@ -382,6 +384,7 @@ class SerializerTest extends TestCase
public function testDeserializeAndSerializeAbstractObjectsWithTheClassMetadataDiscriminatorResolver()
{
$example = new AbstractDummyFirstChild('foo-value', 'bar-value');
$example->setQuux(new DummyFirstChildQuux('quux'));
$loaderMock = $this->getMockBuilder(ClassMetadataFactoryInterface::class)->getMock();
$loaderMock->method('hasMetadataFor')->willReturnMap([
@ -405,9 +408,9 @@ class SerializerTest extends TestCase
]);
$discriminatorResolver = new ClassDiscriminatorFromClassMetadata($loaderMock);
$serializer = new Serializer([new ObjectNormalizer(null, null, null, null, $discriminatorResolver)], ['json' => new JsonEncoder()]);
$serializer = new Serializer([new ObjectNormalizer(null, null, null, new PhpDocExtractor(), $discriminatorResolver)], ['json' => new JsonEncoder()]);
$jsonData = '{"type":"first","bar":"bar-value","foo":"foo-value"}';
$jsonData = '{"type":"first","quux":{"value":"quux"},"bar":"bar-value","foo":"foo-value"}';
$deserialized = $serializer->deserialize($jsonData, AbstractDummy::class, 'json');
$this->assertEquals($example, $deserialized);