From cf58d8230a2d1fbe757c08bee76f736224727e63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cezary=20St=C4=99pkowski?= Date: Thu, 10 May 2018 14:29:58 +0200 Subject: [PATCH 1/2] [Serializer] Allow denormalization of constructor arguments to array of objects --- .../Normalizer/AbstractNormalizer.php | 59 +++++++++++-------- .../Normalizer/AbstractObjectNormalizer.php | 12 ++++ .../Tests/Normalizer/ObjectNormalizerTest.php | 55 +++++++++++++++++ 3 files changed, 101 insertions(+), 25 deletions(-) diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php index 468d9dc3d98d..60cda3c5efda 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php @@ -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]; @@ -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 diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php index 649df9c3a838..7f031ee69fba 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php @@ -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. * diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php index b4619c262d40..2ac2a859deb1 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php @@ -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(); @@ -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'; From 3f6dbe5758ad1e75cdf5683dcce78935e7bd9e99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cezary=20St=C4=99pkowski?= Date: Sun, 2 Dec 2018 19:21:41 +0100 Subject: [PATCH 2/2] Updated changelog --- src/Symfony/Component/Serializer/CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Symfony/Component/Serializer/CHANGELOG.md b/src/Symfony/Component/Serializer/CHANGELOG.md index a82a7c6b2d9e..30c95a9eabcd 100644 --- a/src/Symfony/Component/Serializer/CHANGELOG.md +++ b/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 -----