From b3826fb0e7f48409b100b72889c60dd1e3894b64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Sun, 22 May 2016 09:54:16 +0200 Subject: [PATCH] [Serializer] Add the possibility to filter attributes --- .../Normalizer/AbstractNormalizer.php | 36 +++++++- .../Normalizer/AbstractObjectNormalizer.php | 4 +- .../Tests/Normalizer/ObjectNormalizerTest.php | 88 +++++++++++++++++++ 3 files changed, 123 insertions(+), 5 deletions(-) diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php index cd46b5b97d..f47fcf7d1f 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php @@ -236,7 +236,20 @@ abstract class AbstractNormalizer extends SerializerAwareNormalizer implements N */ protected function isAllowedAttribute($classOrObject, $attribute, $format = null, array $context = array()) { - return !in_array($attribute, $this->ignoredAttributes); + if (in_array($attribute, $this->ignoredAttributes)) { + return false; + } + + if (isset($context['attributes'][$attribute])) { + // Nested attributes + return true; + } + + if (isset($context['attributes']) && is_array($context['attributes'])) { + return in_array($attribute, $context['attributes'], true); + } + + return true; } /** @@ -324,7 +337,7 @@ abstract class AbstractNormalizer extends SerializerAwareNormalizer implements N $key = $this->nameConverter ? $this->nameConverter->normalize($paramName) : $paramName; $allowed = $allowedAttributes === false || in_array($paramName, $allowedAttributes); - $ignored = in_array($paramName, $this->ignoredAttributes); + $ignored = !$this->isAllowedAttribute($class, $paramName, $format, $context); if (method_exists($constructorParameter, 'isVariadic') && $constructorParameter->isVariadic()) { if ($allowed && !$ignored && (isset($data[$key]) || array_key_exists($key, $data))) { if (!is_array($data[$paramName])) { @@ -341,7 +354,7 @@ abstract class AbstractNormalizer extends SerializerAwareNormalizer implements N 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(), static::class)); } $parameterClass = $constructorParameter->getClass()->getName(); - $parameterData = $this->serializer->denormalize($parameterData, $parameterClass, $format, $context); + $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); @@ -372,4 +385,21 @@ abstract class AbstractNormalizer extends SerializerAwareNormalizer implements N return new $class(); } + + /** + * @param array $parentContext + * @param string $attribute + * + * @return array + * + * @internal + */ + protected function createChildContext(array $parentContext, $attribute) + { + if (isset($parentContext['attributes'][$attribute])) { + $parentContext['attributes'] = $parentContext['attributes'][$attribute]; + } + + return $parentContext; + } } diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php index 95f3b630dd..feb7394ebd 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php @@ -95,7 +95,7 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer throw new LogicException(sprintf('Cannot normalize attribute "%s" because the injected serializer is not a normalizer', $attribute)); } - $data = $this->updateData($data, $attribute, $this->serializer->normalize($attributeValue, $format, $context)); + $data = $this->updateData($data, $attribute, $this->serializer->normalize($attributeValue, $format, $this->createChildContext($context, $attribute))); } return $data; @@ -268,7 +268,7 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer } if ($this->serializer->supportsDenormalization($data, $class, $format)) { - return $this->serializer->denormalize($data, $class, $format, $context); + return $this->serializer->denormalize($data, $class, $format, $this->createChildContext($context, $attribute)); } } diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php index 0b016b829f..9a66b62e22 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php @@ -643,6 +643,70 @@ class ObjectNormalizerTest extends \PHPUnit_Framework_TestCase $this->assertSame(array('foo' => 'bar', 'bar' => 'foo'), $normalizer->normalize($data, null, array('include_foo_and_bar' => true))); } + + public function testAttributesContextNormalize() + { + $normalizer = new ObjectNormalizer(); + $serializer = new Serializer(array($normalizer)); + + $objectInner = new ObjectInner(); + $objectInner->foo = 'innerFoo'; + $objectInner->bar = 'innerBar'; + + $objectDummy = new ObjectDummy(); + $objectDummy->setFoo('foo'); + $objectDummy->setBaz(true); + $objectDummy->setObject($objectInner); + + $context = array('attributes' => array('foo', 'baz', 'object' => array('foo'))); + $this->assertEquals( + array( + 'foo' => 'foo', + 'baz' => true, + 'object' => array('foo' => 'innerFoo'), + ), + $serializer->normalize($objectDummy, null, $context) + ); + } + + public function testAttributesContextDenormalize() + { + $normalizer = new ObjectNormalizer(null, null, null, new ReflectionExtractor()); + $serializer = new Serializer(array($normalizer)); + + $objectInner = new ObjectInner(); + $objectInner->foo = 'innerFoo'; + + $objectOuter = new ObjectOuter(); + $objectOuter->bar = 'bar'; + $objectOuter->setInner($objectInner); + + $context = array('attributes' => array('bar', 'inner' => array('foo'))); + $this->assertEquals($objectOuter, $serializer->denormalize( + array( + 'foo' => 'foo', + 'bar' => 'bar', + 'date' => '2017-02-03', + 'inner' => array('foo' => 'innerFoo', 'bar' => 'innerBar'), + ), ObjectOuter::class, null, $context)); + } + + public function testAttributesContextDenormalizeConstructor() + { + $normalizer = new ObjectNormalizer(null, null, null, new ReflectionExtractor()); + $serializer = new Serializer(array($normalizer)); + + $objectInner = new ObjectInner(); + $objectInner->bar = 'bar'; + + $obj = new DummyWithConstructorObjectAndDefaultValue('a', $objectInner); + + $context = array('attributes' => array('inner' => array('bar'))); + $this->assertEquals($obj, $serializer->denormalize(array( + 'foo' => 'b', + 'inner' => array('foo' => 'foo', 'bar' => 'bar'), + ), DummyWithConstructorObjectAndDefaultValue::class, null, $context)); + } } class ObjectDummy @@ -813,6 +877,8 @@ class ObjectTypeHinted class ObjectOuter { + public $foo; + public $bar; private $inner; private $date; @@ -910,3 +976,25 @@ class JsonNumber */ public $number; } + +class DummyWithConstructorObjectAndDefaultValue +{ + private $foo; + private $inner; + + public function __construct($foo = 'a', ObjectInner $inner) + { + $this->foo = $foo; + $this->inner = $inner; + } + + public function getFoo() + { + return $this->foo; + } + + public function getInner() + { + return $this->inner; + } +}