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

Appearance settings

Commit 9e64462

Browse filesBrowse files
Renerenedelima
Rene
authored andcommitted
[HttpKernel] Add MapUploadedFile attribute
1 parent 9549cc2 commit 9e64462
Copy full SHA for 9e64462

File tree

6 files changed

+406
-11
lines changed
Filter options

6 files changed

+406
-11
lines changed
+33Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\HttpKernel\Attribute;
13+
14+
use Symfony\Component\HttpFoundation\Response;
15+
use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestPayloadValueResolver;
16+
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
17+
use Symfony\Component\Validator\Constraint;
18+
19+
#[\Attribute(\Attribute::TARGET_PARAMETER)]
20+
class MapUploadedFile extends ValueResolver
21+
{
22+
public ArgumentMetadata $metadata;
23+
24+
public function __construct(
25+
/** @var Constraint|array<Constraint>|null */
26+
public Constraint|array|null $constraints = null,
27+
public ?string $name = null,
28+
string $resolver = RequestPayloadValueResolver::class,
29+
public readonly int $validationFailedStatusCode = Response::HTTP_UNPROCESSABLE_ENTITY,
30+
) {
31+
parent::__construct($resolver);
32+
}
33+
}

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpKernel/CHANGELOG.md
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ CHANGELOG
99
* Add `$validationFailedStatusCode` argument to `#[MapQueryParameter]` that allows setting a custom HTTP status code when validation fails
1010
* Add `NearMissValueResolverException` to let value resolvers report when an argument could be under their watch but failed to be resolved
1111
* Add `$type` argument to `#[MapRequestPayload]` that allows mapping a list of items
12+
* Add `#[MapUploadedFile]` attribute to fetch, validate, and inject uploaded files into controller arguments
1213

1314
7.0
1415
---

‎src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/RequestPayloadValueResolver.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/RequestPayloadValueResolver.php
+27-11Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@
1212
namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver;
1313

1414
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
15+
use Symfony\Component\HttpFoundation\File\UploadedFile;
1516
use Symfony\Component\HttpFoundation\Request;
1617
use Symfony\Component\HttpKernel\Attribute\MapQueryString;
1718
use Symfony\Component\HttpKernel\Attribute\MapRequestPayload;
19+
use Symfony\Component\HttpKernel\Attribute\MapUploadedFile;
1820
use Symfony\Component\HttpKernel\Controller\ValueResolverInterface;
1921
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
2022
use Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent;
@@ -29,6 +31,7 @@
2931
use Symfony\Component\Serializer\Exception\UnsupportedFormatException;
3032
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
3133
use Symfony\Component\Serializer\SerializerInterface;
34+
use Symfony\Component\Validator\Constraints as Assert;
3235
use Symfony\Component\Validator\ConstraintViolation;
3336
use Symfony\Component\Validator\ConstraintViolationList;
3437
use 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

@@ -105,19 +109,22 @@ public function onKernelControllerArguments(ControllerArgumentsEvent $event): vo
105109
} elseif ($argument instanceof MapRequestPayload) {
106110
$payloadMapper = 'mapRequestPayload';
107111
$validationFailedCode = $argument->validationFailedStatusCode;
112+
} elseif ($argument instanceof MapUploadedFile) {
113+
$payloadMapper = 'mapUploadedFile';
114+
$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 = $this->$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 = $this->$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,12 +202,12 @@ 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.'[]';
196207
}
197208

198209
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] : []));
200211
}
201212

202213
if ('' === $data = $request->getContent()) {
@@ -208,7 +219,7 @@ private function mapRequestPayload(Request $request, string $type, MapRequestPay
208219
}
209220

210221
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);
212223
} catch (UnsupportedFormatException $e) {
213224
throw new UnsupportedMediaTypeHttpException(sprintf('Unsupported format: "%s".', $format), $e);
214225
} catch (NotEncodableValueException $e) {
@@ -217,4 +228,9 @@ private function mapRequestPayload(Request $request, string $type, MapRequestPay
217228
throw new BadRequestHttpException(sprintf('Request payload contains invalid "%s" property.', $e->property), $e);
218229
}
219230
}
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+
}
220236
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.