Movatterモバイル変換


[0]ホーム

URL:


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

Sign up
Appearance settings

Added an ArgumentResolver with clean extension point#18308

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
fabpot merged 5 commits intosymfony:masterfromlinaori:feature/argument-resolver-extention-point
Apr 3, 2016
Merged

Added an ArgumentResolver with clean extension point#18308

fabpot merged 5 commits intosymfony:masterfromlinaori:feature/argument-resolver-extention-point
Apr 3, 2016

Conversation

@linaori
Copy link
Contributor

QA
Branch?master
Bug fix?no
New feature?yes
BC breaks?no
Deprecations?yes
Tests pass?yes
Fixed tickets#17933 (pre-work),#1547,#10710
LicenseMIT
Doc PRsymfony/symfony-docs#6422

This PR is a follow up for and blocked by:#18187, relates to#11457 by@wouterj. When reviewing, please take the last commit:Added an ArgumentResolver with clean extension point

This PR provides:

  • The ability to tag your ownArgumentValueResolverInterface. This means that you can effectively expand on the argument resolving in theHttpKernel without having to implement your ownArgumentResolver.
  • The possibility to cache away argument metadata via a newArgumentMetadataFactory which simply fetches the data from the cache, effectively omitting 1 reflection call per request.Not implemented in this PR, but possible once this is merged.
  • The possibility to add a PSR-7 adapter to resolve the correct request, avoids the paramconverters
  • The possibility to add a value resolver to fetch stuff from $request->query
  • Drupal could simplifytheir argument resolving by a lot
  • etc.

The aim for this PR is to provide a 100% BC variant to add argument resolving in a clean way, this is shown by the 2 tests:LegacyArgumentResolverTest andArgumentResolverTest.

/cc@dawehner@larowlan if you have time, can you check the impact for Drupal? I think this should be a very simple change which should make it more maintainable.

