feature #19277 [Serializer] Argument objects (theofidry, dunglas)
This PR was merged into the 3.2-dev branch. Discussion ---------- [Serializer] Argument objects | Q | A | ------------- | --- | Branch? | 3.1 | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | TODO | Fixed tickets | none | License | MIT | Doc PR | TODO Assuming with have the two following entities: ```php namespace AppBundle\Entity; class Dummy { public function __construct(int $id, string $name, string $email, AnotherDummy $anotherDummy) { $this->id = $id; $this->name = $name; $this->email = $email; $this->anotherDummy = $anotherDummy; } } class AnotherDummy { public function __construct(int $id, string $uuid, bool $isEnabled) { $this->id = $id; $this->uuid = $uuid; $this->isEnabled = $isEnabled; } } ``` Doing the following will fail: ```php $serializer->denormalize( [ 'id' => $i, 'name' => 'dummy', 'email' => 'du@ex.com', 'another_dummy' => [ 'id' => 1000 + $i, 'uuid' => 'azerty', 'is_enabled' => true, ], ], \AppBundle\Entity\Dummy::class ); ``` with a type error, because the 4th argument passed to `Dummy::__construct()` will be an array. The following patch checks if the type of the argument is an object, and if it is tries to denormalize that object as well. I'm not sure if it's me missing something or this is a use case that has been omitted (willingly or not), but if it's a valuable patch I would be happy to work on finishing it. Commits -------988eba1
fix tests98bcb91
Merge pull request #1 from dunglas/theofidry-feature/param-object7b5d55d
Prevent BC in instantiateObjecte437e04
fix reflection type3fe9802
revert CS5556fa5
fixd4cdb00
fix CS93608dc
Add deprecation messagef46a176
Apply patchf361e52
fix tests4884a2e
f1e64e999
Address commentse99a90b
Add tests7bd4ac5
Test
This commit is contained in:
commit
c221908fc4
@ -281,13 +281,16 @@ abstract class AbstractNormalizer extends SerializerAwareNormalizer implements N
|
||||
* @param array $context
|
||||
* @param \ReflectionClass $reflectionClass
|
||||
* @param array|bool $allowedAttributes
|
||||
* @param string|null $format
|
||||
*
|
||||
* @return object
|
||||
*
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
protected function instantiateObject(array &$data, $class, array &$context, \ReflectionClass $reflectionClass, $allowedAttributes)
|
||||
protected function instantiateObject(array &$data, $class, array &$context, \ReflectionClass $reflectionClass, $allowedAttributes/*, $format = null*/)
|
||||
{
|
||||
$format = func_num_args() >= 6 ? func_get_arg(5) : null;
|
||||
|
||||
if (
|
||||
isset($context[static::OBJECT_TO_POPULATE]) &&
|
||||
is_object($context[static::OBJECT_TO_POPULATE]) &&
|
||||
@ -319,8 +322,18 @@ abstract class AbstractNormalizer extends SerializerAwareNormalizer implements N
|
||||
$params = array_merge($params, $data[$paramName]);
|
||||
}
|
||||
} elseif ($allowed && !$ignored && (isset($data[$key]) || array_key_exists($key, $data))) {
|
||||
$params[] = $data[$key];
|
||||
// don't run set for a parameter passed to the constructor
|
||||
$parameterData = $data[$key];
|
||||
try {
|
||||
if (null !== $constructorParameter->getClass()) {
|
||||
$parameterClass = $constructorParameter->getClass()->getName();
|
||||
$parameterData = $this->serializer->deserialize($parameterData, $parameterClass, $format, $context);
|
||||
}
|
||||
} catch (\ReflectionException $e) {
|
||||
throw new RuntimeException(sprintf('Could not determine the class of the parameter "%s".', $key), 0, $e);
|
||||
}
|
||||
|
||||
// Don't run set for a parameter passed to the constructor
|
||||
$params[] = $parameterData;
|
||||
unset($data[$key]);
|
||||
} elseif ($constructorParameter->isDefaultValueAvailable()) {
|
||||
$params[] = $constructorParameter->getDefaultValue();
|
||||
|
@ -175,7 +175,7 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer
|
||||
$normalizedData = $this->prepareForDenormalization($data);
|
||||
|
||||
$reflectionClass = new \ReflectionClass($class);
|
||||
$object = $this->instantiateObject($normalizedData, $class, $context, $reflectionClass, $allowedAttributes);
|
||||
$object = $this->instantiateObject($normalizedData, $class, $context, $reflectionClass, $allowedAttributes, $format);
|
||||
|
||||
foreach ($normalizedData as $attribute => $value) {
|
||||
if ($this->nameConverter) {
|
||||
|
@ -47,7 +47,7 @@ class GetSetMethodNormalizer extends AbstractObjectNormalizer
|
||||
$normalizedData = $this->prepareForDenormalization($data);
|
||||
|
||||
$reflectionClass = new \ReflectionClass($class);
|
||||
$object = $this->instantiateObject($normalizedData, $class, $context, $reflectionClass, $allowedAttributes);
|
||||
$object = $this->instantiateObject($normalizedData, $class, $context, $reflectionClass, $allowedAttributes, $format);
|
||||
|
||||
$classMethods = get_class_methods($object);
|
||||
foreach ($normalizedData as $attribute => $value) {
|
||||
|
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\Serializer\Tests\Fixtures;
|
||||
|
||||
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
|
||||
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
|
||||
use Symfony\Component\Serializer\SerializerInterface;
|
||||
|
||||
/**
|
||||
* @author Théo FIDRY <theo.fidry@gmail.com>
|
||||
*/
|
||||
class DenormalizerDecoratorSerializer implements SerializerInterface
|
||||
{
|
||||
private $normalizer;
|
||||
|
||||
/**
|
||||
* @param NormalizerInterface|DenormalizerInterface $normalizer
|
||||
*/
|
||||
public function __construct($normalizer)
|
||||
{
|
||||
if (false === $normalizer instanceof NormalizerInterface && false === $normalizer instanceof DenormalizerInterface) {
|
||||
throw new \InvalidArgumentException();
|
||||
}
|
||||
|
||||
$this->normalizer = $normalizer;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function serialize($data, $format, array $context = array())
|
||||
{
|
||||
return $this->normalizer->normalize($data, $format, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function deserialize($data, $type, $format, array $context = array())
|
||||
{
|
||||
return $this->normalizer->denormalize($data, $type, $format, $context);
|
||||
}
|
||||
}
|
@ -24,6 +24,19 @@ class AbstractObjectNormalizerTest extends \PHPUnit_Framework_TestCase
|
||||
$this->assertNull($normalizedData->bar);
|
||||
$this->assertSame('baz', $normalizedData->baz);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group legacy
|
||||
*/
|
||||
public function testInstantiateObjectDenormalizer()
|
||||
{
|
||||
$data = array('foo' => 'foo', 'bar' => 'bar', 'baz' => 'baz');
|
||||
$class = __NAMESPACE__.'\Dummy';
|
||||
$context = array();
|
||||
|
||||
$normalizer = new AbstractObjectNormalizerDummy();
|
||||
$normalizer->instantiateObject($data, $class, $context, new \ReflectionClass($class), array());
|
||||
}
|
||||
}
|
||||
|
||||
class AbstractObjectNormalizerDummy extends AbstractObjectNormalizer
|
||||
@ -45,6 +58,11 @@ class AbstractObjectNormalizerDummy extends AbstractObjectNormalizer
|
||||
{
|
||||
return in_array($attribute, array('foo', 'baz'));
|
||||
}
|
||||
|
||||
public function instantiateObject(array &$data, $class, array &$context, \ReflectionClass $reflectionClass, $allowedAttributes)
|
||||
{
|
||||
return parent::instantiateObject($data, $class, $context, $reflectionClass, $allowedAttributes);
|
||||
}
|
||||
}
|
||||
|
||||
class Dummy
|
||||
|
@ -21,6 +21,7 @@ use Symfony\Component\Serializer\Serializer;
|
||||
use Symfony\Component\Serializer\SerializerInterface;
|
||||
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
|
||||
use Symfony\Component\Serializer\Tests\Fixtures\CircularReferenceDummy;
|
||||
use Symfony\Component\Serializer\Tests\Fixtures\DenormalizerDecoratorSerializer;
|
||||
use Symfony\Component\Serializer\Tests\Fixtures\MaxDepthDummy;
|
||||
use Symfony\Component\Serializer\Tests\Fixtures\SiblingHolder;
|
||||
use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader;
|
||||
@ -157,6 +158,49 @@ class ObjectNormalizerTest extends \PHPUnit_Framework_TestCase
|
||||
$this->assertEquals('bar', $obj->bar);
|
||||
}
|
||||
|
||||
public function testConstructorWithObjectTypeHintDenormalize()
|
||||
{
|
||||
$data = array(
|
||||
'id' => 10,
|
||||
'inner' => array(
|
||||
'foo' => 'oof',
|
||||
'bar' => 'rab',
|
||||
),
|
||||
);
|
||||
|
||||
$normalizer = new ObjectNormalizer();
|
||||
$serializer = new DenormalizerDecoratorSerializer($normalizer);
|
||||
$normalizer->setSerializer($serializer);
|
||||
|
||||
$obj = $normalizer->denormalize($data, DummyWithConstructorObject::class);
|
||||
$this->assertInstanceOf(DummyWithConstructorObject::class, $obj);
|
||||
$this->assertEquals(10, $obj->getId());
|
||||
$this->assertInstanceOf(ObjectInner::class, $obj->getInner());
|
||||
$this->assertEquals('oof', $obj->getInner()->foo);
|
||||
$this->assertEquals('rab', $obj->getInner()->bar);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Serializer\Exception\RuntimeException
|
||||
* @expectedExceptionMessage Could not determine the class of the parameter "unknown".
|
||||
*/
|
||||
public function testConstructorWithUnknownObjectTypeHintDenormalize()
|
||||
{
|
||||
$data = array(
|
||||
'id' => 10,
|
||||
'unknown' => array(
|
||||
'foo' => 'oof',
|
||||
'bar' => 'rab',
|
||||
),
|
||||
);
|
||||
|
||||
$normalizer = new ObjectNormalizer();
|
||||
$serializer = new DenormalizerDecoratorSerializer($normalizer);
|
||||
$normalizer->setSerializer($serializer);
|
||||
|
||||
$normalizer->denormalize($data, DummyWithConstructorInexistingObject::class);
|
||||
}
|
||||
|
||||
public function testGroupsNormalize()
|
||||
{
|
||||
$classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
|
||||
@ -782,3 +826,32 @@ class FormatAndContextAwareNormalizer extends ObjectNormalizer
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class DummyWithConstructorObject
|
||||
{
|
||||
private $id;
|
||||
private $inner;
|
||||
|
||||
public function __construct($id, ObjectInner $inner)
|
||||
{
|
||||
$this->id = $id;
|
||||
$this->inner = $inner;
|
||||
}
|
||||
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getInner()
|
||||
{
|
||||
return $this->inner;
|
||||
}
|
||||
}
|
||||
|
||||
class DummyWithConstructorInexistingObject
|
||||
{
|
||||
public function __construct($id, Unknown $unknown)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user