Skip to content

Commit 7fc9fe8

Browse files
author
Iltar van der Berg
committed
Added docs about ArgumentValueResolvers
1 parent e89e5f1 commit 7fc9fe8

File tree

3 files changed

+159
-0
lines changed

3 files changed

+159
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
.. index::
2+
single: Controller; Argument Value Resolvers
3+
4+
Extending Action Argument Resolving
5+
===================================
6+
7+
.. versionadded:: 3.1
8+
The ``ArgumentResolver`` and value resolvers are added in Symfony 3.1.
9+
10+
In the book, you've learned that you can add the :class:`Symfony\\Component\\HttpFoundation\\Request`
11+
as action argument and it will be injected into the method. This is done via the
12+
:class:`Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver`. The ``ArgumentResolver`` uses
13+
several value resolvers which allow you to extend the functionality.
14+
15+
16+
Functionality Shipped With The HttpKernel
17+
-----------------------------------------
18+
19+
Symfony ships with four value resolvers in the HttpKernel:
20+
- The :class:`Symfony\\Component\\HttpKernel\\Controller\\ArgumentValueResolver\\ArgumentFromAttributeResolver`
21+
attempts to find a request attribute that matches the name of the argument.
22+
23+
- The :class:`Symfony\\Component\\HttpKernel\\Controller\\ArgumentValueResolver\\RequestValueResolver`
24+
injects the current ``Request`` if type-hinted with ``Request``, or a sub-class thereof.
25+
26+
- The :class:`Symfony\\Component\\HttpKernel\\Controller\\ArgumentValueResolver\\DefaultValueResolver`
27+
will set the default value of the argument if present and the argument is optional.
28+
29+
- The :class:`Symfony\\Component\\HttpKernel\\Controller\\ArgumentValueResolver\\VariadicValueResolver`
30+
verifies in the request if your data is an array and will add all of them to the argument list.
31+
When the action is called, the last (variadic) argument will contain all the values of this array.
32+
33+
.. note::
34+
In older versions of Symfony this logic was all resolved within the ``ControllerResolver``. The
35+
old functionality is moved to the ``LegacyArgumentResolver``, which contains the previously
36+
used resolving logic.
37+
38+
Adding a New Value Resolver
39+
---------------------------
40+
41+
Adding a new value resolver requires one class and one service defintion. In our next example, we
42+
will be creating a shortcut to inject the ``User`` object from our security. Given we write the following
43+
action::
44+
45+
namespace AppBundle\Controller;
46+
47+
class UserController
48+
{
49+
public function indexAction(User $user)
50+
{
51+
return new Response('<html><body>Hello '.$user->getUsername().'!</body></html>');
52+
}
53+
}
54+
55+
Somehow we will have to get the ``User`` object and inject it into our action. This can be done
56+
by implementing the :class:`Symfony\\Component\\HttpKernel\\Controller\\ArgumentValueResolverInterface`.
57+
This interface specifies that we have to implement two methods::
58+
59+
interface ArgumentValueResolverInterface
60+
{
61+
public function supports(Request $request, ArgumentMetadata $argument);
62+
public function resolve(Request $request, ArgumentMetadata $argument);
63+
}
64+
65+
- The ``supports()`` method is used to check whether the resolver supports the given argument. It will
66+
only continue if it returns ``true``.
67+
68+
- The ``resolve()`` method will be used to resolve the actual value just acknowledged by
69+
``supports()``. Once a value is resolved you can ``yield`` the value to the ``ArgumentResolver``.
70+
71+
- The ``Request`` object is the current ``Request`` which would also be injected into your
72+
action in the forementioned functionality.
73+
74+
- The :class:``Symfony\\Component\\HttpKernel\\ControllerMetadata\\ArgumentMetadata`` represents
75+
information retrieved from the method signature for the current argument it's trying to resolve.
76+
77+
.. note::
78+
The ``ArgumentMetadata`` is a simple data container created by the
79+
:class:``Symfony\\Component\\HttpKernel\\ControllerMetadata\\ArgumentMetadataFactory``. This
80+
factory will work on every supported php version but might give different results. E.g. the
81+
``isVariadic()`` will never return true on php 5.5 and only on php 7.0 and higher it will give
82+
you basic types when calling ``getType()``.
83+
84+
Now that we know what to do, we can implement this interface. In order to get the current ``User``,
85+
we will have to get it from the ``TokenInterface`` which is in the ``TokenStorageInterface``::
86+
87+
namespace AppBundle\ArgumentValueResolver;
88+
89+
use AppBundle\User;
90+
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
91+
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
92+
93+
class UserValueResolver implements ArgumentValueResolverInterface
94+
{
95+
private $tokenStorage;
96+
97+
public function __construct(TokenStorageInterface $tokenStorage)
98+
{
99+
$this->tokenStorage = $tokenStorage;
100+
}
101+
102+
public function supports(Request $request, ArgumentMetadata $argument)
103+
{
104+
return ($token = $this->tokenStorage->getToken()) && $token->getUser() instanceof User;
105+
}
106+
107+
public function resolve(Request $request, ArgumentMetadata $argument)
108+
{
109+
yield $this->tokenStorage->getToken()->getUser();
110+
}
111+
}
112+
113+
This was pretty simple, now all we have to do is add the configuration for the service container. This
114+
can be done by tagging the service with ``kernel.argument_resolver`` and adding a priority.
115+
116+
.. note::
117+
While adding a priority is optional, it's recommended to add one to make sure the expected
118+
value is injected. The ``ArgumentFromAttributeResolver`` has a priority of 100. As this
119+
one is responsible for fetching attributes from the ``Request``, it's also recommended to
120+
trigger your custom value resolver with a lower priority. This makes sure the argument
121+
resolvers are not triggered in (e.g.) subrequests if you pass your user along:
122+
``{{ render(controller('AppBundle:User:index', {'user', app.user})) }}``.
123+
124+
.. configuration-block::
125+
126+
.. code-block:: yaml
127+
128+
# app/config/services.yml
129+
services:
130+
app.value_resolver.user:
131+
class: AppBundle\ArgumentValueResolver\UserValueResolver
132+
arguments:
133+
- '@security.token_storage'
134+
tags:
135+
- { name: kernel.argument_resolver, priority: 50 }
136+
137+
.. code-block:: xml
138+
139+
<!-- app/config/services.xml -->
140+
<services>
141+
<service id="app.value_resolver.user" class="AppBundle\ArgumentValueResolver\UserValueResolver">
142+
<argument type="service" id="security.token_storage">
143+
<tag name="kernel.argument_resolver" priority="50" />
144+
</service>
145+
</services>
146+
147+
.. code-block:: php
148+
149+
// app/config/services.php
150+
use Symfony\Component\DependencyInjection\Definition;
151+
152+
$defintion = new Definition(
153+
'AppBundle\ArgumentValueResolver\UserValueResolver',
154+
array(new Reference('security.token_storage'))
155+
);
156+
$definition->addTag('kernel.argument_resolver', array('priority' => 50));
157+
$container->setDefinition('app.value_resolver.user', $definition);

cookbook/controller/index.rst

+1
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ Controller
77
error_pages
88
service
99
upload_file
10+
argument_value_resolver

cookbook/map.rst.inc

+1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
* :doc:`/cookbook/controller/error_pages`
5757
* :doc:`/cookbook/controller/service`
5858
* :doc:`/cookbook/controller/upload_file`
59+
* :doc:`/cookbook/controller/argument_value_resolver`
5960

6061
* **Debugging**
6162

0 commit comments

Comments
 (0)