Movatterモバイル変換


[0]ホーム

URL:


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

Sign up
Appearance settings

Commit114fca3

Browse files
committed
feature#17660 [Serializer] Integrate the PropertyInfo Component (recursive denormalization and hardening) (mihai-stancu, dunglas)
This PR was merged into the 3.1-dev branch.Discussion----------[Serializer] Integrate the PropertyInfo Component (recursive denormalization and hardening)| Q | A| ------------- | ---| Bug fix? | no| New feature? | yes| BC breaks? | no| Deprecations? | no| Tests pass? | yes| Fixed tickets |#16143,#17193,#14844| License | MIT| Doc PR | todoIntegrates the PropertyInfo Component in order to:* denormalize a graph of objects recursively (see tests)* harden the hydratation logicThe hardening part is interesting. Considering the following example:```phpclass Foo{ public function setDate(\DateTimeInterface $date) { }}// initialize $normalizer$normalizer->denormalize(['date' => 1234], Foo::class);```Previously, a PHP error was thrown because the type passed to the setter (an int) doesn't match the one checked with the typehint. With the PropertyInfo integration, an `UnexpectedValueExcption` is throw instead.It's especially interesting for web APIs dealing with JSON documents. For instance in API Platform, previously a 500 error was thrown, but thanks to this fix a 400 HTTP code with a descriptive error message will be returned. (/cc@csarrazi@mRoca@blazarecki, it's an alternative tohttps://github.com/dunglas/php-to-json-schema for protecting an API)./cc@mihai-stancuCommits-------5194482 [Serializer] Integrate the PropertyInfo Component6b464b0 Recursive denormalize using PropertyInfo
2 parents51d075a +5194482 commit114fca3

File tree

8 files changed

+171
-12
lines changed

8 files changed

+171
-12
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/AbstractObjectNormalizer.php‎

Lines changed: 70 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
1515
useSymfony\Component\Serializer\Exception\CircularReferenceException;
1616
useSymfony\Component\Serializer\Exception\LogicException;
1717
useSymfony\Component\Serializer\Exception\UnexpectedValueException;
18+
useSymfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
19+
useSymfony\Component\PropertyInfo\Type;
20+
useSymfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface;
21+
useSymfony\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
constENABLE_MAX_DEPTH ='enable_max_depth';
2731
constDEPTH_KEY_PATTERN ='depth_%s::%s';
2832

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

