8000 [Serializer] Integrate the PropertyInfo Component · symfony/symfony@5194482 · GitHub
[go: up one dir, main page]

Skip to content

Commit 5194482

Browse files
committed
[Serializer] Integrate the PropertyInfo Component
Recursive denormalization handling and hardening.
1 parent 6b464b0 commit 5194482

9 files changed

+139
-144
lines changed

src/Symfony/Component/Serializer/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ CHANGELOG
1616
* added support for serializing objects that implement `DateTimeInterface`
1717
* added `AbstractObjectNormalizer` as a base class for normalizers that deal
1818
with objects
19+
* added support to relation deserialization
1920

2021
2.7.0
2122
-----

src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php

Lines changed: 1 addition & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111

1212
namespace Symfony\Component\Serializer\Normalizer;
1313

14-
use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface;
1514
use Symfony\Component\Serializer\Exception\CircularReferenceException;
1615
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
1716
use Symfony\Component\Serializer\Exception\RuntimeException;
@@ -69,22 +68,16 @@ abstract class AbstractNormalizer implements NormalizerInterface, DenormalizerIn
6968
*/
7069
protected $camelizedAttributes = array();
7170

72-
/**
73-
* @var PropertyInfoExtractorInterface
74-
*/
75-
protected $propertyInfoExtractor;
76-
7771
/**
7872
* Sets the {@link ClassMetadataFactoryInterface} to use.
7973
*
8074
* @param ClassMetadataFactoryInterface|null $classMetadataFactory
8175
* @param NameConverterInterface|null $nameConverter
8276
*/
83-
public function __construct(ClassMetadataFactoryInterface $classMetadataFactory A93C = null, NameConverterInterface $nameConverter = null, PropertyInfoExtractorInterface $propertyInfoExtractor = null)
77+
public function __construct(ClassMetadataFactoryInterface $classMetadataFactory = null, NameConverterInterface $nameConverter = null)
8478
{
8579
$this->classMetadataFactory = $classMetadataFactory;
8680
$this->nameConverter = $nameConverter;
87-
$this->propertyInfoExtractor = $propertyInfoExtractor;
8881
}
8982

9083
/**
@@ -292,11 +285,6 @@ protected function instantiateObject(array &$data, $class, array &$context, \Ref
292285
return $object;
293286
}
294287

295-
$format = null;
296-
if (isset($context['format'])) {
297-
$format = $context['format'];
298-
}
299-
300288
$constructor = $reflectionClass->getConstructor();
301289
if ($constructor) {
302290
$constructorParameters = $constructor->getParameters();
@@ -317,23 +305,6 @@ protected function instantiateObject(array &$data, $class, array &$context, \Ref
317305
$params = array_merge($params, $data[$paramName]);
318306
}
319307
} elseif ($allowed && !$ignored && (isset($data[$key]) || array_key_exists($key, $data))) {
320-
if ($this->propertyInfoExtractor) {
321-
$types = $this->propertyInfoExtractor->getTypes($class, $key);
322-
323-
foreach ($types as $type) {
324-
if ($type && $type->getClassName() && (!empty($data[$key]) || !$type->isNullable())) {
325-
if (!$this->serializer instanceof DenormalizerInterface) {
326-
throw new RuntimeException(sprintf('Cannot denormalize attribute "%s" because injected serializer is not a denormalizer', $key));
327-
}
328-
329-
$value = $data[$paramName];
330-
$data[$paramName] = $this->serializer->denormalize($value, $type->getClassName(), $format, $context);
331-
332-
break;
333-
}
334-
}
335-
}
336-
337308
$params[] = $data[$key];
338309
// don't run set for a parameter passed to the constructor
339310
unset($data[$key]);

src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php

Lines changed: 70 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
1515
use Symfony\Component\Serializer\Exception\CircularReferenceException;
1616
use Symfony\Component\Serializer\Exception\LogicException;
1717
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
18+
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
19+
use Symfony\Component\PropertyInfo\Type;
20+
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface;
21+
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
1822

1923
/**
2024
* Base class for a normalizer dealing with objects.
@@ -26,8 +30,16 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer
2630
const ENABLE_MAX_DEPTH = 'enable_max_depth';
2731
const DEPTH_KEY_PATTERN = 'depth_%s::%s';
2832

33+
private $propertyTypeExtractor;
2934
private $attributesCache = array();
3035

36+
public function __construct(ClassMetadataFactoryInterface $classMetadataFactory = null, NameConverterInterface $nameConverter = null, PropertyTypeExtractorInterface $propertyTypeExtractor = null)
37+
{
38+
parent::__construct($classMetadataFactory, $nameConverter);
39+
40+
$this->propertyTypeExtractor = $propertyTypeExtractor;
41+
}
42+
3143
/**
3244
* {@inheritdoc}
3345
*/
@@ -76,7 +88,7 @@ public function normalize($object, $format = null, array $context = array())
7688