Nicofuma reacted with thumbs up emoji
public function process(ContainerBuilder $container)
{
$definition = $container->getDefinition('argument_resolver');
$argument_resolvers = $this->findAndSortTaggedServices('controller_argument.value_resolver', $container);
Copy link
Contributor

Choose a reason for hiding this comment

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

Variable should be camel cased.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

Woops, will fix this, thanks!

@Koc
Copy link
Contributor

Koc commentedMar 25, 2016

What benefits ofArgumentMetadata class instead of passingReflectionProperty?https://github.com/symfony/symfony/pull/11457/files#diff-e00e36af5c58d36a069a385025be4f34R22 looks better than yourArgumentIsRequest

*/
public function getValue(Request $request, ArgumentMetadataInterface $argument)
{
return $request->attributes->all()[$argument->getArgumentName()];
Copy link
Contributor

Choose a reason for hiding this comment

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

get() instead ofall()[]?

@Koc
Copy link
Contributor

Koc commentedMar 25, 2016

Also#11457 wasn't merged because of lack of performance tests. Maybe you should add blackfire comparation before and after.

@linaori
Copy link
ContributorAuthor

@Koc for the simple reason that it's reflection. When this PR is merged, I can add a cache warmer that calculates all this information already so no reflection would be required run-time (unless not in cache).

Additionally it also avoids PHP_VERSION_ID checks because types and variadic differs between 5.5, 5.6 and 7.0, see6496e67#diff-4cbcb4a4736436b811aa3f5059ca46b1R19

@linaori
Copy link
ContributorAuthor

@Koc how would I do that? I have to admit that I've never used blackfire yet. In terms of performance this might actually be slightly slower (minimally) until a cached variant has been added.

*/
public function process(ContainerBuilder $container)
{
$definition = $container->getDefinition('argument_resolver');
Copy link
Member

Choose a reason for hiding this comment

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

first, check if the definition exists and return early otherwise.

@Koc
Copy link
Contributor

Koc commentedMar 25, 2016

https://blackfire.io/docs/up-and-running/installation
http://blog.blackfire.io/profiles-public-sharing.html

Create simple app based on symfony-standard, create profile, replace symfony's version with your patched, create profile again and compare them.

@linaori
Copy link
ContributorAuthor

@Koc alright, I will see if I can do that

*/
public function supports(Request $request, ArgumentMetadataInterface $argument)
{
return $argument->hasDefaultValue() && !$request->attributes->has($argument->getArgumentName());
Copy link
Contributor

Choose a reason for hiding this comment

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

Is second check really needed?

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

It's pretty much a failsafe for when theArgumentFromAttribute is triggered after this one. In the framework bundle I've solved this with priorities but here I'd like to make sure it's always triggered only when no default could be found. This would be equal to the if/else check in theLegacyArgumentResolver

@linaori
Copy link
ContributorAuthor

@Koc I made an application that can be testedhttps://github.com/iltar/blackfire-symfony-18308

Sadly I cannot run blackfire here due to system permissions.

<argumenttype="collection" />
</service>

<serviceid="argument_metadata_actory"class="Symfony\Component\HttpKernel\ControllerMetadata\Argument\ArgumentMetadataFactory"public="false" />
Copy link
Member

Choose a reason for hiding this comment

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

typoargument_metadata_factory

@fabpot
Copy link
Member

This is an interesting topic as we've been talking about doing this kind of changes for years now.

Having more flexibility looks good on paper but we need to keep complexity as low as possible (that's why I'm quite reluctant on adding interfaces and factories when not needed -- see my comments). Also, we need to keep good performance. This last point is probably the only blocker if any. Can you profile the old and the new code on a "typical Symfony app" (not an hello world one) to see if there is an impact and what kind if impact these changes have if any?

@linaori
Copy link
ContributorAuthor

@fabpot I'll hook it up somewhere this week and check the performance. Ideally I'd also implement a cache for the benchmarks (because that's what this is all pre-work for).

I agree about the complexity, that's why I want to keep this as simple as possible. Here's a list of the classes/interfaces and what they are designed for:

  • ArgumentResolver(Interface); This is from the previous PR, making it easier to implement your own variant (this PR). This also allows theLegacyArgumentResolver (currently inControllerResolver) to be injected.
  • ArgumentValueResolverInterface; This is what can actually be used on an application level for action arguments, think about:ParamConverter to inject the current user #327. Parameter converters are nice, but slow(er than when this is cached) due to their reflection. This involves less magic to implement and a clear trace for developers where it hooks in. It also avoids injecting objects in the Request object, scoping them more effectively.
  • ArgumentMetadataInterface; As I have commented before, this is a nice way to use the following bullet point to return a cached variant if generating classes.
  • ArgumentMetadataFactoryInterface; This allows multiple implementations to accommodate a cache layer. My idea is to decorate the real-time resolver, when the cached version does not exist, I can fall back to the real-time and still provide the same functionality. I think this interface should be marked with@internal but I'm not 100% sure, in case someone wants to provide their own layer (like never resolving due to performance?).

Regarding the cache implementation I want to open a new PR so it can focus on that specific subject as I'm inexperienced with caching.

Pictor13 reacted with thumbs up emoji

@fabpot
Copy link
Member

Caching should indeed be part of another PR. Performance without caching should stay in the same range as the current performance. Adding a cache layer is adding another layer of complexity by itself and won't be implemented by everyone (think Silex for instance). That's why performance optimizationwithout caching is very important.

But then again, without numbers, we cannot really reason about the proposed changes.

@linaori
Copy link
ContributorAuthor

You've got a fair point there. In any case, I could rename theLegacyArgumentResolver to something else and make sure everyone can keep using that regarding performance.

@javiereguiluz
Copy link
Member

@iltar thanks for working on this. Although I cannot provide specific details, I agree with Fabien and this looks a bit over engineered. The fact that adds +1,200 lines of code and removes just 111 might be an warning about this.

@linaori
Copy link
ContributorAuthor

@fabpot here are the results of my real-world application with the code as-is in this PR.

@javiereguiluz a lot of those lines are actually test duplication to provide 100% BC and a lot of those lines are also comments because a lot of small files got added. If you look at executable lines of code excluding tests, the difference is a lot less.

I agree that it's a bit of complexity added, but it would solve a lot of other problems; Mainly DX wise. Right now it's a pain to customize this code and is highly dependent on the default implementation. With this PR (e.g.)Drupal could significantly reduce the complexity in their code if they want. It also means that they would not have to backport the current fix for the variadic functionality either.

It means that I could add the following code instead of aParamConverter

/**     * {@inheritdoc}     */publicfunctionsupports(Request$request,ArgumentMetadataInterface$argument)    {return$argument->getArgumentType() === MyUser::class;    }/**     * {@inheritdoc}     */publicfunctiongetValue(Request$request,ArgumentMetadataInterface$argument)    {return$this->tokenStorage->getToken()->getUser();    }

The initial overhead is a bit more, but aParamConverter is conceptually wrong here and causes a lot more overhead as it uses reflection to determine the same information yet again. For Developers it will be easier


Conclusion: it's slower in this PR, not by much but it is slower. The difference with 0 parameters is minimal and gains ~initial time*N Arguments. I think the added DX and flexibility is worth the minimal overhead, especially when you can avoid some of the magic done byParamConverters.

note that if this functionality is not desired in the core, I can always publish it myself if#18187 gets accepted

// code used in HttpKernel$sw =newStopwatch();$sw->start('18308');// controller argumentsfor ($i =0;$i <1000;$i++) {$arguments =$this->argumentResolver->getArguments($request,$controller);}$event =$sw->stop('18308');$class =explode('\\',get_class($controller[0]));dump(end($class).':'.$event.'; arguments:'.count($arguments).','.$i.' iteration(s); branch: feature/argument-resolver-extention-point');

1 iteration on getArguments (master)

1 iteration on getArguments (feature/argument-resolver-extention-point)

1000 iterations on getArguments (master)

1000 iterations on getArguments (feature/argument-resolver-extention-point)

Same as the above but changed the variadic priority to -150 (as it cannot have a default value and is a rare case)

Pictor13 reacted with thumbs up emoji

@HeahDude
Copy link
Contributor

@iltar having separated commits will certainly ease reviews, but once done you should squash mines, they are not relevant. Thank you ;)

@linaori
Copy link
ContributorAuthor

Docs PR is made regarding the controller resolver / argument resolver, so that should be ready before 3.1 arrives. I will start working on writing something for the new extension point (otherwise it's pretty useless imo).

@linaori
Copy link
ContributorAuthor

@fabpot I'm having doubts about the resolvers:

service (prefixed byargument_value_resolver.)class
...argument_from_attributeArgumentFromAttributeResolver
...argument_is_requestDefaultArgumentValueResolver
...default_argument_valueRequestResolver
...variadic_argument_from_attributeVariadicArgumentValueResolver

I would like to propose the following naming before this is merged:

serviceclass
argument_value_resolver.request_attributeRequestAttributeValueResolver
argument_value_resolver.defaultDefaultValueResolver
argument_value_resolver.requestRequestValueResolver
argument_value_resolver.variadicVariadicValueResolver

While writing the docs I noticed it's still fairly inconsistent and a lot to write.

@javiereguiluz
Copy link
Member

@iltar don't you think the_value_ part inargument_value_resolver.* is a bit redundant? What about:

serviceclass
argument_resolver.request_attributeRequestAttributeValueResolver
argument_resolver.defaultDefaultValueResolver
argument_resolver.requestRequestValueResolver
argument_resolver.variadicVariadicValueResolver
HeahDude and unexge reacted with thumbs up emoji

@fabpot
Copy link
Member

the new names are indeed much more consistent. I also like@javiereguiluz shorter proposal. Up to you between the 2 new proposals.

@linaori
Copy link
ContributorAuthor

I like that, less is more in this case. I was running into another DX issue; when showing theArgumentResolver in the documentation, I always have to pass an array of 4 items which together mimic the 3.0 behaviour. If I were to make the argument of the value resolvers nullable, I could set those 4 resolvers by default in the constructor. Or is an empty array enough? This implies you cannot ever have no resolvers.

@linaori
Copy link
ContributorAuthor

Okay, small update:

  • TheArgumentResolver arguments were optional (don't know why, lost in rebase). I've made use of that and initialized the objects with the defaults that symfony is using now.
  • The HttpKernel now gives a slightly different deprecation about a behavioral change in 4.0 (BC break)
  • DX is improved for people who don't know anything about the inner workings of theArgumentResolver, it now works out of the box with the current default behavior.

namespaceSymfony\Component\HttpKernel\Controller;

useSymfony\Component\HttpFoundation\Request;
useSymfony\Component\HttpKernel\Controller\ArgumentValueResolver\DefaultValueResolver;
Copy link
Contributor

Choose a reason for hiding this comment

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

Is the term "value" really needed in namespace and classes ?

It could be:

..\ArgumentResover\DefaultArgumentResolver..\ArgumentResover\RequestArgumentResolver..\ArgumentResover\VariadicArgumentResolver...

?

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

That would imply they areArgumentResolver implementations, which they are not, hence I named them aValueResolver. What I could do, is remove it from the namespace as they are tightly coupled to theArgumentResolver.

Pictor13 reacted with thumbs up emojiHeahDude reacted with heart emoji
Copy link
Member

Choose a reason for hiding this comment

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

LikeSymfony\Component\HttpKernel\Controller\ArgumentResolver\RequestAttributeValueResolver? Sounds good

Copy link
Contributor

Choose a reason for hiding this comment

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

👍

@linaori
Copy link
ContributorAuthor

@fabpot I think it's ready to be merged, all tests are passing without issues and I think most edge-cases are covered now.

new RequestValueResolver(),
new DefaultValueResolver(),
new VariadicValueResolver(),
);
Copy link
Member

Choose a reason for hiding this comment

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

Elsewhere, we would also have anadd() method to be able to add more value resolver. But this logic here forbids to have one. What about (and I know it's going to be controversial) removing thearray typehint, make the default value tonull and only automatically register the default revolsers when the value isnull?

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

The current method of adding them is via a compiler pass, you tag it, gets collected and then injected into the constructor. I'm personally in favour of this method to avoid 4+ method calls each webrequest

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

Now that I'm not in the middle of nowhere with an actual keyboard...

This compiler pass does all the logic:https://github.com/symfony/symfony/pull/18308/files#diff-6df4a64da7596af5827901ecbc5d2e78R18

  • If not given, same behavior as theLegacyArgumentResolver
  • Enhanced experience without theFrameworkBundle wiring everything via services
  • Easily extendable because you only need to tag your service withcontroller_argument.value_resolver

In your service.yml you would only have to do this:

services:app.argument_resolver.user:class:App\ArgumentResolver\UserValueResolverarguments:            -"@security.token_storage"tags:            -{ name: controller_argument.value_resolver, priority: 150 }

The compiler pass will simply generate an array and replace the second argument to avoid the aforementioned method calls.

Copy link
Member

Choose a reason for hiding this comment

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

I totally understand how that works for Symfony full-stack, I was more thinking about the integration with Silex or Drupal where the FrameworkBundle is not available. Anyway, I'm going to merge like this and we still have 2 months to see how to deal with that.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

What could be done, is move the compiler pass to the component, but I'm not sure if silex uses this

Copy link
Contributor

Choose a reason for hiding this comment

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

AFAIK Silex does not use Config and DependencyInjection components by default.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

Once this is merged, let's see if we can find a nice solution for those cases because ideally I'd like to support that before 3.1 is released.

Copy link
Member

Choose a reason for hiding this comment

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

If we were to allow to add value resolvers after the argument resolver has been created, we should imo move the priority handling to this class instead of doing that in the compiler pass (i.e. adding a method likeaddArgumentValueResolver(ArgumentValueResolver $resolver, $priority)).

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

Now we have the advantage of little overhead because everything is compiled, I'm personally for this approach because it feels safer

Copy link
Member

Choose a reason for hiding this comment

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

I don't think we should allow to add value resolver after the argument resolver has been created. 👍 for the current way

@fabpot
Copy link
Member

Apart from my comment about the default value resolvers, I'm 👍 to merge this PR.

* The`ControllerResolver::getArguments()` method has been deprecated and will
be removed in 4.0. If you have your own`ControllerResolverInterface`
implementation, you should inject either an`ArgumentResolverInterface`
instance or the new`ArgumentResolver` in the`HttpKernel`.
Copy link
Member

Choose a reason for hiding this comment

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

something similar (but then removed instead of deprecated) should be added to UPGRADE-4.0.md

HeahDude reacted with thumbs up emoji
Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

Wouldn't it be easier to document this in the PR where it's actually removed?

@fabpot
Copy link
Member

Thank you@iltar.

@fabpotfabpot merged commit1bf80c9 intosymfony:masterApr 3, 2016
fabpot added a commit that referenced this pull requestApr 3, 2016
…iltar, HeahDude)This PR was merged into the 3.1-dev branch.Discussion----------Added an ArgumentResolver with clean extension point| Q             | A| ------------- | ---| Branch?       | master| Bug fix?      | no| New feature?  | yes| BC breaks?    | no| Deprecations? | yes| Tests pass?   | yes| Fixed tickets |#17933 (pre-work),#1547,#10710| License       | MIT| Doc PR        |symfony/symfony-docs#6422**This PR is a follow up for and blocked by:#18187**, relates to#11457 by@wouterj. When reviewing, please take the last commit: [Added an ArgumentResolver with clean extension point](4c092b3)This PR provides:- The ability to tag your own `ArgumentValueResolverInterface`. This means that you can effectively expand on the argument resolving in the `HttpKernel` without having to implement your own `ArgumentResolver`.- The possibility to cache away argument metadata via a new `ArgumentMetadataFactory` which simply fetches the data from the cache, effectively omitting 1 reflection call per request. *Not implemented in this PR, but possible once this is merged.*- The possibility to add a PSR-7 adapter to resolve the correct request, avoids the paramconverters- The possibility to add a value resolver to fetch stuff from $request->query- Drupal could simplify [their argument resolving](https://github.com/drupal/drupal/blob/8.1.x/core/lib/Drupal/Core/Controller/ControllerResolver.php) by a lot- etc.The aim for this PR is to provide a 100% BC variant to add argument resolving in a clean way, this is shown by the 2 tests: `LegacyArgumentResolverTest` and `ArgumentResolverTest`./cc@dawehner@larowlan if you have time, can you check the impact for Drupal? I think this should be a very simple change which should make it more maintainable.Commits-------1bf80c9 Improved DX for the ArgumentResolverf29bf4c Refactor ArgumentResolverTestcee5106 cs fixescfcf764 Added an ArgumentResolver with clean extension point360fc5f Extracting arg resolving from ControllerResolver
@HeahDude
Copy link
Contributor

🎉 Thanks@iltar !

@linaori
Copy link
ContributorAuthor

Couldn't have finished it this polished without you guys, thanks!

fabpot added a commit that referenced this pull requestApr 28, 2016
This PR was merged into the 3.1-dev branch.Discussion----------Fixed a redundant check in DefaultValueResolver| Q             | A| ------------- | ---| Branch?       | master| Bug fix?      | no| New feature?  | no| BC breaks?    | no| Deprecations? | no| Tests pass?   | yes| Fixed tickets | ~| License       | MIT| Doc PR        | ~In#18308 I have introduced a `DefaultValueResolver`. When writing documentation, I was planning on adding the code as an example and I noticed it did a check in the request attributes. A default value value should always be injected, whether the request has it or not. In case the request _does_ have the value, it would've already been added and thus never reach the default resolver.Thus as this is never called in the default and configured flows and should not change the default value behavior, I'm removing this.Commits-------e54c1a6 Fixed a redundant check in DefaultValueResolver
@fabpotfabpot mentioned this pull requestMay 13, 2016
wouterj added a commit to symfony/symfony-docs that referenced this pull requestJun 11, 2016
This PR was submitted for the master branch but it was merged into the 3.1 branch instead (closes#6438).Discussion----------Added docs about ArgumentValueResolvers| Q             | A| ------------- | ---| Doc fix?      | no| New docs?     | yes| Applies to    | 3.1| Fixed tickets | ~Adds the documentation for the new `ArgumentValueResolver` feature fromsymfony/symfony#18308.Commits-------f22dc96 Added docs about ArgumentValueResolvers
wouterj added a commit to symfony/symfony-docs that referenced this pull requestJul 5, 2016
…olver (iltar)This PR was submitted for the master branch but it was merged into the 3.1 branch instead (closes#6422).Discussion----------Documented the ArgumentResolver along the ControllerResolver| Q             | A| ------------- | ---| Doc fix?      | yes| New docs?     | no ~symfony/symfony#18308| Applies to    | 3.1| Fixed tickets | ~The ArgumentResolver is used now instead of the ControllerResolver. I have yet to document the extension point but first I want to have this page mention it.Commits-------11920e3 Documented the ArgumentResolver along the ControllerResolver
@linaorilinaori deleted the feature/argument-resolver-extention-point branchFebruary 8, 2019 13:38
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment

Reviewers

No reviews

Assignees

No one assigned

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

14 participants

@linaori@Koc@fabpot@javiereguiluz@HeahDude@stof@stloyd@MacDada@wouterj@jvasseur@xabbuh@trousers@GuilhemN@carsonbot

[8]ページ先頭

©2009-2025 Movatter.jp