Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 6b464b0

Browse files
Mihai Stancudunglas
Mihai Stancu
authored andcommitted
Recursive denormalize using PropertyInfo
- Refactored PR 14844 "Denormalize with typehinting" - Now using PropertyInfo to extract type information - Updated tests - Updated composer.json
1 parent 3a165e5 commit 6b464b0

File tree

4 files changed

+165
-1
lines changed

4 files changed

+165
-1
lines changed

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

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

1212
namespace Symfony\Component\Serializer\Normalizer;
1313

14+
use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface;
1415
use Symfony\Component\Serializer\Exception\CircularReferenceException;
1516
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
1617
use Symfony\Component\Serializer\Exception\RuntimeException;
@@ -68,16 +69,22 @@ abstract class AbstractNormalizer implements NormalizerInterface, DenormalizerIn
6869
*/
6970
protected $camelizedAttributes = array();
7071

72+
/**
73+
* @var PropertyInfoExtractorInterface
74+
*/
75+
protected $propertyInfoExtractor;
76+
7177
/**
7278
* Sets the {@link ClassMetadataFactoryInterface} to use.
7379
*
7480
* @param ClassMetadataFactoryInterface|null $classMetadataFactory
7581
* @param NameConverterInterface|null $nameConverter
7682
*/
77-
public function __construct(ClassMetadataFactoryInterface $classMetadataFactory = null, NameConverterInterface $nameConverter = null)
83+
public function __construct(ClassMetadataFactoryInterface $classMetadataFactory = null, NameConverterInterface $nameConverter = null, PropertyInfoExtractorInterface $propertyInfoExtractor = null)
7884
{
7985
$this->classMetadataFactory = $classMetadataFactory;
8086
$this->nameConverter = $nameConverter;
87+
$this->propertyInfoExtractor = $propertyInfoExtractor;
8188
}
8289

8390
/**
@@ -285,6 +292,11 @@ protected function instantiateObject(array &$data, $class, array &$context, \Ref
285292
return $object;
286293
}
287294

295+
$format = null;
296+
if (isset($context['format'])) {
297+
$format = $context['format'];
298+
}
299+
288300
$constructor = $reflectionClass->getConstructor();
289301
if ($constructor) {
290302
$constructorParameters = $constructor->getParameters();
@@ -305,6 +317,23 @@ protected function instantiateObject(array &$data, $class, array &$context, \Ref
305317
$params = array_merge($params, $data[$paramName]);
306318
}
307319
} 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+
308337
$params[] = $data[$key];
309338
// don't run set for a parameter passed to the constructor
310339
unset($data[$key]);

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

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,66 @@ class GetSetMethodNormalizer extends AbstractObjectNormalizer
3636
{
3737
private static $setterAccessibleCache = array();
3838

39+
/**
40+
* {@inheritdoc}
41+
*
42+
* @throws RuntimeException
43+
*/
44+
public function denormalize($data, $class, $format = null, array $context = array())
45+
{
46+
$allowedAttributes = $this->getAllowedAttributes($class, $context, true);
47+
$normalizedData = $this->prepareForDenormalization($data);
48+
49+
$reflectionClass = new \ReflectionClass($class);
50+
$subcontext = array_merge($context, array('format' => $format));
51+
$object = $this->instantiateObject($normalizedData, $class, $subcontext, $reflectionClass, $allowedAttributes);
52+
53+
$classMethods = get_class_methods($object);
54+
foreach ($normalizedData as $attribute => $value) {
55+
if ($this->nameConverter) {
56+
$attribute = $this->nameConverter->denormalize($attribute);
57+
}
58+
59+
$allowed = $allowedAttributes === false || in_array($attribute, $allowedAttributes);
60+
$ignored = in_array($attribute, $this->ignoredAttributes);
61+
62+
if ($allowed && !$ignored) {
63+
$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+
}
90+
91+
$object->$setter($value);
92+
}
93+
}
94+
}
95+
96+
return $object;
97+
}
98+
3999
/**
40100
* {@inheritdoc}
41101
*/

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

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
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;
1517
use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter;
1618
use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer;
1719
use Symfony\Component\Serializer\Serializer;
@@ -490,6 +492,24 @@ public function testNoStaticGetSetSupport()
490492
$this->assertFalse($this->normalizer->supportsNormalization(new ObjectWithJustStaticSetterDummy()));
491493
}
492494

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+
493513
public function testPrivateSetter()
494514
{
495515
$obj = $this->normalizer->denormalize(array('foo' => 'foobar'), __NAMESPACE__.'\ObjectWithPrivateSetterDummy');
@@ -758,6 +778,59 @@ public function getBar_foo()
758778
}
759779
}
760780

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+
761834
class ObjectConstructorArgsWithPrivateMutatorDummy
762835
{
763836
private $foo;

src/Symfony/Component/Serializer/composer.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"symfony/property-access": "~2.8|~3.0",
2525
"symfony/http-foundation": "~2.8|~3.0",
2626
"symfony/cache": "~3.1",
27+
"symfony/property-info": "~2.8|~3.0",
2728
"doctrine/annotations": "~1.0",
2829
"doctrine/cache": "~1.0"
2930
},
@@ -32,6 +33,7 @@
3233
},
3334
"suggest": {
3435
"psr/cache-implementation": "For using the metadata cache.",
36+
"symfony/property-info": "To harden the component and deserialize relations.",
3537
"symfony/yaml": "For using the default YAML mapping loader.",
3638
"symfony/config": "For using the XML mapping loader.",
3739
"symfony/property-access": "For using the ObjectNormalizer.",

0 commit comments

Comments
 (0)