-
-
Notifications
You must be signed in to change notification settings - Fork 5.2k
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
Added docs about ArgumentValueResolvers #6438
Changes from 13 commits
7fc9fe8
7ddf5d6
1a19d2e
f491199
a07fcc3
2e41c07
7cdc96b
7d00d8c
f9cbe71
4c6ed2a
9784406
e3d1b48
55a87b5
8d30575
dd225e8
ca014a6
14d77e1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,234 @@ | ||
.. index:: | ||
single: Controller; Argument Value Resolvers | ||
|
||
Extending Action Argument Resolving | ||
=================================== | ||
|
||
.. versionadded:: 3.1 | ||
The ``ArgumentResolver`` and value resolvers were introduced in Symfony 3.1. | ||
|
||
In the book, you've learned that you can get the :class:`Symfony\\Component\\HttpFoundation\\Request` | ||
object via an argument in your controller. This argument has to be typehinted | ||
by the ``Request`` class in order to be recognized. This is done via the | ||
:class:`Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver`. By | ||
creating and registering custom argument value resolvers, you can extend | ||
this functionality. | ||
|
||
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. Extra empty line |
||
Functionality Shipped With The HttpKernel | ||
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. with 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. Shouldn't it be something like this then: 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. You're right. |
||
----------------------------------------- | ||
|
||
Symfony ships with four value resolvers in the HttpKernel: | ||
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. [...] in the HttpKernel component |
||
|
||
:class:`Symfony\\Component\\HttpKernel\\Controller\\ArgumentValueResolver\\ArgumentFromAttributeResolver` | ||
Attempts to find a request attribute that matches the name of the argument. | ||
|
||
:class:`Symfony\\Component\\HttpKernel\\Controller\\ArgumentValueResolver\\RequestValueResolver` | ||
Injects the current ``Request`` if type-hinted with ``Request``, or a | ||
sub-class thereof. | ||
|
||
:class:`Symfony\\Component\\HttpKernel\\Controller\\ArgumentValueResolver\\DefaultValueResolver` | ||
Will set the default value of the argument if present and the argument | ||
is optional. | ||
|
||
:class:`Symfony\\Component\\HttpKernel\\Controller\\ArgumentValueResolver\\VariadicValueResolver` | ||
Verifies in the request if your data is an array and will add all of | ||
them to the argument list. When the action is called, the last (variadic) | ||
argument will contain all the values of this array. | ||
|
||
.. note:: | ||
|
||
Prior to Symfony 3.1, this logic was resolved within the ``ControllerResolver``. | ||
The old functionality is rewritten to the aforementioned value resolvers. | ||
|
||
Adding a Custom Value Resolver | ||
------------------------------ | ||
|
||
Adding a new value resolver requires one class and one service defintion. | ||
In the next example, you'll create a value resolver to inject the ``User`` | ||
object from the security system. Given you write the following action:: | ||
|
||
namespace AppBundle\Controller; | ||
|
||
use AppBundle\Entity\User; | ||
use Symfony\Component\HttpFoundation\Response; | ||
|
||
class UserController | ||
{ | ||
public function indexAction(User $user) | ||
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 understand correctly comments below, you should add "= null" in the signature, right ? 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. Only when it's optional |
||
{ | ||
return new Response('<html><body>Hello '.$user->getUsername().'!</body></html>'); | ||
} | ||
} | ||
|
||
Somehow you will have to get the ``User`` object and inject it into the controller. | ||
This can be done by implementing the :class:`Symfony\\Component\\HttpKernel\\Controller\\ArgumentValueResolverInterface`. | ||
This interface specifies that you have to implement two methods:: | ||
|
||
interface ArgumentValueResolverInterface | ||
{ | ||
public function supports(Request $request, ArgumentMetadata $argument); | ||
public function resolve(Request $request, ArgumentMetadata $argument); | ||
} | ||
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 would remove the PHP code of the interface. |
||
|
||
``supports()`` | ||
This method is used to check whether the value resolver supports the | ||
given argument. ``resolve()`` will only be executed when this returns ``true``. | ||
``resolve()`` | ||
This method will resolve the actual value for the argument. Once the value | ||
is resolved, you must `yield`_ the value to the ``ArgumentResolver``. | ||
|
||
Both methods get the ``Request`` object, which is the current request, and an | ||
:class:`Symfony\\Component\\HttpKernel\\ControllerMetadata\\ArgumentMetadata`. | ||
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 would add "instance" in this sentence: "Both methods get [...] and an |
||
This object contains all information retrieved from the method signature for | ||
the current argument. | ||
|
||
.. note:: | ||
|
||
The ``ArgumentMetadata`` is a simple data container created by the | ||
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. there should be an empty line between the directive start ( |
||
:class:`Symfony\\Component\\HttpKernel\\ControllerMetadata\\ArgumentMetadataFactory`. | ||
This factory will work on every supported PHP version but might give | ||
different results. E.g. the ``isVariadic()`` will never return true on | ||
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. [...] |
||
PHP 5.5 and only on PHP 7.0 and higher it will give you basic types when | ||
calling ``getType()``. | ||
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. Not sure if we really need the whole note here. 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. The information is more of an in-depth description of how it works, I'll remove it for now as this is not important for the reader. |
||
|
||
Now that you know what to do, you can implement this interface. To get the | ||
current ``User``, you need the current security token. This token can be | ||
retrieved from the token storage:: | ||
|
||
namespace AppBundle\ArgumentValueResolver; | ||
|
||
use AppBundle\Entity\User; | ||
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; | ||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; | ||
|
||
class UserValueResolver implements ArgumentValueResolverInterface | ||
{ | ||
private $tokenStorage; | ||
|
||
public function __construct(TokenStorageInterface $tokenStorage) | ||
{ | ||
$this->tokenStorage = $tokenStorage; | ||
} | ||
|
||
public function supports(Request $request, ArgumentMetadata $argument) | ||
{ | ||
if (User::class !== $argument->getType()) { | ||
return false; | ||
} | ||
|
||
$token = $this->tokenStorage->getToken(); | ||
|
||
if (!$token instanceof TokenInterface) { | ||
return false; | ||
} | ||
|
||
return $token->getUser() instanceof User; | ||
} | ||
|
||
public function resolve(Request $request, ArgumentMetadata $argument) | ||
{ | ||
yield $this->tokenStorage->getToken()->getUser(); | ||
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. yield can be pretty new to our readers. Maybe we should add a short description about what it does? 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 could add a reference to the php.net docs with one of those nice footnote features rst has 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. The 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. What if there is no token or no user? This should probably return null, which means that you must have a 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. In that case the default value resolver will handle it 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. Right, so I think it deserves a comment to explain this behavior. And adding 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. Would you want it additionally in the docblock or just in here? |
||
} | ||
} | ||
|
||
In order to get the actual ``User`` object in your argument, the given value | ||
must fulfill the following requirements: | ||
|
||
* An argument must be typehinted as ``User`` in your action method signature; | ||
* A security token must be present; | ||
* The value must be an instance of the ``User``. | ||
|
||
When all those requirements are met and true is returned, the ``ArgumentResolver`` | ||
calls ``resolve()`` with the same values as it called ``supports()``. | ||
|
||
That's it! Now all you have to do is add the configuration for the service | ||
container. This can be done by tagging the service with ``kernel.argument_resolver`` | ||
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.
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. Did a quick check on this string, I seem to have mistaken it more often, but found 0 other usages luckily |
||
and adding a priority. | ||
|
||
.. note:: | ||
|
||
While adding a priority is optional, it's recommended to add one to | ||
make sure the expected value is injected. The ``ArgumentFromAttributeResolver`` | ||
has a priority of 100. As this one is responsible for fetching attributes | ||
from the ``Request``, it's also recommended to trigger your custom value | ||
resolver with a lower priority. This makes sure the argument resolvers | ||
are not triggered in (e.g.) subrequests if you pass your user along: | ||
``{{ render(controller('AppBundle:User:index', {'user', app.user})) }}``. | ||
|
||
.. configuration-block:: | ||
|
||
.. code-block:: yaml | ||
|
||
# app/config/services.yml | ||
services: | ||
app.value_resolver.user: | ||
class: AppBundle\ArgumentValueResolver\UserValueResolver | ||
arguments: | ||
- '@security.token_storage' | ||
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.
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. Using single quotes is better in Yaml 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. Would be curious for the reason: not that I don't agree with it but I always thought there was absolutely not difference. Besides it's quite inconsistent (or at least used to be I didn't check for a while now) in Symfony/Symfony docs. 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. Here you can find more details about single/double quotes: http://symfony.com/doc/current/components/yaml/yaml_format.html 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. @javiereguiluz my comment was maybe a bit misleading: I'm aware of the differences, I was only talking for when both were giving the same result as why to pick simple quotes over double ones. 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. @theofidry the link actually says:
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. @theofidry we recently updated most double quotes in Yaml to single quotes (for consistency). Using double quotes (or advocating using them) leads to problems with certain strings that are valid in PHP double quotes. For instance, |
||
tags: | ||
- { name: controller.argument_value_resolver, priority: 50 } | ||
|
||
.. code-block:: xml | ||
|
||
<!-- app/config/services.xml --> | ||
<?xml version="1.0" encoding="UTF-8" ?> | ||
<container xmlns="http://symfony.com/schema/dic/services" | ||
xmlns:xsi="'http://www.w3.org/2001/XMLSchema-Instance" | ||
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> | ||
|
||
<services> | ||
<service id="app.value_resolver.user" class="AppBundle\ArgumentValueResolver\UserValueResolver"> | ||
<argument type="service" id="security.token_storage"> | ||
<tag name="controller.argument_value_resolver" priority="50" /> | ||
</service> | ||
</services> | ||
|
||
</container> | ||
|
||
.. code-block:: php | ||
|
||
// app/config/services.php | ||
use Symfony\Component\DependencyInjection\Definition; | ||
|
||
$defintion = new Definition( | ||
'AppBundle\ArgumentValueResolver\UserValueResolver', | ||
array(new Reference('security.token_storage')) | ||
); | ||
$definition->addTag('controller.argument_value_resolver', array('priority' => 50)); | ||
$container->setDefinition('app.value_resolver.user', $definition); | ||
|
||
Creating an Optional User Resolver | ||
---------------------------------- | ||
|
||
When you want your user to be optional, e.g. when your page is behind a | ||
firewall that also allows anonymous authentication, you might not always | ||
have a security user. To get this to work, you only have to change your | ||
method signature to `UserInterface $user = null`. | ||
|
||
When you take the ``UserValueResolver`` from the previous example, you can | ||
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. Ok, thanks, it's definitely more clear. But the problem I was pointing (if there was one) still exists. Currently, the "previous example" requires a user (no default I think you should either make the user optional in the signature of the example, or remove the test of the returned value from the What do you think ? Question off topic, coming up while writing this: why 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. Regarding the name: "A supports B", hence I named it like that, is this incorrect?
If nothing can be resolved, the What I could do is grab the 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. Ok sorry, I missed the For the naming I used the same for formatters' interface in symfony/symfony#18450 but I was just wondering why this convention of an "s" for |
||
see there is no logic in case of failure to comply to the requirements. Default | ||
values are defined in the signature and are available in the ``ArgumentMetadata``. | ||
When a default value is available and there are no resolvers that support | ||
the given value, the ``DefaultValueResolver`` is triggered. This Resolver | ||
takes the default value of your argument and yields it to the argument list:: | ||
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. would link the yield reference here too 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. There is no "yield" that I can use though, the only word here referring to it is "yields" |
||
|
||
namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver; | ||
|
||
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. use statements are missing |
||
use Symfony\Component\HttpFoundation\Request; | ||
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; | ||
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; | ||
|
||
final class DefaultValueResolver implements ArgumentValueResolverInterface | ||
{ | ||
public function supports(Request $request, ArgumentMetadata $argument) | ||
{ | ||
return $argument->hasDefaultValue(); | ||
} | ||
|
||
public function resolve(Request $request, ArgumentMetadata $argument) | ||
{ | ||
yield $argument->getDefaultValue(); | ||
} | ||
} | ||
|
||
.. _`yield`: http://php.net/manual/en/language.generators.syntax.php |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,3 +7,4 @@ Controller | |
error_pages | ||
service | ||
upload_file | ||
argument_value_resolver |
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.
type-hinted