-
-
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
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,13 +14,14 @@ | |
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\Ex 8000 ception\PartialDenormalizationException; | ||
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\ValidationFailedException; | ||
use Symfony\Component\Validator\Exception\InputValidationFailedException; | ||
use Symfony\Component\Validator\Validator\ValidatorInterface; | ||
|
||
/** | ||
|
@@ -32,7 +33,7 @@ | |
*/ | ||
class UserInputResolver implements ArgumentValueResolverInterface | ||
{ | ||
public function __construct(private ValidatorInterface $validator, private SerializerInterface $serializer, ) | ||
public function __construct(private SerializerInterface $serializer, private ?ValidatorInterface $validator = null) | ||
{ | ||
} | ||
|
||
|
@@ -55,12 +56,13 @@ public function resolve(Request $request, ArgumentMetadata $argument): iterable | |
]); | ||
$format = $attribute->format ?? $request->attributes->get('_format', 'json'); | ||
|
||
$input = null; | ||
try { | ||
$input = $this->serializer->deserialize(data: $request->getContent(), type: $argument->getType(), format: $format, context: $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. Please use ordered arguments. |
||
|
||
$errors = $this->validator->validate(value: $input, groups: $attribute->validationGroups); | ||
} catch (PartialDenormalizationException $e) { | ||
if (null === $this->validator) { | ||
throw new UnprocessableEntityHttpException(message: $e->getMessage(), previous: $e); | ||
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. This will show an empty message which is not useful. However, we can still normalize the |
||
} | ||
|
||
$errors = new ConstraintViolationList(); | ||
|
||
foreach ($e->getErrors() as $exception) { | ||
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. I don't think it's the argument resolver's responsibility to normalize these errors, but the Serializer normalizers instead. In order not to depend on the Validator component (as I've pointed out in another comment) you can add a new |
||
|
@@ -73,10 +75,16 @@ public function resolve(Request $request, ArgumentMetadata $argument): iterable | |
|
||
$errors->add(new ConstraintViolation($message, '', $parameters, null, $exception->getPath(), null)); | ||
} | ||
|
||
throw new InputValidationFailedException(null, $errors); | ||
} | ||
|
||
if ($errors->count() > 0) { | ||
throw new ValidationFailedException($input, $errors); | ||
if ($this->validator) { | ||
$errors = $this->validator->validate(value: $input, groups: $attribute->validationGroups); | ||
|
||
if ($errors->count() > 0) { | ||
throw new InputValidationFailedException($input, $errors); | ||
} | ||
} | ||
|
||
yield $input; | ||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -15,7 +15,7 @@ | |||||
use Symfony\Component\HttpFoundation\Response; | ||||||
use Symfony\Component\HttpKernel\Event\ExceptionEvent; | ||||||
use Symfony\Component\Serializer\SerializerInterface; | ||||||
use Symfony\Component\Validator\Exception\ValidationFailedException; | ||||||
use Symfony\Component\Validator\Exception\InputValidationFailedException; | ||||||
|
||||||
/** | ||||||
* Works in duo with Symfony\Bundle\FrameworkBundle\ArgumentResolver\UserInputResolver. | ||||||
|
@@ -33,7 +33,7 @@ public function __invoke(ExceptionEvent $event): void | |||||
$throwable = $event->getThrowable(); | ||||||
$format = $event->getRequest()->attributes->get('_format', '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. Better use
Suggested change
|
||||||
|
||||||
if (!$throwable instanceof ValidationFailedException) { | ||||||
if (!$throwable instanceof InputValidationFailedException) { | ||||||
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. Who throw this exception? |
||||||
return; | ||||||
} | ||||||
|
||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
<?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\Validator\Exception; | ||
|
||
/** | ||
* @author Gary PEGEOT <garypegeot@gmail.com> | ||
*/ | ||
class InputValidationFailedException extends ValidationFailedException | ||
{ | ||
} |
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.
Use
$request->getPreferredFormat('json')
instead (it will take into account the acceptable content type when provided)Uh oh!
There was an error while loading. Please reload this page.
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.
Neither is correct, imho.
getPreferredFormat()
as well as the_format
attribute hint at the format of the response. What we need here is the format of the request payload. This can be inferred from theContent-Type
header of the request.That being said, the developer should be able to limit the set of possible formats (and content types?) via the attribute. Maybe we should even default to JSON-only, so nobody enables CSV deserialization by accident. 😓
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.
You're right, then we should use
$request->getContentType()
instead.[Off topic] Btw, the name of this method is not exactly what you'd expect here, it returns
json
,xml
, etc. even if that's what we want here it seems like the expected result (according to the method name) isapplication/json
, etc. maybe this method should be renamed togetContentTypeFormat()
or something like this, but that's another topic :)