Skip to content

Commit

Permalink
[Serializer] Allow denormalization of constructor arguments to array …
Browse files Browse the repository at this point in the history
…of objects
  • Loading branch information
cezarystepkowski committed Dec 2, 2018
1 parent 79b661d commit cf58d82
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 25 deletions.
59 changes: 34 additions & 25 deletions src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php
Expand Up @@ -406,32 +406,8 @@ protected function instantiateObject(array &$data, $class, array &$context, \Ref
$params = array_merge($params, $data[$paramName]);
}
} elseif ($allowed && !$ignored && (isset($data[$key]) || array_key_exists($key, $data))) {
$parameterData = $data[$key];
if (null === $parameterData && $constructorParameter->allowsNull()) {
$params[] = null;
// Don't run set for a parameter passed to the constructor
unset($data[$key]);
continue;
}
try {
if (null !== $constructorParameter->getClass()) {
if (!$this->serializer instanceof DenormalizerInterface) {
throw new LogicException(sprintf('Cannot create an instance of %s from serialized data because the serializer inject in "%s" is not a denormalizer', $constructorParameter->getClass(), self::class));
}
$parameterClass = $constructorParameter->getClass()->getName();
$parameterData = $this->serializer->denormalize($parameterData, $parameterClass, $format, $this->createChildContext($context, $paramName));
}
} catch (\ReflectionException $e) {
throw new RuntimeException(sprintf('Could not determine the class of the parameter "%s".', $key), 0, $e);
} catch (MissingConstructorArgumentsException $e) {
if (!$constructorParameter->getType()->allowsNull()) {
throw $e;
}
$parameterData = null;
}

// Don't run set for a parameter passed to the constructor
$params[] = $parameterData;
$params[] = $this->parseConstructorParameter($data, $key, $context, $constructorParameter, $format);
unset($data[$key]);
} elseif (array_key_exists($key, $context[static::DEFAULT_CONSTRUCTOR_ARGUMENTS][$class] ?? array())) {
$params[] = $context[static::DEFAULT_CONSTRUCTOR_ARGUMENTS][$class][$key];
Expand All @@ -454,6 +430,39 @@ protected function instantiateObject(array &$data, $class, array &$context, \Ref
return new $class();
}

/**
* @internal
*/
protected function parseConstructorParameter(array &$data, $key, array &$context, \ReflectionParameter $constructorParameter, $format)
{
$parameterData = $data[$key];
if (null === $parameterData && $constructorParameter->allowsNull()) {
$params[] = null;
// Don't run set for a parameter passed to the constructor
unset($data[$key]);

return $parameterData;
}
try {
if (null !== $constructorParameter->getClass()) {
if (!$this->serializer instanceof DenormalizerInterface) {
throw new LogicException(sprintf('Cannot create an instance of "%s" from serialized data because the serializer injected in "%s" is not a denormalizer', $constructorParameter->getClass(), self::class));
}
$parameterClass = $constructorParameter->getClass()->getName();
$parameterData = $this->serializer->denormalize($parameterData, $parameterClass, $format, $this->createChildContext($context, $constructorParameter->name));
}
} catch (\ReflectionException $e) {
throw new RuntimeException(sprintf('Could not determine the class of the parameter "%s".', $key), 0, $e);
} catch (MissingConstructorArgumentsException $e) {
if (!$constructorParameter->getType()->allowsNull()) {
throw $e;
}
$parameterData = null;
}

return $parameterData;
}

/**
* @param array $parentContext
* @param string $attribute
Expand Down
Expand Up @@ -157,6 +157,18 @@ protected function instantiateObject(array &$data, $class, array &$context, \Ref
return parent::instantiateObject($data, $class, $context, $reflectionClass, $allowedAttributes, $format);
}

/**
* @internal
*/
protected function parseConstructorParameter(array &$data, $key, array &$context, \ReflectionParameter $constructorParameter, $format)
{
if (!$constructorParameter->hasType() || $constructorParameter->getType()->isBuiltin()) {
return $this->validateAndDenormalize($constructorParameter->getDeclaringClass()->name, $constructorParameter->name, $data[$key], $format, $context);
}

return parent::parseConstructorParameter($data, $key, $context, $constructorParameter, $format);
}

/**
* Gets and caches attributes for the given object, format and context.
*
Expand Down
Expand Up @@ -157,6 +157,35 @@ public function testConstructorDenormalizeWithOptionalDefaultArgument()
$this->assertEquals('test', $obj->getBar());
}

public function testConstructorDenormalizeWithArgumentArrayOfObjects()
{
$extractor = new PropertyInfoExtractor(array(), array(new PhpDocExtractor()));
$normalizer = new ObjectNormalizer(null, null, null, $extractor);
$serializer = new Serializer(array(new ArrayDenormalizer(), $normalizer));

$expectedObjectInner1 = new ObjectInner();
$expectedObjectInner1->foo = 1;
$expectedObjectInner2 = new ObjectInner();
$expectedObjectInner2->foo = 2;

$data = array(
'id' => 10,
'inners' => array(array('foo' => 1), array('foo' => 2)),
);

$obj = $serializer->denormalize($data, DummyWithConstructorArrayOfObjects::class);

$this->assertInstanceOf(DummyWithConstructorArrayOfObjects::class, $obj);
$this->assertSame(10, $obj->getId());
$this->assertEquals(
array(
$expectedObjectInner1,
$expectedObjectInner2,
),
$obj->getInners()
);
}

public function testConstructorWithObjectDenormalize()
{
$data = new \stdClass();
Expand Down Expand Up @@ -1153,6 +1182,32 @@ public function otherMethod()
}
}

class DummyWithConstructorArrayOfObjects
{
private $id;

/**
* @var ObjectInner[]
*/
private $inners;

public function __construct($id, array $inners)
{
$this->id = $id;
$this->inners = $inners;
}

public function getId()
{
return $this->id;
}

public function getInners(): array
{
return $this->inners;
}
}

class ObjectWithStaticPropertiesAndMethods
{
public $foo = 'K';
Expand Down

0 comments on commit cf58d82

Please sign in to comment.