From a3b779f5c652d4af98387612303107de1883a80e Mon Sep 17 00:00:00 2001 From: Maxime Veber Date: Thu, 14 Dec 2017 00:47:37 +0100 Subject: [PATCH 1/2] Add new MissingConstructorArgumentsException Before: impossible to catch value object hydratation failure After: catch the new exception No BC break. --- src/Symfony/Component/Serializer/CHANGELOG.md | 6 +++++ .../MissingConstructorArgumentsException.php | 21 +++++++++++++++ .../Normalizer/AbstractNormalizer.php | 4 ++- .../Tests/Normalizer/ObjectNormalizerTest.php | 26 +++++++++++++++++++ 4 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 src/Symfony/Component/Serializer/Exception/MissingConstructorArgumentsException.php diff --git a/src/Symfony/Component/Serializer/CHANGELOG.md b/src/Symfony/Component/Serializer/CHANGELOG.md index 853e00eb2bbec..7daf7ade5ab2c 100644 --- a/src/Symfony/Component/Serializer/CHANGELOG.md +++ b/src/Symfony/Component/Serializer/CHANGELOG.md @@ -1,6 +1,12 @@ CHANGELOG ========= +4.1.0 +----- + +* added `MissingConstructorArgumentsException` new exception for deserialization failure + of objects that needs data insertion in constructor + 4.0.0 ----- diff --git a/src/Symfony/Component/Serializer/Exception/MissingConstructorArgumentsException.php b/src/Symfony/Component/Serializer/Exception/MissingConstructorArgumentsException.php new file mode 100644 index 0000000000000..b9b768b53f5e7 --- /dev/null +++ b/src/Symfony/Component/Serializer/Exception/MissingConstructorArgumentsException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Exception; + +/** + * IncompleteInputDataException. + * + * @author Maxime VEBER + */ +class MissingConstructorArgumentsException extends RuntimeException +{ +} diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php index c43f0598a820a..b798a512942c8 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Serializer\Normalizer; use Symfony\Component\Serializer\Exception\CircularReferenceException; +use Symfony\Component\Serializer\Exception\MissingConstructorArgumentsException; use Symfony\Component\Serializer\Exception\InvalidArgumentException; use Symfony\Component\Serializer\Exception\LogicException; use Symfony\Component\Serializer\Exception\RuntimeException; @@ -308,6 +309,7 @@ protected function getConstructor(array &$data, $class, array &$context, \Reflec * @return object * * @throws RuntimeException + * @throws MissingConstructorArgumentsException */ protected function instantiateObject(array &$data, $class, array &$context, \ReflectionClass $reflectionClass, $allowedAttributes, string $format = null) { @@ -356,7 +358,7 @@ protected function instantiateObject(array &$data, $class, array &$context, \Ref } elseif ($constructorParameter->isDefaultValueAvailable()) { $params[] = $constructorParameter->getDefaultValue(); } else { - throw new RuntimeException( + throw new MissingConstructorArgumentsException( sprintf( 'Cannot create an instance of %s from serialized data because its constructor requires parameter "%s" to be present.', $class, diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php index f2d389a0df07e..fee706d9a2653 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php @@ -203,6 +203,21 @@ public function testConstructorWithUnknownObjectTypeHintDenormalize() $normalizer->denormalize($data, DummyWithConstructorInexistingObject::class); } + /** + * @expectedException \Symfony\Component\Serializer\Exception\MissingConstructorArgumentsException + * @expectedExceptionMessage Cannot create an instance of Symfony\Component\Serializer\Tests\Normalizer\DummyValueObject from serialized data because its constructor requires parameter "bar" to be present. + */ + public function testConstructorWithMissingData() + { + $data = array( + 'foo' => 10, + ); + + $normalizer = new ObjectNormalizer(); + + $normalizer->denormalize($data, DummyValueObject::class); + } + public function testGroupsNormalize() { $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); @@ -1025,6 +1040,17 @@ public function __construct($id, Unknown $unknown) { } } +class DummyValueObject +{ + private $foo; + private $bar; + + public function __construct($foo, $bar) + { + $this->foo = $foo; + $this->bar = $bar; + } +} class JsonNumber { From 7b8b16515694634928e4cda781c7b9dd83d07414 Mon Sep 17 00:00:00 2001 From: Maxime Veber Date: Thu, 14 Dec 2017 01:03:15 +0100 Subject: [PATCH 2/2] Add new feature to serializer: default_constructor_arguments You can now add empty_data to the context on deserialization of objects. This allow you to deserialize objects that have requirements in the constructor that can't be given. Basically, it helps you hydrate with value objects, so the validation component can invalid the object without the serializer send an error. --- src/Symfony/Component/Serializer/CHANGELOG.md | 2 ++ .../Normalizer/AbstractNormalizer.php | 3 +++ .../Tests/Normalizer/ObjectNormalizerTest.php | 17 +++++++++++++++++ 3 files changed, 22 insertions(+) diff --git a/src/Symfony/Component/Serializer/CHANGELOG.md b/src/Symfony/Component/Serializer/CHANGELOG.md index 7daf7ade5ab2c..e3f7fb0a14610 100644 --- a/src/Symfony/Component/Serializer/CHANGELOG.md +++ b/src/Symfony/Component/Serializer/CHANGELOG.md @@ -6,6 +6,8 @@ CHANGELOG * added `MissingConstructorArgumentsException` new exception for deserialization failure of objects that needs data insertion in constructor +* added an optional `default_constructor_arguments` option of context to specify a default data in + case the object is not initializable by its constructor because of data missing 4.0.0 ----- diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php index b798a512942c8..6442e98a1473f 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php @@ -37,6 +37,7 @@ abstract class AbstractNormalizer implements NormalizerInterface, DenormalizerIn const GROUPS = 'groups'; const ATTRIBUTES = 'attributes'; const ALLOW_EXTRA_ATTRIBUTES = 'allow_extra_attributes'; + const DEFAULT_CONSTRUCTOR_ARGUMENTS = 'default_constructor_arguments'; /** * @var int @@ -355,6 +356,8 @@ protected function instantiateObject(array &$data, $class, array &$context, \Ref // Don't run set for a parameter passed to the constructor $params[] = $parameterData; unset($data[$key]); + } elseif (isset($context[static::DEFAULT_CONSTRUCTOR_ARGUMENTS][$class][$key])) { + $params[] = $context[static::DEFAULT_CONSTRUCTOR_ARGUMENTS][$class][$key]; } elseif ($constructorParameter->isDefaultValueAvailable()) { $params[] = $constructorParameter->getDefaultValue(); } else { diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php index fee706d9a2653..68f062cd5dcc6 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php @@ -218,6 +218,23 @@ public function testConstructorWithMissingData() $normalizer->denormalize($data, DummyValueObject::class); } + public function testFillWithEmptyDataWhenMissingData() + { + $data = array( + 'foo' => 10, + ); + + $normalizer = new ObjectNormalizer(); + + $result = $normalizer->denormalize($data, DummyValueObject::class, 'json', array( + 'default_constructor_arguments' => array( + DummyValueObject::class => array('foo' => '', 'bar' => ''), + ), + )); + + $this->assertEquals(new DummyValueObject(10, ''), $result); + } + public function testGroupsNormalize() { $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));