Skip to content

[HttpKernel] FromQuery, FromBody, FromRoute, etc. attributes #58709

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

Open
wants to merge 6 commits into
base: 7.3
Choose a base branch
from

Conversation

klkvsk
Copy link

@klkvsk klkvsk commented Oct 29, 2024

Q A
Branch? 7.3
Bug fix? no
New feature? yes
Deprecations? no
Issues #58662
License MIT

This is an attempt to separate how and from where the controller arguments are resolved.

Proposed new attributes #[FromQuery] #[FromBody] #[FromRoute] and #[FromHeader] (ValueBagResolvers) can be used in conjunction with existing #[MapDateTime] or #[MapEntity].

Most ValueResolvers that use data from request currently use only $request->attributes bag. They are easily converted to work with another bag specified by attribute by using ValueBagResolverTrait. BC is kept: when no argument attributes specified, $request->attributes is used.

It can deprecate #[MapQueryParameter].

It may deprecate #[MapQueryString] and #[MapRequestPayload] in the future, but it will need a something like #[MapDto] to replace them.

Currently I'm not quite done with handling variadic parameters. I want BackedEnum, Uid, and DateTime values to be supported, but I don't feel like their respective ValueResovlers should handle both variadic and non-variadic logic. On the other side, VariadicValueResolver has no ability to subsequently resolve values.

Feedback much appreciated.

Example:

#[Route("/foo/list", methods: ['GET'])]
public function listFoo(
    #[FromQuery] 
    int $page = 1,
    #[FromQuery('sort')] 
    string $sortingField = 'id',
    #[FromQuery('asc')] 
    bool $sortingAsc = true,
) {}

#[Route('/foo/{fooId}/status', methods: ['POST'])]
public function updateFoo(
    #[MapEntity(Foo::class, id: 'fooId')] // 
    Foo $foo,                             // 
                                          // -- same as --
    #[FromRoute('fooId')]                 // 
    #[MapEntity(Foo::class)]              // 
    Foo $foo,                             // 
    
    #[FromBody]
    FooStatus $status, // enum
) {}

@derrabus
Copy link
Member

Thank you. Please apply the patch from fabbot to fix all code-style issues.

@@ -31,6 +33,8 @@
*/
final class EntityValueResolver implements ValueResolverInterface
{
use ValueBagResolverTrait;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change will render the Doctrine Bridge incompatible with older versions of the HttpKernel component. That's fine, but we need to tell Composer about that.

Please adjust this conflict rule:

"symfony/http-kernel": "<6.4",

Since you're currently targeting 7.2 for this change, declaring incompatibility with versions before 7.2 would solve the problem.

@@ -122,7 +128,7 @@ private function find(ObjectManager $manager, Request $request, MapEntity $optio
}
}

private function getIdentifier(Request $request, MapEntity $options, ArgumentMetadata $argument): mixed
private function getIdentifier(Request $request, MapEntity $options, ArgumentMetadata $argument, ParameterBag $valueBag): mixed
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I can tell, the $valueBag parameter has effectively replaced the $request in most private methods you've touched here. Let's remove the now-unused parameter where we can.

Suggested change
private function getIdentifier(Request $request, MapEntity $options, ArgumentMetadata $argument, ParameterBag $valueBag): mixed
private function getIdentifier(ParameterBag $valueBag, MapEntity $options, ArgumentMetadata $argument): mixed

Comment on lines 347 to 360
public function testVariadicEnum()
{
// TODO
}

public function testVariadicUid()
{
// TODO
}

public function testVariadicDateTime()
{
// TODO
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🙃

@yceruto
Copy link
Member

yceruto commented Oct 30, 2024

I'd prefer FromPath instead of FromRoute.

@klkvsk
Copy link
Author

klkvsk commented Nov 11, 2024

Thank you all for comments.

@ro0NL

#[MapEntity(Foo::class, id: new MapQueryParameter('fooId'))]

General idea is to decouple #[From..] attributes from #[Map...] attributes, so in order to do it, I needed to make ValueResolvers for Entity, DateTime, Uid and other unaware of any #[From..] attributes. That is why they just receive a $valueBag ready for them to consume values from.

In latest commit I addressed the compatibility with variadic parameters. Now any ValueResolver work with variadic and non-variadic parameters the same way. They just being called resolveValue() multiple times if it's variadic.

Due to this, we no longer need VariadicValueResolver. It is deprecated.

Also, MapQueryParameter is deprecated, as FromQuery does the same thing, but more flexible, i.e. can be combined with mappers such as MapDateTime.

I think this PR is finalized in my point of view. There are couple things I'm not still very happy about:

  • had to keep RequestAttributeValueResolver named this way for a better BC (in case someone is disabling it with #[ValueResolver(RequestAttributeValueResolver::class, disabled: true)], but it should better be called RequestParameterValueResolver or SimpleValueResolver or something. Basically, it resolves any values not converted with higher-priority ValueResolvers, so mostly built-ins.
  • the EntityValueResolver is the only thing that required passing request to resolveValue due to it's use in expression. Otherwise, the resolveValue method signature would be cleaner without $request.

The thing I am happy about is that any custom ValueResolver can be converted with just a few lines of code.

@fabpot fabpot modified the milestones: 7.2, 7.3 Nov 20, 2024
@klkvsk klkvsk force-pushed the request-value-from branch from ef7e5e4 to 9e5b4ff Compare December 3, 2024 20:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants