Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[HttpKernel] Fix ArgumentValueResolver for arguments default null #21333

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

Merged
merged 1 commit into from
Jan 19, 2017

Conversation

chalasr
Copy link
Member

@chalasr chalasr commented Jan 18, 2017

Q A
Branch? 2.7
Bug fix? yes
New feature? no
BC breaks? no
Deprecations? no
Tests pass? yes
Fixed tickets n/a
License MIT
Doc PR n/a

If an argument has null as default value and annotations are used for routing, then it is not resolved by the ArgumentResolver e.g:

public function showAction(Request $request) {
   dump($request); // Request object
}

public function showAction(?Request $request) {
   dump($request); // Request object
}

public function showAction(Request $request = null) {
   dump($request); // null
}

To me, the last example should have been a Request object too.

Note that it is the same behavior when using UserInterface or whatever, I ran into this issue while trying to dump a user for a route accepting both anonymous and authenticated requests, the argument was always null while $this->getUser() returned the expected value. According to http://symfony.com/blog/new-in-symfony-3-2-user-value-resolver-for-controllers it should have worked through the argument.

I propose to make it works as a bugfix.
Any suggestion for improving the patch will be really welcomed. ping @iltar

@chalasr
Copy link
Member Author

chalasr commented Jan 18, 2017

There is no BC break nor deprecation here, please remove the corresponding labels, my bad sorry.

@chalasr chalasr force-pushed the kernel/resolve-nullable-arguments branch 2 times, most recently from 50b4bc7 to d49390d Compare January 18, 2017 13:32
@@ -58,7 +58,7 @@ public function createArgumentMetadata($controller)
}

