-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[FrameworkBundle][Serializer] Add an ArgumentResolver to deserialize & validate user input #45628
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
da5e8a4
3012614
6000089
4bb2235
642c7d0
2368c7f
5905ae0
406f1eb
7f7500c
eae458f
1523617
5976a4b
ebeee98
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
…tResolver to split functionalities
- Loading branch information
There are no files selected for viewing
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <fabien@symfony.com> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Component\Serializer\Annotation; | ||
|
||
/** | ||
* Indicates that this argument should be deserialized from request body. | ||
* | ||
* @author Gary PEGEOT <garypegeot@gmail.com> | ||
*/ | ||
#[\Attribute(\Attribute::TARGET_PARAMETER)] | ||
class RequestBody | ||
{ | ||
/** | ||
* @param string|null $format Will be guessed from request if empty, and default to JSON. | ||
* @param array $context The serialization context (Useful to set groups / ignore fields). | ||
*/ | ||
public function __construct(public readonly ?string $format = null, public readonly array $context = []) | ||
{ | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,26 +14,18 @@ | |
use Symfony\Component\HttpFoundation\Request; | ||
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; | ||
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; | ||
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException; | ||
use Symfony\Component\Serializer\Annotation\Input; | ||
use Symfony\Component\Serializer\Exception\PartialDenormalizationException; | ||
use Symfony\Component\Serializer\Annotation\RequestBody; | ||
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; | ||
use Symfony\Component\Serializer\SerializerInterface; | ||
use Symfony\Component\Validator\ConstraintViolation; | ||
use Symfony\Component\Validator\ConstraintViolationList; | ||
use Symfony\Component\Validator\Exception\InputValidationFailedException; | ||
use Symfony\Component\Validator\Validator\ValidatorInterface; | ||
|
||
/** | ||
* Deserialize & validate user input. | ||
* | ||
* Works in duo with Symfony\Bundle\FrameworkBundle\EventListener\InputValidationFailedExceptionListener. | ||
* Deserialize request body if Symfony\Component\Serializer\Annotation\RequestBody attribute is present on an argument. | ||
* | ||
* @author Gary PEGEOT <garypegeot@gmail.com> | ||
*/ | ||
class UserInputResolver implements ArgumentValueResolverInterface | ||
{ | ||
public function __construct(private SerializerInterface $serializer, private ?ValidatorInterface $validator = null) | ||
public function __construct(private SerializerInterface $serializer) | ||
{ | ||
} | ||
|
||
|
@@ -51,47 +43,16 @@ public function supports(Request $request, ArgumentMetadata $argument): bool | |
public function resolve(Request $request, ArgumentMetadata $argument): iterable | ||
{ | ||
$attribute = $this->getAttribute($argument); | ||
$context = array_merge($attribute->serializationContext, [ | ||
$context = array_merge($attribute->context, [ | ||
DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS => true, | ||
]); | ||
$format = $attribute->format ?? $request->getContentType() ?? 'json'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For security reasons, I suggest to throw an exception if the format isn't provided in the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we just use |
||
|
||
try { | ||
$input = $this->serializer->deserialize($request->getContent(), $argument->getType(), $format, $context); | ||
} catch (PartialDenormalizationException $e) { | ||
if (null === $this->validator) { | ||
throw new UnprocessableEntityHttpException(message: $e->getMessage(), previous: $e); | ||
} | ||
|
||
$errors = new ConstraintViolationList(); | ||
|
||
foreach ($e->getErrors() as $exception) { | ||
$message = sprintf('The type must be one of "%s" ("%s" given).', implode(', ', $exception->getExpectedTypes()), $exception->getCurrentType()); | ||
$parameters = []; | ||
|
||
if ($exception->canUseMessageForUser()) { | ||
$parameters['hint'] = $exception->getMessage(); | ||
} | ||
|
||
$errors->add(new ConstraintViolation($message, '', $parameters, null, $exception->getPath(), null)); | ||
} | ||
|
||
throw new InputValidationFailedException(null, $errors); | ||
} | ||
|
||
if ($this->validator) { | ||
$errors = $this->validator->validate(value: $input, groups: $attribute->validationGroups); | ||
|
||
if ($errors->count() > 0) { | ||
throw new InputValidationFailedException($input, $errors); | ||
} | ||
} | ||
|
||
yield $input; | ||
yield $this->serializer->deserialize($request->getContent(), $argument->getType(), $format, $context); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be great to resolve input not only from request content but from query/request parameters Something like this:
|
||
} | ||
|
||
private function getAttribute(ArgumentMetadata $argument): ?Input | ||
private function getAttribute(ArgumentMetadata $argument): ?RequestBody | ||
{ | ||
return $argument->getAttributes(Input::class, ArgumentMetadata::IS_INSTANCEOF)[0] ?? null; | ||
return $argument->getAttributes(RequestBody::class, ArgumentMetadata::IS_INSTANCEOF)[0] ?? null; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about adding
AbstractObjectNormalizer::ALLOW_EXTRA_ATTRIBUTES => false
?