36+
publicfunction__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 ($stackas$attribute =>$attributeValue) {
7890
if (!$this->serializerinstanceof NormalizerInterface) {
79-
thrownewLogicException(sprintf('Cannot normalize attribute "%s" because injected serializer is not a normalizer',$attribute));
91+
thrownewLogicException(sprintf('Cannot normalize attribute "%s" becausetheinjected 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-
thrownewUnexpectedValueException($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+
thrownewUnexpectedValueException($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+
privatefunctionvalidateAndDenormalize($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 ($typesas$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->serializerinstanceof DenormalizerInterface) {
260+
thrownewLogicException(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+
thrownewUnexpectedValueException(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: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,40 @@ class GetSetMethodNormalizer extends AbstractObjectNormalizer
3636
{
3737
privatestatic$setterAccessibleCache =array();
3838

39+
/**
40+
* {@inheritdoc}
41+
*
42+
* @throws RuntimeException
43+
*/
44+
publicfunctiondenormalize($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+
$object =$this->instantiateObject($normalizedData,$class,$context,$reflectionClass,$allowedAttributes);
51+
52+
$classMethods =get_class_methods($object);
53+
foreach ($normalizedDataas$attribute =>$value) {
54+
if ($this->nameConverter) {
55+
$attribute =$this->nameConverter->denormalize($attribute);
56+
}
57+
58+
$allowed =$allowedAttributes ===false ||in_array($attribute,$allowedAttributes);
59+
$ignored =in_array($attribute,$this->ignoredAttributes);
60+
61+
if ($allowed && !$ignored) {
62+
$setter ='set'.ucfirst($attribute);
63+
64+
if (in_array($setter,$classMethods) && !$reflectionClass->getMethod($setter)->isStatic()) {
65+
$object->$setter($value);
66+
}
67+
}
68+
}
69+
70+
return$object;
71+
}
72+
3973
/**
4074
* {@inheritdoc}
4175
*/

‎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
useSymfony\Component\PropertyAccess\Exception\NoSuchPropertyException;
1515
useSymfony\Component\PropertyAccess\PropertyAccess;
1616
useSymfony\Component\PropertyAccess\PropertyAccessorInterface;
17+
useSymfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
1718
useSymfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface;
1819
useSymfony\Component\Serializer\NameConverter\NameConverterInterface;
1920

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

32-
publicfunction__construct(ClassMetadataFactoryInterface$classMetadataFactory =null,NameConverterInterface$nameConverter =null,PropertyAccessorInterface$propertyAccessor =null)
33+
publicfunction__construct(ClassMetadataFactoryInterface$classMetadataFactory =null,NameConverterInterface$nameConverter =null,PropertyAccessorInterface$propertyAccessor =null,PropertyTypeExtractorInterface$propertyTypeExtractor =null)
3334
{
34-
parent::__construct($classMetadataFactory,$nameConverter);
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 & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,7 @@ public function provideCallbacks()
390390

391391
/**
392392
* @expectedException \Symfony\Component\Serializer\Exception\LogicException
393-
* @expectedExceptionMessage Cannot normalize attribute "object" because injected serializer is not a normalizer
393+
* @expectedExceptionMessage Cannot normalize attribute "object" becausetheinjected serializer is not a normalizer
394394
*/
395395
publicfunctiontestUnableToNormalizeObjectAttribute()
396396
{

‎src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php‎

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@
1212
namespaceSymfony\Component\Serializer\Tests\Normalizer;
1313

1414
useDoctrine\Common\Annotations\AnnotationReader;
15+
useSymfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
16+
useSymfony\Component\Serializer\Exception\UnexpectedValueException;
1517
useSymfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter;
18+
useSymfony\Component\Serializer\Normalizer\DateTimeNormalizer;
1619
useSymfony\Component\Serializer\Normalizer\ObjectNormalizer;
1720
useSymfony\Component\Serializer\Serializer;
1821
useSymfony\Component\Serializer\SerializerInterface;
@@ -372,7 +375,7 @@ public function provideCallbacks()
372375

373376
/**
374377
* @expectedException \Symfony\Component\Serializer\Exception\LogicException
375-
* @expectedExceptionMessage Cannot normalize attribute "object" because injected serializer is not a normalizer
378+
* @expectedExceptionMessage Cannot normalize attribute "object" becausetheinjected serializer is not a normalizer
376379
*/
377380
publicfunctiontestUnableToNormalizeObjectAttribute()
378381
{
@@ -506,6 +509,29 @@ public function testThrowUnexpectedValueException()
506509
{
507510
$this->normalizer->denormalize(array('foo' =>'bar'), ObjectTypeHinted::class);
508511
}
512+
513+
publicfunctiontestDenomalizeRecursive()
514+
{
515+
$normalizer =newObjectNormalizer(null,null,null,newReflectionExtractor());
516+
$serializer =newSerializer(array(newDateTimeNormalizer(),$normalizer));
517+
518+
$obj =$serializer->denormalize(array('inner' =>array('foo' =>'foo','bar' =>'bar'),'date' =>'1988/01/21'), ObjectOuter::class);
519+
$this->assertEquals('foo',$obj->getInner()->foo);
520+
$this->assertEquals('bar',$obj->getInner()->bar);
521+
$this->assertEquals('1988-01-21',$obj->getDate()->format('Y-m-d'));
522+
}
523+
524+
/**
525+
* @expectedException UnexpectedValueException
526+
* @expectedExceptionMessage The type of the "date" attribute for class "Symfony\Component\Serializer\Tests\Normalizer\ObjectOuter" must be one of "DateTimeInterface" ("string" given).
527+
*/
528+
publicfunctiontestRejectInvalidType()
529+
{
530+
$normalizer =newObjectNormalizer(null,null,null,newReflectionExtractor());
531+
$serializer =newSerializer(array($normalizer));
532+
533+
$serializer->denormalize(array('date' =>'foo'), ObjectOuter::class);
534+
}
509535
}
510536

511537
class ObjectDummy
@@ -673,3 +699,35 @@ public function setFoo(array $f)
673699
{
674700
}
675701
}
702+
703+
class ObjectOuter
704+
{
705+
private$inner;
706+
private$date;
707+
708+
publicfunctiongetInner()
709+
{
710+
return$this->inner;
711+
}
712+
713+
publicfunctionsetInner(ObjectInner$inner)
714+
{
715+
$this->inner =$inner;
716+
}
717+
718+
publicfunctionsetDate(\DateTimeInterface$date)
719+
{
720+
$this->date =$date;
721+
}
722+
723+
publicfunctiongetDate()
724+
{
725+
return$this->date;
726+
}
727+
}
728+
729+
class ObjectInner
730+
{
731+
public$foo;
732+
public$bar;
733+
}

‎src/Symfony/Component/Serializer/Tests/Normalizer/PropertyNormalizerTest.php‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,7 @@ public function testDenormalizeShouldIgnoreStaticProperty()
349349

350350
/**
351351
* @expectedException \Symfony\Component\Serializer\Exception\LogicException
352-
* @expectedExceptionMessage Cannot normalize attribute "bar" because injected serializer is not a normalizer
352+
* @expectedExceptionMessage Cannot normalize attribute "bar" becausetheinjected serializer is not a normalizer
353353
*/
354354
publicfunctiontestUnableToNormalizeObjectAttribute()
355355
{

‎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 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)

[8]ページ先頭

©2009-2025 Movatter.jp