foreach ($reflection->getParameters() as $param) {
$arguments[] = new ArgumentMetadata($param->getName(), $this->getType($param), $this->isVariadic($param), $this->hasDefaultValue($param), $this->getDefaultValue($param), $param->allowsNull());
$arguments[] = new ArgumentMetadata($param->getName(), $this->getType($param), $this->isVariadic($param), $this->hasDefaultValue($param), $this->getDefaultValue($param), $this->isDefaultNull($param));
Copy link
Member Author

@chalasr chalasr Jan 18, 2017

Choose a reason for hiding this comment

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

ReflectionParameter::allowsNull() returns true for mandatory arguments which don't have any type hint i.e: function ($foo); // allowsNull(): true
Here we only want to know if the argument is default null, or has a typehint which allows null i.e. ?type

@chalasr chalasr force-pushed the kernel/resolve-nullable-arguments branch from d49390d to 0473c18 Compare January 18, 2017 13:46
@linaori
Copy link
Contributor

linaori commented Jan 18, 2017

How come it injects null instead of the Request? In theory the Request resolver should inject it as the default value one is triggered way later

@chalasr
Copy link
Member Author

chalasr commented Jan 18, 2017

Not sure but I believe the current implementation makes the Request resolver is just skipped so the default value wins. It can be reproduced with a fresh SE, changing the provided DefaultController::indexAction(Request $request) to DefaultController::indexAction(Request $request = null);

@linaori
Copy link
Contributor

linaori commented Jan 18, 2017

But that would mean that this code is not hit properly: https://github.com/symfony/symfony/blob/master/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/RequestValueResolver.php#L30-L38

Can you confirm this? Because this code is responsible for setting the request if it's supported and should work with the original setup (in theory).

@chalasr
Copy link
Member Author

chalasr commented Jan 18, 2017

@iltar I can confirm this since using indexAction(Request $request = null) and adding a dump($request);die; immediately before this line https://github.com/symfony/symfony/blob/master/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/RequestValueResolver.php#L30, the dump is never reached.

But right now I'm not able to say what exactly is wrong with the current implem, the patch made here avoids the only doubts I have about the origin of the issue (one being continue 2;) while covering all other cases that are working well originally, I was hoping you can say me more :/

@chalasr chalasr force-pushed the kernel/resolve-nullable-arguments branch from 0473c18 to 46758c7 Compare January 18, 2017 17:30
@linaori
Copy link
Contributor

linaori commented Jan 18, 2017

I'll try to reproduce it tomorrow. Maybe it's a sequence issue, because in theory this is only not called when something else already supports it and the continue 2; is hit. So if something else hits this first, there is probably another issue at hand.

@chalasr chalasr force-pushed the kernel/resolve-nullable-arguments branch from 46758c7 to 86931d5 Compare January 18, 2017 19:37
@chalasr
Copy link
Member Author

chalasr commented Jan 18, 2017

Maybe it's a sequence issue

Right! I looked deeper into and fact is that the RequestAttributeValueResolver is the culprit.
I updated this PR to give it a lower priority than the RequestValueResolver/SecurityUserValueResolver, but maybe there's something wrong in the request argument one that should be fixed instead.
Waiting for your feedback.

@chalasr chalasr changed the title [HttpKernel] Fix ArgumentResolver for argument with default null [HttpKernel] Fix ArgumentValueResolver registration order Jan 18, 2017
@linaori
Copy link
Contributor

linaori commented Jan 18, 2017

@chalasr is there a specific reason that this line is returning true? https://github.com/symfony/symfony/blob/master/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/RequestAttributeValueResolver.php#L30

Perhaps the 'request' key is created for some reason? Not sure why a null 'request' key would return true in a has here. Regarding your fix, if having the request in the attribute bag but as null is correct (which I kinda doubt), make sure the request attribute resolver is still called before the RequestValueResolver, due to the parameter converters

@chalasr
Copy link
Member Author

chalasr commented Jan 18, 2017

make sure the request attribute resolver is still called before the RequestValueResolver, due to the parameter converters

Did not considered that :/ then the current fix is not good, it just gives a lower priority to the request attribute resolver so it is called after the request/user resolver.

Perhaps the "request" key is created for some reason?

Right again! A request (variable name) key is put in the attribute bag with null as value. Same problem for the SecurityUserValueResolver given a user attribute with null as value exists, the RequestAttributeValueResolver takes precedence of course...

Not sure what's the proper way to fix this :/ Any idea @iltar?

@linaori
Copy link
Contributor

linaori commented Jan 18, 2017

I think we have to check why the attributes are created but empty, perhaps this is done for the parameter converters? An additional check could be to check if it has() the key and it's not empty

@chalasr
Copy link
Member Author

chalasr commented Jan 18, 2017

perhaps this is done for the parameter converters?

Not sure but there're good chances. Note that these attributes stay null at the end (from the controller itself) since I do not have any param converter explicitly configured

make sure the request attribute resolver is still called before the RequestValueResolver, due to the parameter converters

Could you please develop?
How calling the request/user resolvers before the request attribute one would be a problem for param converters? I mean if one sets up an argument value resolver for a given argument, then he should not rely on a param converter to populate it, should he?

An additional check could be to check if it has() the key and it's not empty

If you mean adding an additional check inside RequestAttributeValueResolver::supports() I think it could help indeed, I'd say a strict null !== $attribute though

@linaori
Copy link
Contributor

linaori commented Jan 18, 2017

The PC attribute resolver should be called first because that's how the behavior was before the argument value resolvers were introduced. if attributes.foo existed, 'foo' should be inserted first rather than trigger the PCs (when passing in templates for example).

edit: Not PC but attribute resolver should be called first

@chalasr chalasr force-pushed the kernel/resolve-nullable-arguments branch from 86931d5 to 426fafc Compare January 18, 2017 20:32
@chalasr
Copy link
Member Author

chalasr commented Jan 18, 2017

I updated this for reverting the previous changes and only adding the additional null !== $attribute check instead, it fixes the issue.
It would be great if you could have a look at it and confirm it won't cause any issue with PCs.

@chalasr chalasr changed the title [HttpKernel] Fix ArgumentValueResolver registration order [HttpKernel] Fix ArgumentValueResolver for arguments default null Jan 18, 2017

$argumentName = $argument->getName();

return $request->attributes->has($argumentName) && null !== $request->attributes->get($argumentName);
Copy link
Contributor

@linaori linaori Jan 18, 2017

Choose a reason for hiding this comment

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

Hmm I'm curious what happens if you have this:

$request->attributes->set('required', null);
public function fooAction(string $required);

edit: probably already broken/proper error in an old version?

Copy link
Member Author

@chalasr chalasr Jan 18, 2017

Choose a reason for hiding this comment

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

Using:

/**
 * @Route("/", name="homepage", defaults={"required": null})
 */
public function indexAction(string $required)
{
}

Controller "AppBundle\Controller\DefaultController::indexAction()" requires that you provide a value for the "$required" argument [...]

Changing the signature to indexAction(string $required = null); or indexAction(?string $required); works of course.

Same behavior on 3.0, run into the same exception if the attribute is null.

Copy link
Member

Choose a reason for hiding this comment

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

But this will now break the case where an attribute was set to null deliberately. Isn't that a BC break?

Copy link
Member Author

Choose a reason for hiding this comment

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

Indeed, it is...

Copy link
Contributor

Choose a reason for hiding this comment

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

With this signature it would fail already, you just simply can't set it to null anymore and it will trigger the resolver.

// call this with null, it will still trigger the resolver
public function fooAction(UserInterface $user = null);

But I'm still curious as of why the request values are null in the attribute bag.

@xabbuh regarding the above example, that would fail already. Also I don't think that values not mapped in the route should be affecting the parameter resolvers:

  • /{required} with (string $required) = allowed to be set to null but will throw an error as its required
  • / with (string $required) != allowed to set to null, as it was not added in the route definition

Copy link
Member Author

Choose a reason for hiding this comment

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

@iltar it happens only when the argument has a default, I'll look if avoiding this breaks something but I think you're right. If we can remove this magic then the resolver stays as it is and continue to work as expected in case the attribute is set explicitly

Copy link
Contributor

Choose a reason for hiding this comment

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

If you ask me, routing information should not be affected by arguments in the method that are not mapped in the route, but that's exactly what happens right now 😢

Copy link
Member

Choose a reason for hiding this comment

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

@iltar No, I mean a parameter without a type hint and without a default value.

Copy link
Member Author

@chalasr chalasr Jan 19, 2017

Choose a reason for hiding this comment

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

@xabbuh it could even break for ones that are type-hinted as objects with null as default value I think, which is what we are trying to fix. The current approach is definitely not the good one.

routing information should not be affected by arguments in the method that are not mapped in the route

I agree, but I don't know if it is expected somewhere internally (if someone has an hint?), nor where it happens

Copy link
Contributor

Choose a reason for hiding this comment

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

@xabbuh I agree on that point. If the compiled route would be without the default value, everything will work as expected

@chalasr
Copy link
Member Author

chalasr commented Jan 19, 2017

@iltar My bad, I forgot to revert it. The diff is clean now

@chalasr chalasr force-pushed the kernel/resolve-nullable-arguments branch from c9f527f to e31ad5a Compare January 19, 2017 09:55
@linaori
Copy link
Contributor

linaori commented Jan 19, 2017

Nice! 👍

So just for the confirmation, this behavior is only present with annotations?

@chalasr
Copy link
Member Author

chalasr commented Jan 19, 2017

Yes, it is only present for annotations, which make this bug exists only when using them.

Copy link
Contributor

@linaori linaori left a comment

Choose a reason for hiding this comment

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

Seems like a double fix to me

@chalasr chalasr force-pushed the kernel/resolve-nullable-arguments branch from e31ad5a to 2c808ca Compare January 19, 2017 14:41
@fabpot
Copy link
Member

fabpot commented Jan 19, 2017

The code you removed was introduced in 84adcb1 by @lyrixx. See #5904 for the associated PR.

@chalasr
Copy link
Member Author

chalasr commented Jan 19, 2017

@fabpot I found it and was about to add a comment for clarifying this patch after discussed it with @iltar and @xabbuh on slack.

The code removed here seems unnecessary, arguments with default values properly keep their default value even after removing it, same on 3.0.
Actually, it seems that this code sets attributes that are not needed, and it prevents the ArgumentResolver from resolving these arguments (since they have an equivalent request attribute which takes precedence), and this only occurs when using annotations.

@lyrixx Could you please confirm that this code is no more needed and that removing it in 2.7 would not break anything? The bug fixed here exists on 2.7 as well, before that argument value resolvers were introduced.

@fabpot
Copy link
Member

fabpot commented Jan 19, 2017

If the tests added back then by @lyrixx still work after the removal, I would say that this is safe to do on 2.7 instead of 3.1. Any drawbacks?

@chalasr chalasr changed the base branch from 3.1 to 2.7 January 19, 2017 16:02
@chalasr chalasr force-pushed the kernel/resolve-nullable-arguments branch from 2c808ca to f014cb6 Compare January 19, 2017 16:02
@chalasr chalasr force-pushed the kernel/resolve-nullable-arguments branch from f014cb6 to d3fa8a1 Compare January 19, 2017 16:03
@chalasr
Copy link
Member Author

chalasr commented Jan 19, 2017

Changed the target to 2.7. Tests seem green, let's see the build.

@lyrixx
Copy link
Member

lyrixx commented Jan 19, 2017

Could you please confirm that this code is no more needed and that removing it in 2.7 would not break anything? The bug fixed here exists on 2.7 as well, before that argument value resolvers were introduced.

Actually I don't know. The best way to know it is to test in a real project I guess.

@chalasr
Copy link
Member Author

chalasr commented Jan 19, 2017

Ok thanks, tried out on 2.7 and it works as expected (for the record: screenshot on a fresh 2.7).

@fabpot
Copy link
Member

fabpot commented Jan 19, 2017

Thank you @chalasr.

@fabpot fabpot merged commit d3fa8a1 into symfony:2.7 Jan 19, 2017
fabpot added a commit that referenced this pull request Jan 19, 2017
…lt null (chalasr)

This PR was merged into the 2.7 branch.

Discussion
----------

[HttpKernel] Fix ArgumentValueResolver for arguments default null

| Q             | A
| ------------- | ---
| Branch?       | 2.7
| Bug fix?      | yes
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | n/a
| License       | MIT
| Doc PR        | n/a

If an argument has `null` as default value __and annotations are used for routing__, then it is not resolved by the ArgumentResolver e.g:

```php

public function showAction(Request $request) {
   dump($request); // Request object
}

public function showAction(?Request $request) {
   dump($request); // Request object
}

public function showAction(Request $request = null) {
   dump($request); // null
}
```
To me, the last example should have been a Request object too.

Note that it is the same behavior when using `UserInterface` or whatever, I ran into this issue while trying to dump a user for a route accepting both anonymous and authenticated requests, the argument was always null while `$this->getUser()` returned the expected value. According to http://symfony.com/blog/new-in-symfony-3-2-user-value-resolver-for-controllers it should have worked through the argument.

I propose to make it works as a bugfix.
Any suggestion for improving the patch will be really welcomed. ping @iltar

Commits
-------

d3fa8a1 Avoid setting request attributes from signature arguments in AnnotationClassLoader
@chalasr chalasr deleted the kernel/resolve-nullable-arguments branch January 19, 2017 17:09
fabpot added a commit that referenced this pull request Jan 24, 2017
… attributes handling (chalasr)

This PR was merged into the 2.7 branch.

Discussion
----------

[Routing] Fix BC break in AnnotationClassLoader defaults attributes handling

| Q             | A
| ------------- | ---
| Branch?       | 2.7
| Bug fix?      | no
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | 77289b9#commitcomment-20572462
| License       | MIT
| Doc PR        | n/a

This fixes a BC break introduced in #21333. Instead of removing the automatic request attributes creation, we keep it but only for attributes that are mandatory (i.e. present in the route path).

Thanks to @iltar for the idea.

Commits
-------

1d298f0 [Routing] Fix BC break in AnnotationClassLoader defaults attributes handling
@fabpot fabpot mentioned this pull request Jan 28, 2017
This was referenced Feb 6, 2017
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.

6 participants