Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 6b464b0

Browse filesBrowse 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
Copy full SHA for 6b464b0

File tree

Expand file treeCollapse file tree

4 files changed

+165
-1
lines changed
Filter options
Expand file treeCollapse file tree

4 files changed

+165
-1
lines changed

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php
+30-1Lines 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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Serializer/Normalizer/GetSetMethodNormalizer.php
+60Lines 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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Serializer/Tests/Normalizer/GetSetMethodNormalizerTest.php
+73Lines 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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Serializer/composer.json
+2Lines 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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.