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

Commitcb066b3

Browse files
committed
Add support for union collection value types inArrayDenormalizer
1 parentba7f78a commitcb066b3

File tree

6 files changed

+162
-80
lines changed

6 files changed

+162
-80
lines changed

‎src/Symfony/Component/Serializer/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ CHANGELOG
1111
* JsonDecode: Add`json_decode_detailed_errors` option
1212
* Make`ProblemNormalizer` give details about Messenger's`ValidationFailedException`
1313
* Add`XmlEncoder::CDATA_WRAPPING` context option
14+
* Add support for union collection value types in`ArrayDenormalizer`
1415

1516
6.3
1617
---

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

Lines changed: 21 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,6 @@ public function __construct(
139139
}
140140

141141
/**
142-
* @param array $context
143-
*
144142
* @return bool
145143
*/
146144
publicfunctionsupportsNormalization(mixed$data,string$format =null/* , array $context = [] */)
@@ -290,24 +288,17 @@ abstract protected function extractAttributes(object $object, string $format = n
290288

291289
/**
292290
* Gets the attribute value.
293-
*
294-
* @return mixed
295291
*/
296292
abstractprotectedfunctiongetAttributeValue(object$object,string$attribute,string$format =null,array$context = []);
297293

298294
/**
299-
* @param array $context
300-
*
301295
* @return bool
302296
*/
303297
publicfunctionsupportsDenormalization(mixed$data,string$type,string$format =null/* , array $context = [] */)
304298
{
305299
returnclass_exists($type) || (interface_exists($type,false) &&null !==$this->classDiscriminatorResolver?->getMappingForClass($type));
306300
}
307301

308-
/**
309-
* @return mixed
310-
*/
311302
publicfunctiondenormalize(mixed$data,string$type,string$format =null,array$context = [])
312303
{
313304
if (!isset($context['cache_key'])) {
@@ -439,11 +430,9 @@ private function validateAndDenormalize(array $types, string $currentClass, stri
439430
returnnull;
440431
}
441432

442-
$collectionValueType =$type->isCollection() ?$type->getCollectionValueTypes()[0] ??null :null;
443-
444433
// Fix a collection that contains the only one element
445434
// This is special to xml format only
446-
if ('xml' ===$format &&null !==$collectionValueType && (!\is_array($data) || !\is_int(key($data)))) {
435+
if ('xml' ===$format &&\count($type->getCollectionValueTypes()) >0 && (!\is_array($data) || !\is_int(key($data)))) {
447436
$data = [$data];
448437
}
449438

@@ -499,41 +488,33 @@ private function validateAndDenormalize(array $types, string $currentClass, stri
499488
}
500489
}
501490

502-
if (null !==$collectionValueType && Type::BUILTIN_TYPE_OBJECT ===$collectionValueType->getBuiltinType()) {
503-
$builtinType = Type::BUILTIN_TYPE_OBJECT;
504-
$class =$collectionValueType->getClassName().'[]';
491+
$builtinType =$type->getBuiltinType();
492+
$class =$type->getClassName();
505493

506-
if (\count($collectionKeyType =$type->getCollectionKeyTypes()) >0) {
507-
$context['key_type'] =\count($collectionKeyType) >1 ?$collectionKeyType :$collectionKeyType[0];
508-
}
509-
510-
$context['value_type'] =$collectionValueType;
511-
}elseif ($type->isCollection() &&\count($collectionValueType =$type->getCollectionValueTypes()) >0 && Type::BUILTIN_TYPE_ARRAY ===$collectionValueType[0]->getBuiltinType()) {
512-
// get inner type for any nested array
513-
[$innerType] =$collectionValueType;
514-
515-
// note that it will break for any other builtinType
516-
$dimensions ='[]';
517-
while (\count($innerType->getCollectionValueTypes()) >0 && Type::BUILTIN_TYPE_ARRAY ===$innerType->getBuiltinType()) {
518-
$dimensions .='[]';
494+
$innerType =$type;
495+
if ($type->isCollection() &&\count($type->getCollectionValueTypes()) >0) {
496+
while (1 ===\count($innerType->getCollectionValueTypes()) && Type::BUILTIN_TYPE_ARRAY ===$innerType->getCollectionValueTypes()[0]->getBuiltinType()) {
519497
[$innerType] =$innerType->getCollectionValueTypes();
520498
}
521499

522-
if (null !==$innerType->getClassName()) {
523-
// the builtinType is the inner one and the class is the class followed by []...[]
524-
$builtinType =$innerType->getBuiltinType();
525-
$class =$innerType->getClassName().$dimensions;
526-
}else {
527-
// default fallback (keep it as array)
528-
$builtinType =$type->getBuiltinType();
529-
$class =$type->getClassName();
500+
$dimensions ='';
501+
$arrayType =$type;
502+
do {
503+
$dimensions .='[]';
504+
[$arrayType] =$arrayType->getCollectionValueTypes();
505+
}while (\count($arrayType->getCollectionValueTypes()) >0 && Type::BUILTIN_TYPE_ARRAY ===$arrayType->getBuiltinType());
506+
507+
if (\count($innerType->getCollectionValueTypes()) >1 ||\in_array($innerType->getCollectionValueTypes()[0]->getBuiltinType(), [Type::BUILTIN_TYPE_OBJECT, Type::BUILTIN_TYPE_ARRAY],true)) {
508+
$builtinType = Type::BUILTIN_TYPE_OBJECT;
509+
$class =$arrayType->getClassName().$dimensions;
510+
$context['value_type'] =$type;
511+
$expectedTypes['array<'.implode('|',array_map(fn (Type$t) =>$t->getClassName() ??$t->getBuiltinType(),$innerType->getCollectionValueTypes())).'>'] =true;
530512
}
531-
}else {
532-
$builtinType =$type->getBuiltinType();
533-
$class =$type->getClassName();
534513
}
535514

536-
$expectedTypes[Type::BUILTIN_TYPE_OBJECT ===$builtinType &&$class ?$class :$builtinType] =true;
515+
if (!str_ends_with($class,'[]')) {
516+
$expectedTypes[Type::BUILTIN_TYPE_OBJECT ===$builtinType &&$class ?$class :$builtinType] =true;
517+
}
537518

538519
if (Type::BUILTIN_TYPE_OBJECT ===$builtinType) {
539520
if (!$this->serializerinstanceof DenormalizerInterface) {

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

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@
1313

1414
useSymfony\Component\PropertyInfo\Type;
1515
useSymfony\Component\Serializer\Exception\BadMethodCallException;
16+
useSymfony\Component\Serializer\Exception\ExtraAttributesException;
1617
useSymfony\Component\Serializer\Exception\InvalidArgumentException;
18+
useSymfony\Component\Serializer\Exception\MissingConstructorArgumentsException;
1719
useSymfony\Component\Serializer\Exception\NotNormalizableValueException;
1820

1921
/**
@@ -50,25 +52,58 @@ public function denormalize(mixed $data, string $type, string $format = null, ar
5052
thrownewBadMethodCallException('Please set a denormalizer before calling denormalize()!');
5153
}
5254
if (!\is_array($data)) {
53-
throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('Data expected to be "%s", "%s" given.',$type,get_debug_type($data)),$data, [Type::BUILTIN_TYPE_ARRAY],$context['deserialization_path'] ??null);
55+
$valueType =$context['value_type'] ??null;
56+
$expected =$valueType ?'array<'.implode('|',array_map(fn (Type$type) =>$type->getClassName() ??$type->getBuiltinType(),$valueType->getCollectionValueTypes())).'>' :$type;
57+
58+
throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('Data expected to be "%s", "%s" given.',$expected,get_debug_type($data)),$data, [Type::BUILTIN_TYPE_ARRAY],$context['deserialization_path'] ??null);
5459
}
5560
if (!str_ends_with($type,'[]')) {
5661
thrownewInvalidArgumentException('Unsupported class:'.$type);
5762
}
5863

5964
$type =substr($type,0, -2);
65+
$valueType =$context['value_type'] ??null;
6066

61-
$builtinTypes =array_map(staticfunction (Type$keyType) {
62-
return$keyType->getBuiltinType();
63-
},\is_array($keyType =$context['key_type'] ?? []) ?$keyType : [$keyType]);
67+
if ($valueTypeinstanceof Type &&\count($keyTypes =$valueType->getCollectionKeyTypes()) >0) {
68+
$builtinTypes =array_map(staticfn (Type$keyType) =>$keyType->getBuiltinType(),$keyTypes);
69+
}else {
70+
$builtinTypes =array_map(staticfn (Type$keyType) =>$keyType->getBuiltinType(),\is_array($keyType =$context['key_type'] ?? []) ?$keyType : [$keyType]);
71+
}
6472

6573
foreach ($dataas$key =>$value) {
6674
$subContext =$context;
6775
$subContext['deserialization_path'] = ($context['deserialization_path'] ??false) ?sprintf('%s[%s]',$context['deserialization_path'],$key) :"[$key]";
6876

6977
$this->validateKeyType($builtinTypes,$key,$subContext['deserialization_path']);
7078

71-
$data[$key] =$this->denormalizer->denormalize($value,$type,$format,$subContext);
79+
if ($valueTypeinstanceof Type) {
80+
foreach ($valueType->getCollectionValueTypes()as$subtype) {
81+
try {
82+
$subContext['value_type'] =$subtype;
83+
84+
if ($subtype->isNullable() &&null ===$value) {
85+
$data[$key] =null;
86+
87+
continue2;
88+
}
89+
90+
if (Type::BUILTIN_TYPE_ARRAY ===$subtype->getBuiltinType()) {
91+
$class =$type;
92+
}else {
93+
$class =$subtype->getClassName() ??$subtype->getBuiltinType();
94+
}
95+
96+
$data[$key] =$this->denormalizer->denormalize($value,$class,$format,$subContext);
97+
98+
continue2;
99+
}catch (NotNormalizableValueException|InvalidArgumentException|ExtraAttributesException|MissingConstructorArgumentsException$e) {
100+
}
101+
}
102+
103+
throw$e;
104+
}else {
105+
$data[$key] =$this->denormalizer->denormalize($value,$type,$format,$subContext);
106+
}
72107
}
73108

74109
return$data;
@@ -94,9 +129,6 @@ public function hasCacheableSupportsMethod(): bool
94129
return$this->denormalizerinstanceof CacheableSupportsMethodInterface &&$this->denormalizer->hasCacheableSupportsMethod();
95130
}
96131

97-
/**
98-
* @param mixed $key
99-
*/
100132
privatefunctionvalidateKeyType(array$builtinTypes,$key,string$path):void
101133
{
102134
if (!$builtinTypes) {

‎src/Symfony/Component/Serializer/Tests/Mapping/Loader/AnnotationLoaderWithDoctrineAnnotationsTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ class AnnotationLoaderWithDoctrineAnnotationsTest extends AnnotationLoaderTestCa
2424

2525
protectedfunctionsetUp():void
2626
{
27-
$this->expectDeprecation('Since symfony/validator 6.4: Passing a "Doctrine\Common\Annotations\AnnotationReader" instance as argument 1 to "Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader::__construct()" is deprecated, pass null or omit the parameter instead.');
27+
$this->expectDeprecation('Since symfony/serializer 6.4: Passing a "Doctrine\Common\Annotations\AnnotationReader" instance as argument 1 to "Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader::__construct()" is deprecated, pass null or omit the parameter instead.');
2828

2929
parent::setUp();
3030
}

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

Lines changed: 98 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313

1414
usePHPUnit\Framework\MockObject\MockObject;
1515
usePHPUnit\Framework\TestCase;
16+
useSymfony\Component\PropertyInfo\Type;
17+
useSymfony\Component\Serializer\Exception\NotNormalizableValueException;
1618
useSymfony\Component\Serializer\Normalizer\ArrayDenormalizer;
1719
useSymfony\Component\Serializer\Tests\Fixtures\UpcomingDenormalizerInterfaceasDenormalizerInterface;
1820

@@ -28,38 +30,26 @@ protected function setUp(): void
2830
$this->denormalizer->setDenormalizer($this->serializer);
2931
}
3032

31-
publicfunctiontestDenormalize()
33+
/**
34+
* @dataProvider getTestArrays
35+
*/
36+
publicfunctiontestDenormalize(array$input,array$expected,string$type,string$format,array$context = [])
3237
{
33-
$series = [
34-
[[['foo' =>'one','bar' =>'two']],newArrayDummy('one','two')],
35-
[[['foo' =>'three','bar' =>'four']],newArrayDummy('three','four')],
36-
];
37-
38-
$this->serializer->expects($this->exactly(2))
38+
$this->serializer->expects($this->atLeastOnce())
3939
->method('denormalize')
40-
->willReturnCallback(function ($data)use (&$series) {
41-
[$expectedArgs,$return] =array_shift($series);
42-
$this->assertSame($expectedArgs, [$data]);
43-
44-
return$return;
45-
})
46-
;
47-
48-
$result =$this->denormalizer->denormalize(
49-
[
50-
['foo' =>'one','bar' =>'two'],
51-
['foo' =>'three','bar' =>'four'],
52-
],
53-
__NAMESPACE__.'\ArrayDummy[]'
54-
);
55-
56-
$this->assertEquals(
57-
[
58-
newArrayDummy('one','two'),
59-
newArrayDummy('three','four'),
60-
],
61-
$result
62-
);
40+
->willReturnCallback(function ($data,$type,$format,$context)use ($input) {
41+
$key = (int)trim($context['deserialization_path'],'[]');
42+
$expected =$input[$key];
43+
$this->assertSame($expected,$data);
44+
45+
try {
46+
returnclass_exists($type) ?new$type(...$data) :$data;
47+
}catch (\Throwable$e) {
48+
thrownewNotNormalizableValueException($e->getMessage(),$e->getCode(),$e);
49+
}
50+
});
51+
52+
$this->assertEquals($expected,$this->denormalizer->denormalize($input,$type,$format,$context));
6353
}
6454

6555
publicfunctiontestSupportsValidArray()
@@ -108,6 +98,74 @@ public function testSupportsNoArray()
10898
)
10999
);
110100
}
101+
102+
publicstaticfunctiongetTestArrays():array
103+
{
104+
return [
105+
'array<ArrayDummy>' => [
106+
[
107+
['foo' =>'one','bar' =>'two'],
108+
['foo' =>'three','bar' =>'four'],
109+
],
110+
[
111+
newArrayDummy('one','two'),
112+
newArrayDummy('three','four'),
113+
],
114+
__NAMESPACE__.'\ArrayDummy[]',
115+
'json',
116+
],
117+
118+
'array<ArrayDummy|UnionDummy|null>' => [
119+
[
120+
['foo' =>'one','bar' =>'two'],
121+
['baz' =>'three'],
122+
null,
123+
],
124+
[
125+
newArrayDummy('one','two'),
126+
newUnionDummy('three'),
127+
null,
128+
],
129+
'mixed[]',
130+
'json',
131+
[
132+
'value_type' =>newType(
133+
Type::BUILTIN_TYPE_ARRAY,
134+
collection:true,
135+
collectionValueType: [
136+
newType(Type::BUILTIN_TYPE_OBJECT,true, ArrayDummy::class),
137+
newType(Type::BUILTIN_TYPE_OBJECT, class: UnionDummy::class),
138+
]
139+
),
140+
],
141+
],
142+
143+
'array<ArrayDummy|string>' => [
144+
[
145+
['foo' =>'one','bar' =>'two'],
146+
['foo' =>'three','bar' =>'four'],
147+
'string',
148+
],
149+
[
150+
newArrayDummy('one','two'),
151+
newArrayDummy('three','four'),
152+
'string',
153+
],
154+
'mixed[]',
155+
'json',
156+
[
157+
'value_type' =>newType(
158+
Type::BUILTIN_TYPE_ARRAY,
159+
collection:true,
160+
collectionValueType: [
161+
newType(Type::BUILTIN_TYPE_OBJECT, class: ArrayDummy::class),
162+
newType(Type::BUILTIN_TYPE_STRING),
163+
]
164+
),
165+
],
166+
],
167+
];
168+
}
111169
}
112170

113171
class ArrayDummy
@@ -121,3 +179,13 @@ public function __construct($foo, $bar)
121179
$this->bar =$bar;
122180
}
123181
}
182+
183+
class UnionDummy
184+
{
185+
public$baz;
186+
187+
publicfunction__construct($baz)
188+
{
189+
$this->baz =$baz;
190+
}
191+
}

‎src/Symfony/Component/Serializer/Tests/SerializerTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1084,7 +1084,7 @@ public function testCollectDenormalizationErrors(?ClassMetadataFactory $classMet
10841084
'expectedTypes' => ['array'],
10851085
'path' =>'anotherCollection',
10861086
'useMessageForUser' =>false,
1087-
'message' =>'Data expected to be "Symfony\Component\Serializer\Tests\Fixtures\Php74Full[]", "null" given.',
1087+
'message' =>'Data expected to be "array<Symfony\Component\Serializer\Tests\Fixtures\Php74Full>", "null" given.',
10881088
],
10891089
];
10901090

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp