Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Serializer] Allow denormalization of constructor arguments to array of objects #27221

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/Symfony/Component/Serializer/CHANGELOG.md
@@ -1,6 +1,11 @@
CHANGELOG
=========

4.3.0
-----

* added support for denormalization of constructor arguments to an array of objects

4.2.0
-----

Expand Down
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