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
Create Attributes #[MapRequestBody]
and #[MapQueryString]
to map Request input to typed objects
#49138
base: 6.3
Are you sure you want to change the base?
Conversation
87eb66c
to
a08be6b
Compare
#[MapQueryString
and #[MapRequestContent]
to map Request input to typed objects
#[MapQueryString
and #[MapRequestContent]
to map Request input to typed objects#[MapQueryString]
and #[MapRequestContent]
to map Request input to typed objects
078781e
to
b3bbe43
Compare
It looks like your committer email is not associated with your Github account |
Was this a coincidence that we both did something similar or did you create this afterwards? |
b3bbe43
to
4e8cb8e
Compare
@OskarStark thanx, fixed now, please check @ruudk it is similar but has few differences comparing to your implementation. I've updated PR description to highlight the differences. BTW anybody know how to fix unrelated fabbot failure? |
this looks promising :)
what about deserializing errors vs. ValidationFailedException? Does either produce a 400 bad request? is only the latter i18n friendly? |
My idea was start from something like simple implementation in this PR and improve it later before 6.3 release.
|
4e8cb8e
to
1b619a2
Compare
i like a simple implementation as first iteration, but i like http/i18n compatibility more ;) |
19069b1
to
052f625
Compare
Your PR also makes this one #47425 obsolete. |
052f625
to
25e7d70
Compare
I would use a separate exception, which builds on the |
cf31004
to
a2b5f96
Compare
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.
I understand the feature that is a shortcut to Deserializing and Validating your data (query or request) before it hits the Controller.
I think that this is helpful but I know it is already possible using API Platform, although API Platform enforces standards (for example for error validation, resource-oriented designs, but also plain json). I'm afraid that these attributes will lead to bad API designs in the future and wanted to share my concern.
Last but not least, the feature you propose looks out of the scope of the HTTP Kernel (adds a dependency on the Serializer and the Validator).
KernelEvents::EXCEPTION => ['onKernelException', -32], | ||
]; | ||
} | ||
} |
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.
Why do you need new exception listeners? Isn't that supposed to be the responsibility of the validation component?
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.
RequestPayloadValueResolver
calls Validator, check for result and throws ValidationFailedException
for an validation errors. Then we catch this Exception using Listeners mechanism to normalze Violations and respond with proper http code.
src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/RequestPayloadValueResolver.php
Show resolved
Hide resolved
We're already using such a similar arg value resolver in house, and from the reaction to this PR (and related PRs or issues), looks like it is not uncommon. It increases overall DX. I shouldn't need to import a big library just to avoid manually mapping json to PHP objects, the framework should do it for me. |
My argument is more that the join of validation and serialization is probably out of the scope of the HTTP Kernel. Actually what you do here is definitely already possible using the Symfony Framework by doing (pseudo code):
By "need to import a big library " you're probably talking about API Platform, I understand that this is the current "image" of this framework although it's not that "big" and its integration with the Symfony Framework is quite good. I see some quite interesting things in the DX here don't get me wrong, I just have some concerns about it being a part of the http-kernel. |
Yes, indeed. Attribute does not allow to do things that are not possible with plain PHP. But it's a shortcut to reduce boilerplate code. Like So I disagree with you. I'm a heavy user of APIP, but sometime I don't use it and this kind of entity would be very useful! So it's a big 👍🏼 for me |
Indeed, it is possible with a bit of boilerplate, that is specifically what we (users) would like to avoid.
It is big imho, I mean it does a lot more than just deserialize + validate.
Yeah, the concern about this being part of http-kernel is legit. I do think in an another PR @derrabus had suggested to move the changes to serializer component with optional validation |
I also saw #49134, I find the implementation quite good and it doesn't bring the issues I see here with the serializer/validator link. I love the idea behind these and this is going to inspire us for some of the upcoming features in API P:D.
Thing is it doesn't need to, it can do just the above with about no overhead:
(NB: pseudo code not sure it works just like that!) And you can already disable the validation ^^. We're in the process of spliting the code so that one could use only the relevant parts. /edit: I think we'll find something even better to avoid the Resource approach, I think I get the why you did this, and I quite like the approach! |
a15fa9d
to
25744fd
Compare
8899d39
to
661b21b
Compare
src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php
Outdated
Show resolved
Hide resolved
661b21b
to
147f8fe
Compare
147f8fe
to
0b525d1
Compare
@nicolas-grekas rebased again |
0b525d1
to
25061ae
Compare
btw, given #49614 we should consider |
|
||
$response = new Response( | ||
$data, | ||
Response::HTTP_UNPROCESSABLE_ENTITY, |
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.
it's reasonable to allow for a custom status code from eg. ValidationFailedException::getStatusCode()
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.
but ValidationFailedException
throwed from RequestPayloadValueResolver
, you can't throw your own special exception from it.
Can we merge this one? I have a local branch with some changes (merge of type error + validation ; Drop of partial violation normalizer and other nipticks). |
Yes, please |
Conversion and validation are two topics. I am familiar with a few frameworks in Java/Jakarta EE. Firstly compare to the existing web framework, I would like rename the attributes/annotations in this PR to <?php
class CreatingUserForm
{
public function __construct(
#[RequestParam(name:"name")]
public readonly string $name,
#[RequestParam(name:"age", required:false, defaultValue:"0")]
public readonly int $age= 25,
#[RequestPart(name:"file")]
public readonly byte[] $offset = 0,
) {
}
}
class CreatingUserCommand
{
public function __construct(
public readonly string $name,
public readonly int $age= 25
) {
}
}
#[Controller]
class UserApiController
{
// accept a traditional multiform/data
public function saveFromWeb(
#[ReuqestForm] CreatingUserForm $body,
): Response {
}
// accept json/xml
public function saveFromJson(
#[ReuqestBody] CreatingUserCommand $body,
): Response {
}
} Through these attrbutes/annotaions to convert to the target object. Applying the validation is targeted the converted object. class CreatingUserCommand
{
public function __construct(
#[NotBlank]
public readonly string $name,
#[Positive]
public readonly int $age= 25
) {
}
}
// in controller
public function saveFromJson(
#[ReuqestBody] #[Validated] CreatingUserCommand $body,
): Response {
} The above dummy usage of attributes/annotations is similar to Jakarta Bean Validations in Spring framework. |
that's more work in terms of maintaining the mapping :), i'd prefer to control it from the calling side (the argument attribute) if there's a usecase not validating the payload by default sounds like a footgun to me combining multiple sources eg. body/query/cookie/header into a single DTO could be done witth a new attribute then you can create pre-defined mappings in userland: it doesnt block this PR IMHO. please merge it :) |
Yet another variation of how we can map raw Request data to typed objects with validation. We can even build OpenApi Specification based on this DTO classes using NelmioApiDocBundle.
Usage Example🔧
#[MapRequestBody]
#[MapQueryString]
Exception handling💥
ConstraintViolationList
.Comparison to another implementations📑
Differences to #49002:
Differences to #49134:
$request->getContent()
and$request->request->all()
mappingDifferences to #45628:
$request->request->all()
and$request->query->all()
mappingBonus part🎁
UnsupportedFormatException
which thrown when there is no decoder for a given formatPartialDenormalizationException
which will convert it toValidationFailedException
ValidationFailedException
which will provideHTTP_UNPROCESSABLE_ENTITY
Response with serialized to json Constraint Violations