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
7478 if (!$ attribute ) {
7579 return [];
7680 }
7781
78- if ($ argument ->isVariadic ()) {
82+ if (! $ attribute instanceof MapUploadedFile && $ argument ->isVariadic ()) {
7983 throw new \LogicException (sprintf ('Mapping variadic argument "$%s" is not supported. ' , $ argument ->getName ()));
8084 }
8185
@@ -100,24 +104,27 @@ public function onKernelControllerArguments(ControllerArgumentsEvent $event): vo
100104
101105 foreach ($ arguments as $ i => $ argument ) {
102106 if ($ argument instanceof MapQueryString) {
103- $ payloadMapper = ' mapQueryString ' ;
107+ $ payloadMapper = $ this -> mapQueryString (...) ;
104108 $ validationFailedCode = $ argument ->validationFailedStatusCode ;
105109 } elseif ($ argument instanceof MapRequestPayload) {
106- $ payloadMapper = 'mapRequestPayload ' ;
110+ $ payloadMapper = $ this ->mapRequestPayload (...);
111+ $ validationFailedCode = $ argument ->validationFailedStatusCode ;
112+ } elseif ($ argument instanceof MapUploadedFile) {
113+ $ payloadMapper = $ this ->mapUploadedFile (...);
107114 $ validationFailedCode = $ argument ->validationFailedStatusCode ;
108115 } else {
109116 continue ;
110117 }
111118 $ request = $ event ->getRequest ();
112119
113- if (!$ type = $ argument ->metadata ->getType ()) {
120+ if (!$ argument ->metadata ->getType ()) {
114121 throw new \LogicException (sprintf ('Could not resolve the "$%s" controller argument: argument should be typed. ' , $ argument ->metadata ->getName ()));
115122 }
116123
117124 if ($ this ->validator ) {
118125 $ violations = new ConstraintViolationList ();
119126 try {
120- $ payload = $ this -> $ payloadMapper ($ request , $ type , $ argument );
127+ $ payload = $ payloadMapper ($ request , $ argument -> metadata , $ argument );
121128 } catch (PartialDenormalizationException $ e ) {
122129 $ trans = $ this ->translator ? $ this ->translator ->trans (...) : fn ($ m , $ p ) => strtr ($ m , $ p );
123130 foreach ($ e ->getErrors () as $ error ) {
@@ -137,15 +144,19 @@ public function onKernelControllerArguments(ControllerArgumentsEvent $event): vo
137144 }
138145
139146 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 ));
141152 }
142153
143154 if (\count ($ violations )) {
144155 throw HttpException::fromStatusCode ($ validationFailedCode , implode ("\n" , array_map (static fn ($ e ) => $ e ->getMessage (), iterator_to_array ($ violations ))), new ValidationFailedException ($ payload , $ violations ));
145156 }
146157 } else {
147158 try {
148- $ payload = $ this -> $ payloadMapper ($ request , $ type , $ argument );
159+ $ payload = $ payloadMapper ($ request , $ argument -> metadata , $ argument );
149160 } catch (PartialDenormalizationException $ e ) {
150161 throw 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 {
177188 if (!$ data = $ request ->query ->all ()) {
178189 return 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 {
186197 if (null === $ format = $ request ->getContentTypeFormat ()) {
187198 throw new UnsupportedMediaTypeHttpException ('Unsupported format. ' );
@@ -191,8 +202,10 @@ private function mapRequestPayload(Request $request, string $type, MapRequestPay
191202 throw 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
198211 if ($ data = $ request ->request ->all ()) {
@@ -217,4 +230,9 @@ private function mapRequestPayload(Request $request, string $type, MapRequestPay
217230 throw 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}
0 commit comments