12
12
namespace Symfony \Component \HttpKernel \Controller \ArgumentResolver ;
13
13
14
14
use Symfony \Component \EventDispatcher \EventSubscriberInterface ;
15
+ use Symfony \Component \HttpFoundation \File \UploadedFile ;
15
16
use Symfony \Component \HttpFoundation \Request ;
16
17
use Symfony \Component \HttpKernel \Attribute \MapQueryString ;
17
18
use Symfony \Component \HttpKernel \Attribute \MapRequestPayload ;
19
+ use Symfony \Component \HttpKernel \Attribute \MapUploadedFile ;
18
20
use Symfony \Component \HttpKernel \Controller \ValueResolverInterface ;
19
21
use Symfony \Component \HttpKernel \ControllerMetadata \ArgumentMetadata ;
20
22
use Symfony \Component \HttpKernel \Event \ControllerArgumentsEvent ;
28
30
use Symfony \Component \Serializer \Exception \UnsupportedFormatException ;
29
31
use Symfony \Component \Serializer \Normalizer \DenormalizerInterface ;
30
32
use Symfony \Component \Serializer \SerializerInterface ;
33
+ use Symfony \Component \Validator \Constraints as Assert ;
31
34
use Symfony \Component \Validator \ConstraintViolation ;
32
35
use Symfony \Component \Validator \ConstraintViolationList ;
33
36
use Symfony \Component \Validator \Exception \ValidationFailedException ;
@@ -68,13 +71,14 @@ public function resolve(Request $request, ArgumentMetadata $argument): iterable
68
71
{
69
72
$ attribute = $ argument ->getAttributesOfType (MapQueryString::class, ArgumentMetadata::IS_INSTANCEOF )[0 ]
70
73
?? $ argument ->getAttributesOfType (MapRequestPayload::class, ArgumentMetadata::IS_INSTANCEOF )[0 ]
74
+ ?? $ argument ->getAttributesOfType (MapUploadedFile::class, ArgumentMetadata::IS_INSTANCEOF )[0 ]
71
75
?? null ;
72
76
73
77
if (!$ attribute ) {
74
78
return [];
75
79
}
76
80
77
- if ($ argument ->isVariadic ()) {
81
+ if (! $ attribute instanceof MapUploadedFile && $ argument ->isVariadic ()) {
78
82
throw new \LogicException (sprintf ('Mapping variadic argument "$%s" is not supported. ' , $ argument ->getName ()));
79
83
}
80
84
@@ -94,19 +98,22 @@ public function onKernelControllerArguments(ControllerArgumentsEvent $event): vo
94
98
} elseif ($ argument instanceof MapRequestPayload) {
95
99
$ payloadMapper = 'mapRequestPayload ' ;
96
100
$ validationFailedCode = $ argument ->validationFailedStatusCode ;
101
+ } elseif ($ argument instanceof MapUploadedFile) {
102
+ $ payloadMapper = 'mapUploadedFile ' ;
103
+ $ validationFailedCode = $ argument ->validationFailedStatusCode ;
97
104
} else {
98
105
continue ;
99
106
}
100
107
$ request = $ event ->getRequest ();
101
108
102
- if (!$ type = $ argument ->metadata ->getType ()) {
109
+ if (!$ argument ->metadata ->getType ()) {
103
110
throw new \LogicException (sprintf ('Could not resolve the "$%s" controller argument: argument should be typed. ' , $ argument ->metadata ->getName ()));
104
111
}
105
112
106
113
if ($ this ->validator ) {
107
114
$ violations = new ConstraintViolationList ();
108
115
try {
109
- $ payload = $ this ->$ payloadMapper ($ request , $ type , $ argument );
116
+ $ payload = $ this ->$ payloadMapper ($ request , $ argument -> metadata , $ argument );
110
117
} catch (PartialDenormalizationException $ e ) {
111
118
$ trans = $ this ->translator ? $ this ->translator ->trans (...) : fn ($ m , $ p ) => strtr ($ m , $ p );
112
119
foreach ($ e ->getErrors () as $ error ) {
@@ -126,15 +133,19 @@ public function onKernelControllerArguments(ControllerArgumentsEvent $event): vo
126
133
}
127
134
128
135
if (null !== $ payload && !\count ($ violations )) {
129
- $ violations ->addAll ($ this ->validator ->validate ($ payload , null , $ argument ->validationGroups ?? null ));
136
+ $ constraints = $ argument ->constraints ?? null ;
137
+ if (\is_array ($ payload ) && !empty ($ constraints ) && !$ constraints instanceof Assert \All) {
138
+ $ constraints = new Assert \All ($ constraints );
139
+ }
140
+ $ violations ->addAll ($ this ->validator ->validate ($ payload , $ constraints , $ argument ->validationGroups ?? null ));
130
141
}
131
142
132
143
if (\count ($ violations )) {
133
144
throw HttpException::fromStatusCode ($ validationFailedCode , implode ("\n" , array_map (static fn ($ e ) => $ e ->getMessage (), iterator_to_array ($ violations ))), new ValidationFailedException ($ payload , $ violations ));
134
145
}
135
146
} else {
136
147
try {
137
- $ payload = $ this ->$ payloadMapper ($ request , $ type , $ argument );
148
+ $ payload = $ this ->$ payloadMapper ($ request , $ argument -> metadata , $ argument );
138
149
} catch (PartialDenormalizationException $ e ) {
139
150
throw HttpException::fromStatusCode ($ validationFailedCode , implode ("\n" , array_map (static fn ($ e ) => $ e ->getMessage (), $ e ->getErrors ())), $ e );
140
151
}
@@ -161,16 +172,16 @@ public static function getSubscribedEvents(): array
161
172
];
162
173
}
163
174
164
- private function mapQueryString (Request $ request , string $ type , MapQueryString $ attribute ): ?object
175
+ private function mapQueryString (Request $ request , ArgumentMetadata $ argument , MapQueryString $ attribute ): ?object
165
176
{
166
177
if (!$ data = $ request ->query ->all ()) {
167
178
return null ;
168
179
}
169
180
170
- return $ this ->serializer ->denormalize ($ data , $ type , null , $ attribute ->serializationContext + self ::CONTEXT_DENORMALIZE + ['filter_bool ' => true ]);
181
+ return $ this ->serializer ->denormalize ($ data , $ argument -> getType () , null , $ attribute ->serializationContext + self ::CONTEXT_DENORMALIZE + ['filter_bool ' => true ]);
171
182
}
172
183
173
- private function mapRequestPayload (Request $ request , string $ type , MapRequestPayload $ attribute ): ?object
184
+ private function mapRequestPayload (Request $ request , ArgumentMetadata $ argument , MapRequestPayload $ attribute ): ?object
174
185
{
175
186
if (null === $ format = $ request ->getContentTypeFormat ()) {
176
187
throw new UnsupportedMediaTypeHttpException ('Unsupported format. ' );
@@ -181,7 +192,7 @@ private function mapRequestPayload(Request $request, string $type, MapRequestPay
181
192
}
182
193
183
194
if ($ data = $ request ->request ->all ()) {
184
- return $ this ->serializer ->denormalize ($ data , $ type , null , $ attribute ->serializationContext + self ::CONTEXT_DENORMALIZE + ('form ' === $ format ? ['filter_bool ' => true ] : []));
195
+ return $ this ->serializer ->denormalize ($ data , $ argument -> getType () , null , $ attribute ->serializationContext + self ::CONTEXT_DENORMALIZE + ('form ' === $ format ? ['filter_bool ' => true ] : []));
185
196
}
186
197
187
198
if ('' === $ data = $ request ->getContent ()) {
@@ -193,7 +204,7 @@ private function mapRequestPayload(Request $request, string $type, MapRequestPay
193
204
}
194
205
195
206
try {
196
- return $ this ->serializer ->deserialize ($ data , $ type , $ format , self ::CONTEXT_DESERIALIZE + $ attribute ->serializationContext );
207
+ return $ this ->serializer ->deserialize ($ data , $ argument -> getType () , $ format , self ::CONTEXT_DESERIALIZE + $ attribute ->serializationContext );
197
208
} catch (UnsupportedFormatException $ e ) {
198
209
throw new UnsupportedMediaTypeHttpException (sprintf ('Unsupported format: "%s". ' , $ format ), $ e );
199
210
} catch (NotEncodableValueException $ e ) {
@@ -202,4 +213,9 @@ private function mapRequestPayload(Request $request, string $type, MapRequestPay
202
213
throw new BadRequestHttpException (sprintf ('Request payload contains invalid "%s" property. ' , $ e ->property ), $ e );
203
214
}
204
215
}
216
+
217
+ private function mapUploadedFile (Request $ request , ArgumentMetadata $ argument , MapUploadedFile $ attribute ): UploadedFile |array |null
218
+ {
219
+ return $ request ->files ->get ($ attribute ->name ?? $ argument ->getName (), []);
220
+ }
205
221
}
0 commit comments