1212namespace Symfony \Component \HttpKernel \Controller \ArgumentResolver ;
1313
1414use Symfony \Component \EventDispatcher \EventSubscriberInterface ;
15+ use Symfony \Component \HttpFoundation \File \UploadedFile ;
1516use Symfony \Component \HttpFoundation \Request ;
1617use Symfony \Component \HttpKernel \Attribute \MapQueryString ;
1718use Symfony \Component \HttpKernel \Attribute \MapRequestPayload ;
19+ use Symfony \Component \HttpKernel \Attribute \MapUploadedFile ;
1820use Symfony \Component \HttpKernel \Controller \ValueResolverInterface ;
1921use Symfony \Component \HttpKernel \ControllerMetadata \ArgumentMetadata ;
2022use Symfony \Component \HttpKernel \Event \ControllerArgumentsEvent ;
2931use Symfony \Component \Serializer \Exception \UnsupportedFormatException ;
3032use Symfony \Component \Serializer \Normalizer \DenormalizerInterface ;
3133use Symfony \Component \Serializer \SerializerInterface ;
34+ use Symfony \Component \Validator \Constraints as Assert ;
3235use Symfony \Component \Validator \ConstraintViolation ;
3336use Symfony \Component \Validator \ConstraintViolationList ;
3437use Symfony \Component \Validator \Exception \ValidationFailedException ;
@@ -69,13 +72,14 @@ public function resolve(Request $request, ArgumentMetadata $argument): iterable
6972 {
7073$ attribute =$ argument ->getAttributesOfType (MapQueryString::class, ArgumentMetadata::IS_INSTANCEOF )[0 ]
7174 ??$ argument ->getAttributesOfType (MapRequestPayload::class, ArgumentMetadata::IS_INSTANCEOF )[0 ]
75+ ??$ argument ->getAttributesOfType (MapUploadedFile::class, ArgumentMetadata::IS_INSTANCEOF )[0 ]
7276 ??null ;
7377
7478if (!$ attribute ) {
7579return [];
7680 }
7781
78- if ($ argument ->isVariadic ()) {
82+ if (! $ attribute instanceof MapUploadedFile && $ argument ->isVariadic ()) {
7983throw new \LogicException (sprintf ('Mapping variadic argument "$%s" is not supported. ' ,$ argument ->getName ()));
8084 }
8185
@@ -105,19 +109,22 @@ public function onKernelControllerArguments(ControllerArgumentsEvent $event): vo
105109 }elseif ($ argumentinstanceof MapRequestPayload) {
106110$ payloadMapper ='mapRequestPayload ' ;
107111$ validationFailedCode =$ argument ->validationFailedStatusCode ;
112+ }elseif ($ argumentinstanceof MapUploadedFile) {
113+ $ payloadMapper ='mapUploadedFile ' ;
114+ $ validationFailedCode =$ argument ->validationFailedStatusCode ;
108115 }else {
109116continue ;
110117 }
111118$ request =$ event ->getRequest ();
112119
113- if (!$ type = $ argument ->metadata ->getType ()) {
120+ if (!$ argument ->metadata ->getType ()) {
114121throw new \LogicException (sprintf ('Could not resolve the "$%s" controller argument: argument should be typed. ' ,$ argument ->metadata ->getName ()));
115122 }
116123
117124if ($ this ->validator ) {
118125$ violations =new ConstraintViolationList ();
119126try {
120- $ payload =$ this ->$ payloadMapper ($ request ,$ type ,$ argument );
127+ $ payload =$ this ->$ payloadMapper ($ request ,$ argument -> metadata ,$ argument );
121128 }catch (PartialDenormalizationException $ e ) {
122129$ trans =$ this ->translator ?$ this ->translator ->trans (...) :fn ($ m ,$ p ) =>strtr ($ m ,$ p );
123130foreach ($ e ->getErrors ()as $ error ) {
@@ -137,15 +144,19 @@ public function onKernelControllerArguments(ControllerArgumentsEvent $event): vo
137144 }
138145
139146if (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 ) && !$ constraintsinstanceof Assert \All) {
149+ $ constraints =new Assert \All ($ constraints );
150+ }
151+ $ violations ->addAll ($ this ->validator ->validate ($ payload ,$ constraints ,$ argument ->validationGroups ??null ));
141152 }
142153
143154if (\count ($ violations )) {
144155throw HttpException::fromStatusCode ($ validationFailedCode ,implode ("\n" ,array_map (static fn ($ e ) =>$ e ->getMessage (),iterator_to_array ($ violations ))),new ValidationFailedException ($ payload ,$ violations ));
145156 }
146157 }else {
147158try {
148- $ payload =$ this ->$ payloadMapper ($ request ,$ type ,$ argument );
159+ $ payload =$ this ->$ payloadMapper ($ request ,$ argument -> metadata ,$ argument );
149160 }catch (PartialDenormalizationException $ e ) {
150161throw HttpException::fromStatusCode ($ validationFailedCode ,implode ("\n" ,array_map (static fn ($ e ) =>$ e ->getMessage (),$ e ->getErrors ())),$ e );
151162 }
@@ -172,16 +183,16 @@ public static function getSubscribedEvents(): array
172183 ];
173184 }
174185
175- private function mapQueryString (Request $ request ,string $ type ,MapQueryString $ attribute ): ?object
186+ private function mapQueryString (Request $ request ,ArgumentMetadata $ argument ,MapQueryString $ attribute ): ?object
176187 {
177188if (!$ data =$ request ->query ->all ()) {
178189return null ;
179190 }
180191
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 ]);
182193 }
183194
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
185196 {
186197if (null ===$ format =$ request ->getContentTypeFormat ()) {
187198throw new UnsupportedMediaTypeHttpException ('Unsupported format. ' );
@@ -191,8 +202,10 @@ private function mapRequestPayload(Request $request, string $type, MapRequestPay
191202throw new UnsupportedMediaTypeHttpException (sprintf ('Unsupported format, expects "%s", but "%s" given. ' ,implode ('", " ' , (array )$ attribute ->acceptFormat ),$ format ));
192203 }
193204
194- if ('array ' ===$ type &&null !==$ attribute ->type ) {
205+ if ('array ' ===$ argument -> getType () &&null !==$ attribute ->type ) {
195206$ type =$ attribute ->type .'[] ' ;
207+ }else {
208+ $ type =$ argument ->getType ();
196209 }
197210
198211if ($ data =$ request ->request ->all ()) {
@@ -217,4 +230,9 @@ private function mapRequestPayload(Request $request, string $type, MapRequestPay
217230throw new BadRequestHttpException (sprintf ('Request payload contains invalid "%s" property. ' ,$ e ->property ),$ e );
218231 }
219232 }
233+
234+ private function mapUploadedFile (Request $ request ,ArgumentMetadata $ argument ,MapUploadedFile $ attribute ):UploadedFile |array |null
235+ {
236+ return $ request ->files ->get ($ attribute ->name ??$ argument ->getName (), []);
237+ }
220238}