From 585e86be3c60f38552beb7e95250db292c350a8f Mon Sep 17 00:00:00 2001 From: jewome62 Date: Mon, 18 Mar 2019 22:44:06 +0100 Subject: [PATCH] Add deep populate option #21669 --- src/Symfony/Component/Serializer/CHANGELOG.md | 1 + .../Normalizer/AbstractObjectNormalizer.php | 9 ++++ .../Normalizer/ObjectToPopulateTrait.php | 2 +- .../Fixtures/DeepObjectPopulateChildDummy.php | 22 +++++++++ .../DeepObjectPopulateParentDummy.php | 33 +++++++++++++ .../AbstractObjectNormalizerTest.php | 48 +++++++++++++++++++ 6 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 src/Symfony/Component/Serializer/Tests/Fixtures/DeepObjectPopulateChildDummy.php create mode 100644 src/Symfony/Component/Serializer/Tests/Fixtures/DeepObjectPopulateParentDummy.php diff --git a/src/Symfony/Component/Serializer/CHANGELOG.md b/src/Symfony/Component/Serializer/CHANGELOG.md index 57e4ed1fd7c2a..de2099fb1c951 100644 --- a/src/Symfony/Component/Serializer/CHANGELOG.md +++ b/src/Symfony/Component/Serializer/CHANGELOG.md @@ -5,6 +5,7 @@ CHANGELOG ----- * added the list of constraint violations' parameters in `ConstraintViolationListNormalizer` + * added a `deep_object_to_populate` context option to recursive denormalize on `object_to_populate` object. 4.2.0 ----- diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php index b17d1e8c59e10..599f503e42421 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Serializer\Normalizer; use Symfony\Component\PropertyAccess\Exception\InvalidArgumentException; +use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException; use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface; use Symfony\Component\PropertyInfo\Type; use Symfony\Component\Serializer\Encoder\JsonEncoder; @@ -38,6 +39,7 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer const SKIP_NULL_VALUES = 'skip_null_values'; const MAX_DEPTH_HANDLER = 'max_depth_handler'; const EXCLUDE_FROM_CACHE_KEY = 'exclude_from_cache_key'; + const DEEP_OBJECT_TO_POPULATE = 'deep_object_to_populate'; private $propertyTypeExtractor; private $typesCache = []; @@ -274,6 +276,13 @@ public function denormalize($data, $class, $format = null, array $context = []) continue; } + if ($context[self::DEEP_OBJECT_TO_POPULATE] ?? $this->defaultContext[self::DEEP_OBJECT_TO_POPULATE] ?? false) { + try { + $context[self::OBJECT_TO_POPULATE] = $this->getAttributeValue($object, $attribute, $format, $context); + } catch (NoSuchPropertyException $e) { + } + } + $value = $this->validateAndDenormalize($class, $attribute, $value, $format, $context); try { $this->setAttributeValue($object, $attribute, $value, $format, $context); diff --git a/src/Symfony/Component/Serializer/Normalizer/ObjectToPopulateTrait.php b/src/Symfony/Component/Serializer/Normalizer/ObjectToPopulateTrait.php index 7150a6e6ee383..ed45d6a0c41dd 100644 --- a/src/Symfony/Component/Serializer/Normalizer/ObjectToPopulateTrait.php +++ b/src/Symfony/Component/Serializer/Normalizer/ObjectToPopulateTrait.php @@ -26,7 +26,7 @@ trait ObjectToPopulateTrait */ protected function extractObjectToPopulate($class, array $context, $key = null) { - $key = $key ?: 'object_to_populate'; + $key = $key ?: AbstractNormalizer::OBJECT_TO_POPULATE; if (isset($context[$key]) && \is_object($context[$key]) && $context[$key] instanceof $class) { return $context[$key]; diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/DeepObjectPopulateChildDummy.php b/src/Symfony/Component/Serializer/Tests/Fixtures/DeepObjectPopulateChildDummy.php new file mode 100644 index 0000000000000..339b25be3566a --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/DeepObjectPopulateChildDummy.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Fixtures; + +/** + * @author Jérôme Desjardin + */ +class DeepObjectPopulateChildDummy +{ + public $foo; + + public $bar; +} diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/DeepObjectPopulateParentDummy.php b/src/Symfony/Component/Serializer/Tests/Fixtures/DeepObjectPopulateParentDummy.php new file mode 100644 index 0000000000000..4e65ab4307d74 --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/DeepObjectPopulateParentDummy.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Fixtures; + +/** + * @author Jérôme Desjardin + */ +class DeepObjectPopulateParentDummy +{ + /** + * @var DeepObjectPopulateChildDummy|null + */ + private $child; + + public function setChild(?DeepObjectPopulateChildDummy $child) + { + $this->child = $child; + } + + public function getChild(): ?DeepObjectPopulateChildDummy + { + return $this->child; + } +} diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php index a45009d2330d5..9176f7f82a87d 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -23,6 +23,8 @@ use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; use Symfony\Component\Serializer\SerializerAwareInterface; use Symfony\Component\Serializer\SerializerInterface; +use Symfony\Component\Serializer\Tests\Fixtures\DeepObjectPopulateChildDummy; +use Symfony\Component\Serializer\Tests\Fixtures\DeepObjectPopulateParentDummy; class AbstractObjectNormalizerTest extends TestCase { @@ -171,6 +173,48 @@ public function testSkipNullValues() $result = $normalizer->normalize($dummy, null, [AbstractObjectNormalizer::SKIP_NULL_VALUES => true]); $this->assertSame(['bar' => 'present'], $result); } + + public function testDeepObjectToPopulate() + { + $child = new DeepObjectPopulateChildDummy(); + $child->bar = 'bar-old'; + $child->foo = 'foo-old'; + + $parent = new DeepObjectPopulateParentDummy(); + $parent->setChild($child); + + $context = [ + AbstractObjectNormalizer::OBJECT_TO_POPULATE => $parent, + AbstractObjectNormalizer::DEEP_OBJECT_TO_POPULATE => true, + ]; + + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $normalizer = new ObjectNormalizer($classMetadataFactory, null, null, new PhpDocExtractor()); + + $newChild = new DeepObjectPopulateChildDummy(); + $newChild->bar = 'bar-new'; + $newChild->foo = 'foo-old'; + + $serializer = $this->getMockBuilder(__NAMESPACE__.'\ObjectSerializerDenormalizer')->getMock(); + $serializer + ->method('supportsDenormalization') + ->with($this->arrayHasKey('bar'), + $this->equalTo(DeepObjectPopulateChildDummy::class), + $this->isNull(), + $this->contains($child)) + ->willReturn(true); + $serializer->method('denormalize')->willReturn($newChild); + + $normalizer->setSerializer($serializer); + $normalizer->denormalize([ + 'child' => [ + 'bar' => 'bar-new', + ], + ], 'Symfony\Component\Serializer\Tests\Fixtures\DeepObjectPopulateParentDummy', null, $context); + + $this->assertSame('bar-new', $parent->getChild()->bar); + $this->assertSame('foo-old', $parent->getChild()->foo); + } } class AbstractObjectNormalizerDummy extends AbstractObjectNormalizer @@ -348,3 +392,7 @@ public function setSerializer(SerializerInterface $serializer) $this->serializer = $serializer; } } + +abstract class ObjectSerializerDenormalizer implements SerializerInterface, DenormalizerInterface +{ +}