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 ;
29
31
use Symfony \Component \Serializer \Exception \UnsupportedFormatException ;
30
32
use Symfony \Component \Serializer \Normalizer \DenormalizerInterface ;
31
33
use Symfony \Component \Serializer \SerializerInterface ;
34
+ use Symfony \Component \Validator \Constraints as Assert ;
32
35
use Symfony \Component \Validator \ConstraintViolation ;
33
36
use Symfony \Component \Validator \ConstraintViolationList ;
34
37
use Symfony \Component \Validator \Exception \ValidationFailedException ;
@@ -69,13 +72,14 @@ public function resolve(Request $request, ArgumentMetadata $argument): iterable
69
72
{
70
73
$ attribute = $ argument ->getAttributesOfType (MapQueryString::class, ArgumentMetadata::IS_INSTANCEOF )[0 ]
71
74
?? $ argument ->getAttributesOfType (MapRequestPayload::class, ArgumentMetadata::IS_INSTANCEOF )[0 ]
75
+ ?? $ argument ->getAttributesOfType (MapUploadedFile::class, ArgumentMetadata::IS_INSTANCEOF )[0 ]
72
76
?? null ;
73
77
74
78
if (!$ attribute ) {
75
79
return [];
76
80
}
77
81
78
- if ($ argument ->isVariadic ()) {
82
+ if (! $ attribute instanceof MapUploadedFile && $ argument ->isVariadic ()) {
79
83
throw new \LogicException (sprintf ('Mapping variadic argument "$%s" is not supported. ' , $ argument ->getName ()));
80
84
}
81
85
@@ -105,19 +109,22 @@ public function onKernelControllerArguments(ControllerArgumentsEvent $event): vo
105
109
} elseif ($ argument instanceof MapRequestPayload) {
106
110
$ payloadMapper = 'mapRequestPayload ' ;
107
111
$ validationFailedCode = $ argument ->validationFailedStatusCode ;
112
+ } elseif ($ argument instanceof MapUploadedFile) {
113
+ $ payloadMapper = 'mapUploadedFile ' ;
114
+ $ validationFailedCode = $ argument ->validationFailedStatusCode ;
108
115
} else {
109
116
continue ;
110
117
}
111
118
$ request = $ event ->getRequest ();
112
119
113
- if (!$ type = $ argument ->metadata ->getType ()) {
120
+ if (!$ argument ->metadata ->getType ()) {
114
121
throw new \LogicException (sprintf ('Could not resolve the "$%s" controller argument: argument should be typed. ' , $ argument ->metadata ->getName ()));
115
122
}
116
123
117
124
if ($ this ->validator ) {
118
125
$ violations = new ConstraintViolationList ();
119
126
try {
120
- $ payload = $ this ->$ payloadMapper ($ request , $ type , $ argument );
127
+ $ payload = $ this ->$ payloadMapper ($ request , $ argument -> metadata , $ argument );
121
128
} catch (PartialDenormalizationException $ e ) {
122
129
$ trans = $ this ->translator ? $ this ->translator ->trans (...) : fn ($ m , $ p ) => strtr ($ m , $ p );
123
130
foreach ($ e ->getErrors () as $ error ) {
@@ -137,15 +144,19 @@ public function onKernelControllerArguments(ControllerArgumentsEvent $event): vo
137
144
}
138
145
139
146
if (null !== $ payload && !\count ($ violations )) {
140
- $ violations ->addAll ($ this ->validator ->validate ($ payload , null , $ argument ->validationGroups ?? null ));
147
+ $ constraints = $ argument ->constraints ?? null ;
148
+ if (\is_array ($ payload ) && !empty ($ constraints ) && !$ constraints instanceof Assert \All) {
149
+ $ constraints = new Assert \All ($ constraints );
150
+ }
151
+ $ violations ->addAll ($ this ->validator ->validate ($ payload , $ constraints , $ argument ->validationGroups ?? null ));
141
152
}
142
153
143
154
if (\count ($ violations )) {
144
155
throw HttpException::fromStatusCode ($ validationFailedCode , implode ("\n" , array_map (static fn ($ e ) => $ e ->getMessage (), iterator_to_array ($ violations ))), new ValidationFailedException ($ payload , $ violations ));
145
156
}
146
157
} else {
147
158
try {
148
- $ payload = $ this ->$ payloadMapper ($ request , $ type , $ argument );
159
+ $ payload = $ this ->$ payloadMapper ($ request , $ argument -> metadata , $ argument );
149
160
} catch (PartialDenormalizationException $ e ) {
150
161
throw HttpException::fromStatusCode ($ validationFailedCode , implode ("\n" , array_map (static fn ($ e ) => $ e ->getMessage (), $ e ->getErrors ())), $ e );
151
162
}
@@ -172,16 +183,16 @@ public static function getSubscribedEvents(): array
172
183
];
173
184
}
174
185
175
- private function mapQueryString (Request $ request , string $ type , MapQueryString $ attribute ): ?object
186
+ private function mapQueryString (Request $ request , ArgumentMetadata $ argument , MapQueryString $ attribute ): ?object
176
187
{
177
188
if (!$ data = $ request ->query ->all ()) {
178
189
return null ;
179
190
}
180
191
181
- return $ this ->serializer ->denormalize ($ data , $ type , null , $ attribute ->serializationContext + self ::CONTEXT_DENORMALIZE + ['filter_bool ' => true ]);
192
+ return $ this ->serializer ->denormalize ($ data , $ argument -> getType () , null , $ attribute ->serializationContext + self ::CONTEXT_DENORMALIZE + ['filter_bool ' => true ]);
182
193
}
183
194
184
- private function mapRequestPayload (Request $ request , string $ type , MapRequestPayload $ attribute ): object |array |null
195
+ private function mapRequestPayload (Request $ request , ArgumentMetadata $ argument , MapRequestPayload $ attribute ): object |array |null
185
196
{
186
197
if (null === $ format = $ request ->getContentTypeFormat ()) {
187
198
throw new UnsupportedMediaTypeHttpException ('Unsupported format. ' );
@@ -191,12 +202,12 @@ private function mapRequestPayload(Request $request, string $type, MapRequestPay
191
202
throw new UnsupportedMediaTypeHttpException (sprintf ('Unsupported format, expects "%s", but "%s" given. ' , implode ('", " ' , (array ) $ attribute ->acceptFormat ), $ format ));
192
203
}
193
204
194
- if ('array ' === $ type && null !== $ attribute ->type ) {
205
+ if ('array ' === $ argument -> getType () && null !== $ attribute ->type ) {
195
206
$ type = $ attribute ->type .'[] ' ;
196
207
}
197
208
198
209
if ($ data = $ request ->request ->all ()) {
199
- return $ this ->serializer ->denormalize ($ data , $ type , null , $ attribute ->serializationContext + self ::CONTEXT_DENORMALIZE + ('form ' === $ format ? ['filter_bool ' => true ] : []));
210
+ return $ this ->serializer ->denormalize ($ data , $ argument -> getType () , null , $ attribute ->serializationContext + self ::CONTEXT_DENORMALIZE + ('form ' === $ format ? ['filter_bool ' => true ] : []));
200
211
}
201
212
202
213
if ('' === $ data = $ request ->getContent ()) {
@@ -208,7 +219,7 @@ private function mapRequestPayload(Request $request, string $type, MapRequestPay
208
219
}
209
220
210
221
try {
211
- return $ this ->serializer ->deserialize ($ data , $ type , $ format , self ::CONTEXT_DESERIALIZE + $ attribute ->serializationContext );
222
+ return $ this ->serializer ->deserialize ($ data , $ argument -> getType () , $ format , self ::CONTEXT_DESERIALIZE + $ attribute ->serializationContext );
212
223
} catch (UnsupportedFormatException $ e ) {
213
224
throw new UnsupportedMediaTypeHttpException (sprintf ('Unsupported format: "%s". ' , $ format ), $ e );
214
225
} catch (NotEncodableValueException $ e ) {
@@ -217,4 +228,9 @@ private function mapRequestPayload(Request $request, string $type, MapRequestPay
217
228
throw new BadRequestHttpException (sprintf ('Request payload contains invalid "%s" property. ' , $ e ->property ), $ e );
218
229
}
219
230
}
231
+
232
+ private function mapUploadedFile (Request $ request , ArgumentMetadata $ argument , MapUploadedFile $ attribute ): UploadedFile |array |null
233
+ {
234
+ return $ request ->files ->get ($ attribute ->name ?? $ argument ->getName (), []);
235
+ }
220
236
}
0 commit comments