7789
foreach ($stack as $attribute => $attributeValue) {
7890
if (!$this->serializer instanceof NormalizerInterface) {
79-
throw new LogicException(sprintf('Cannot normalize attribute "%s" because injected serializer is not a normalizer', $attribute));
91+
throw new LogicException(sprintf('Cannot normalize attribute "%s" because the injected serializer is not a normalizer', $attribute));
8092
}
8193

8294
$data = $this->updateData($data, $attribute, $this->serializer->normalize($attributeValue, $format, $context));
@@ -173,12 +185,15 @@ public function denormalize($data, $class, $format = null, array $context = arra
173185
$allowed = $allowedAttributes === false || in_array($attribute, $allowedAttributes);
174186
$ignored = in_array($attribute, $this->ignoredAttributes);
175187

176-
if ($allowed && !$ignored) {
177-
try {
178-
$this->setAttributeValue($object, $attribute, $value, $format, $context);
179-
} catch (InvalidArgumentException $e) {
180-
throw new UnexpectedValueException($e->getMessage(), $e->getCode(), $e);
181-
}
188+
if (!$allowed || $ignored) {
189+
continue;
190+
}
191+
192+
$value = $this->validateAndDenormalize($class, $attribute, $value, $format, $context);
193+
try {
194+
$this->setAttributeValue($object, $attribute, $value, $format, $context);
195+
} catch (InvalidArgumentException $e) {
196+
throw new UnexpectedValueException($e->getMessage(), $e->getCode(), $e);
182197
}
183198
}
184199

@@ -210,6 +225,54 @@ protected function isAttributeToNormalize($object, $attributeName, &$context)
210225
return !in_array($attributeName, $this->ignoredAttributes) && !$this->isMaxDepthReached(get_class($object), $attributeName, $context);
211226
}
212227

228+
/**
229+
* Validates the submitted data and denormalizes it.
230+
*
231+
* @param string $currentClass
232+
* @param string $attribute
233+
* @param mixed $data
234+
* @param string|null $format
235+
* @param array $context
236+
*
237+
* @return mixed
238+
*
239+
* @throws UnexpectedValueException
240+
* @throws LogicException
241+
*/
242+
private function validateAndDenormalize($currentClass, $attribute, $data, $format, array $context)
243+
{
244+
if (null === $this->propertyTypeExtractor || null === $types = $this->propertyTypeExtractor->getTypes($currentClass, $attribute)){
245+
return $data;
246+
}
247+
248+
$expectedTypes = array();
249+
foreach ($types as $type) {
250+
if (null === $data && $type->isNullable()) {
251+
return;
252+
}
253+
254+
$builtinType = $type->getBuiltinType();
255+
$class = $type->getClassName();
256+
$expectedTypes[Type::BUILTIN_TYPE_OBJECT === $builtinType && $class ? $class : $builtinType] = true;
257+
258+
if (Type::BUILTIN_TYPE_OBJECT === $builtinType) {
259+
if (!$this->serializer instanceof DenormalizerInterface) {
260+
throw new LogicException(sprintf('Cannot denormalize attribute "%s" for class "%s" because injected serializer is not a denormalizer', $attribute, $class));
261+
}
262+
263+
if ($this->serializer->supportsDenormalization($data, $class, $format)) {
264+
return $this->serializer->denormalize($data, $class, $format, $context);
265+
}
266+
}
267+
268+
if (call_user_func('is_'.$builtinType, $data)) {
269+
return $data;
270+
}
271+
}
272+
273+
throw new UnexpectedValueException(sprintf('The type of the "%s" attribute for class "%s" must be one of "%s" ("%s" given).', $attribute, $currentClass, implode('", "', array_keys($expectedTypes)), gettype($data)));
274+
}
275+
213276
/**
214277
* Sets an attribute and apply the name converter if necessary.
215278
*

src/Symfony/Component/Serializer/Normalizer/GetSetMethodNormalizer.php

Lines changed: 2 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,7 @@ public function denormalize($data, $class, $format = null, array $context = arra
4747
$normalizedData = $this->prepareForDenormalization($data);
4848

4949
$reflectionClass = new \ReflectionClass($class);
50-
$subcontext = array_merge($context, array('format' => $format));
51-
$object = $this->instantiateObject($normalizedData, $class, $subcontext, $reflectionClass, $allowedAttributes);
50+
$object = $this->instantiateObject($normalizedData, $class, $context, $reflectionClass, $allowedAttributes);
5251

5352
$classMethods = get_class_methods($object);
5453
foreach ($normalizedData as $attribute => $value) {
@@ -61,33 +60,8 @@ public function denormalize($data, $class, $format = null, array $context = arra
6160

6261
if ($allowed && !$ignored) {
6362
$setter = 'set'.ucfirst($attribute);
64-
if (in_array($setter, $classMethods) && !$reflectionClass->getMethod($setter)->isStatic()) {
65-
if ($this->propertyInfoExtractor) {
66-
$types = (array) $this->propertyInfoExtractor->getTypes($class, $attribute);
67-
68-
foreach ($types as $type) {
69-
if ($type && (!empty($value) || !$type->isNullable())) {
70-
if (!$this->serializer instanceof DenormalizerInterface) {
71-
throw new RuntimeException(
72-
sprintf(
73-
'Cannot denormalize attribute "%s" because injected serializer is not a denormalizer',
74-
$attribute
75-
)
76-
);
77-
}
78-
79-
$value = $this->serializer->denormalize(
80-
$value,
81-
$type->getClassName(),
82-
$format,
83-
$context
84-
);
85-
86-
break;
87-
}
88-
}
89-
}
9063

64+
if (in_array($setter, $classMethods) && !$reflectionClass->getMethod($setter)->isStatic()) {
9165
$object->$setter($value);
9266
}
9367
}

src/Symfony/Component/Serializer/Normalizer/ObjectNormalizer.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException;
1515
use Symfony\Component\PropertyAccess\PropertyAccess;
1616
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
17+
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
1718
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface;
1819
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
1920

@@ -29,9 +30,9 @@ class ObjectNormalizer extends AbstractObjectNormalizer
2930
*/
3031
protected $propertyAccessor;
3132

32-
public function __construct(ClassMetadataFactoryInterface $classMetadataFactory = null, NameConverterInterface $nameConverter = null, PropertyAccessorInterface $propertyAccessor = null)
33+
public function __construct(ClassMetadataFactoryInterface $classMetadataFactory = null, NameConverterInterface $nameConverter = null, PropertyAccessorInterface $propertyAccessor = null, PropertyTypeExtractorInterface $propertyTypeExtractor = null)
3334
{
34-
parent::__construct($classMetadataFactory, $nameConverter);
3D22
35+
parent::__construct($classMetadataFactory, $nameConverter, $propertyTypeExtractor);
3536

3637
$this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor();
3738
}

src/Symfony/Component/Serializer/Tests/Normalizer/GetSetMethodNormalizerTest.php

Lines changed: 1 addition & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@
1212
namespace Symfony\Component\Serializer\Tests\Normalizer;
1313

1414
use Doctrine\Common\Annotations\AnnotationReader;
15-
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
16-
use Symfony\Component\PropertyInfo\PropertyInfoExtractor;
1715
use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter;
1816
use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer;
1917
use Symfony\Component\Serializer\Serializer;
@@ -392,7 +390,7 @@ public function provideCallbacks()
392390

393391
/**
394392
* @expectedException \Symfony\Component\Serializer\Exception\LogicException
395-
* @expectedExceptionMessage Cannot normalize attribute "object" because injected serializer is not a normalizer
393+
* @expectedExceptionMessage Cannot normalize attribute "object" because the injected serializer is not a normalizer
396394
*/
397395
public function testUnableToNormalizeObjectAttribute()
398396
{
@@ -492,24 +490,6 @@ public function testNoStaticGetSetSupport()
492490
$this->assertFalse($this->normalizer->supportsNormalization(new ObjectWithJustStaticSetterDummy()));
493491
}
494492

495-
public function testDenormalizeWithTypehint()
496-
{
497-
/* need a serializer that can recurse denormalization $normalizer */
498-
$normalizer = new GetSetMethodNormalizer(null, null, new PropertyInfoExtractor(array(), array(new ReflectionExtractor())));
499-
$serializer = new Serializer(array($normalizer));
500-
$normalizer->setSerializer($serializer);
501-
502-
$obj = $normalizer->denormalize(
503-
array(
504-
'object' => array('foo' => 'foo', 'bar' => 'bar'),
505-
),
506-
__NAMESPACE__.'\GetTypehintedDummy',
507-
'any'
508-
);
509-
$this->assertEquals('foo', $obj->getObject()->getFoo());
510-
$this->assertEquals('bar', $obj->getObject()->getBar());
511-
}
512-
513493
public function testPrivateSetter()
514494
{
515495
$obj = $this->normalizer->denormalize(array('foo' => 'foobar'), __NAMESPACE__.'\ObjectWithPrivateSetterDummy');
@@ -778,59 +758,6 @@ public function getBar_foo()
778758
}
779759
}
780760

781-
class GetTypehintedDummy
782-
{
783-
protected $object;
784-
785-
public function getObject()
786-
{
787-
return $this->object;
788-
}
789-
790-
public function setObject(GetTypehintDummy $object)
791-
{
792-
$this->object = $object;
793-
}
794-
}
795-
796-
class GetTypehintDummy
797-
{
798-
protected $foo;
799-
protected $bar;
800-
801-
/**
802-
* @return mixed
803-
*/
804-
public function getFoo()
805-
{
806-
return $this->foo;
807-
}
808-
809-
/**
810-
* @param mixed $foo
811-
*/
812-
public function setFoo($foo)
813-
{
814-
$this->foo = $foo;
815-
}
816-
817-
/**
818-
* @return mixed
819-
*/
820-
public function getBar()
821-
{
822-
return $this->bar;
823-
}
824-
825-
/**
826-
* @param mixed $bar
827-
*/
828-
public function setBar($bar)
829-
{
830-
$this->bar = $bar;
831-
}
832-
}
833-
834761
class ObjectConstructorArgsWithPrivateMutatorDummy
835762
{
836763
private $foo;

0 commit comments

Comments
 (0)
0