Skip to content

Commit b6bcc3d

Browse files
committed
[HttpKernel] Replace ArgumentValueResolverInterface by ValueResolverInterface
1 parent ad86890 commit b6bcc3d

File tree

2 files changed

+55
-113
lines changed

2 files changed

+55
-113
lines changed

_build/redirection_map

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -538,3 +538,4 @@
538538
/email /mailer
539539
/frontend/assetic /frontend
540540
/frontend/assetic/index /frontend
541+
/controller/argument_value_resolver /controller/value_resolver

controller/argument_value_resolver.rst renamed to controller/value_resolver.rst

Lines changed: 54 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ In the :doc:`controller guide </controller>`, you've learned that you can get th
99
your controller. This argument has to be type-hinted by the ``Request`` class
1010
in order to be recognized. This is done via the
1111
:class:`Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver`. By
12-
creating and registering custom argument value resolvers, you can extend this
12+
creating and registering custom value resolvers, you can extend this
1313
functionality.
1414

1515
.. _functionality-shipped-with-the-httpkernel:
@@ -168,132 +168,88 @@ PSR-7 Objects Resolver:
168168
Adding a Custom Value Resolver
169169
------------------------------
170170

171-
In the next example, you'll create a value resolver to inject the object that
172-
represents the current user whenever a controller method type-hints an argument
173-
with the ``User`` class::
171+
In the next example, you'll create a value resolver to inject an ID value
172+
object whenever a controller argument has a type implementing
173+
``IdentifierInterface`` (e.g. ``BookingId``)::
174174

175-
// src/Controller/UserController.php
175+
// src/Controller/BookingController.php
176176
namespace App\Controller;
177177

178-
use App\Entity\User;
178+
use App\Reservation\BookingId;
179179
use Symfony\Component\HttpFoundation\Response;
180180

181-
class UserController
181+
class BookingController
182182
{
183-
public function index(User $user)
183+
public function index(BookingId $id): Response
184184
{
185-
return new Response('Hello '.$user->getUserIdentifier().'!');
185+
// ... do something with $id
186186
}
187187
}
188188

189-
Beware that this feature is already provided by the `#[ParamConverter]`_
190-
attribute from the SensioFrameworkExtraBundle. If you have that bundle
191-
installed in your project, add this config to disable the auto-conversion of
192-
type-hinted method arguments:
189+
.. versionadded:: 6.2
193190

194-
.. configuration-block::
195-
196-
.. code-block:: yaml
197-
198-
# config/packages/sensio_framework_extra.yaml
199-
sensio_framework_extra:
200-
request:
201-
converters: true
202-
auto_convert: false
203-
204-
.. code-block:: xml
205-
206-
<!-- config/packages/sensio_framework_extra.xml -->
207-
<?xml version="1.0" encoding="UTF-8" ?>
208-
<container xmlns="http://symfony.com/schema/dic/services"
209-
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
210-
xmlns:sensio-framework-extra="http://symfony.com/schema/dic/symfony_extra"
211-
xsi:schemaLocation="http://symfony.com/schema/dic/services
212-
https://symfony.com/schema/dic/services/services-1.0.xsd
213-
http://symfony.com/schema/dic/symfony_extra
214-
https://symfony.com/schema/dic/symfony_extra/symfony_extra-1.0.xsd">
215-
216-
<sensio-framework-extra:config>
217-
<request converters="true" auto-convert="false"/>
218-
</sensio-framework-extra:config>
219-
</container>
220-
221-
.. code-block:: php
222-
223-
// config/packages/sensio_framework_extra.php
224-
$container->loadFromExtension('sensio_framework_extra', [
225-
'request' => [
226-
'converters' => true,
227-
'auto_convert' => false,
228-
],
229-
]);
191+
The ``ValueResolverInterface`` was introduced in Symfony 6.2. Prior to
192+
6.2, you had to use the
193+
:class:`Symfony\\Component\\HttpKernel\\Controller\\ArgumentValueResolverInterface`,
194+
which defines different methods.
230195

231196
Adding a new value resolver requires creating a class that implements
232-
:class:`Symfony\\Component\\HttpKernel\\Controller\\ArgumentValueResolverInterface`
233-
and defining a service for it. The interface defines two methods:
234-
235-
``supports()``
236-
This method is used to check whether the value resolver supports the
237-
given argument. ``resolve()`` will only be called when this returns ``true``.
238-
``resolve()``
239-
This method will resolve the actual value for the argument. Once the value
240-
is resolved, you must `yield`_ the value to the ``ArgumentResolver``.
197+
:class:`Symfony\\Component\\HttpKernel\\Controller\\ValueResolverInterface`
198+
and defining a service for it.
241199

242-
Both methods get the ``Request`` object, which is the current request, and an
200+
This interface contains a ``resolve()`` method, which is called for each
201+
argument of the controller. It receives the current ``Request`` object and an
243202
:class:`Symfony\\Component\\HttpKernel\\ControllerMetadata\\ArgumentMetadata`
244-
instance. This object contains all information retrieved from the method signature
245-
for the current argument.
203+
instance, which contains all information from the method signature. The
204+
method should return either an empty array (if it cannot resolve this
205+
argument) or an array with the resolved value(s).
246206

247-
Now that you know what to do, you can implement this interface. To get the
248-
current ``User``, you need the current security token. This token can be
249-
retrieved from the token storage::
207+
.. code-block:: php
250208
251-
// src/ArgumentResolver/UserValueResolver.php
252-
namespace App\ArgumentResolver;
209+
// src/ValueResolver/IdentifierValueResolver.php
210+
namespace App\ValueResolver;
253211
254-
use App\Entity\User;
255-
use Symfony\Bundle\SecurityBundle\Security\Security;
212+
use App\IdentifierInterface;
256213
use Symfony\Component\HttpFoundation\Request;
257-
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
214+
use Symfony\Component\HttpKernel\Controller\ValueResolverInterface;
258215
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
259216
260-
class UserValueResolver implements ArgumentValueResolverInterface
217+
class UserValueResolver implements ValueResolverInterface
261218
{
262-
private $security;
263-
264-
public function __construct(Security $security)
219+
public function resolve(Request $request, ArgumentMetadata $argument): array
265220
{
266-
$this->security = $security;
267-
}
268-
269-
public function supports(Request $request, ArgumentMetadata $argument): bool
270-
{
271-
if (User::class !== $argument->getType()) {
272-
return false;
221+
// get the argument type (e.g. BookingId)
222+
$argumentType = $argument->getType();
223+
if (
224+
!$argumentType
225+
|| !is_subclass_of($argumentType, IdentifierInterface::class, true)
226+
) {
227+
return [];
273228
}
274229
275-
return $this->security->getUser() instanceof User;
276-
}
230+
// get the value from the request, based on the argument name
231+
$value = $request->attributes->get($argument->getName());
232+
if (!is_string($value)) {
233+
return [];
234+
}
277235
278-
public function resolve(Request $request, ArgumentMetadata $argument): iterable
279-
{
280-
yield $this->security->getUser();
236+
// create and return the value object
237+
return [$argumentType::fromString($value)];
281238
}
282239
}
283240
284-
In order to get the actual ``User`` object in your argument, the given value
285-
must fulfill the following requirements:
241+
This method first checks whether it can resolve the value:
286242

287-
* An argument must be type-hinted as ``User`` in your action method signature;
288-
* The value must be an instance of the ``User`` class.
243+
* The argument must be type-hinted with a class implementing a custom ``IdentifierInterface``;
244+
* The argument name (e.g. ``$id``) must match the name of a request
245+
attribute (e.g. using a ``/booking/{id}`` route placeholder).
289246

290-
When all those requirements are met and ``true`` is returned, the
291-
``ArgumentResolver`` calls ``resolve()`` with the same values as it called
292-
``supports()``.
247+
When those requirements are met, the method creates a new instance of the
248+
custom value object and returns it as the value for this argument.
293249

294250
That's it! Now all you have to do is add the configuration for the service
295251
container. This can be done by tagging the service with ``controller.argument_value_resolver``
296-
and adding a priority.
252+
and adding a priority:
297253

298254
.. configuration-block::
299255

@@ -308,7 +264,7 @@ and adding a priority.
308264
309265
App\ArgumentResolver\UserValueResolver:
310266
tags:
311-
- { name: controller.argument_value_resolver, priority: 50 }
267+
- { name: controller.argument_value_resolver, priority: 150 }
312268
313269
.. code-block:: xml
314270
@@ -325,7 +281,7 @@ and adding a priority.
325281
<!-- ... -->
326282
327283
<service id="App\ArgumentResolver\UserValueResolver">
328-
<tag name="controller.argument_value_resolver" priority="50"/>
284+
<tag name="controller.argument_value_resolver" priority="150"/>
329285
</service>
330286
</services>
331287
@@ -342,7 +298,7 @@ and adding a priority.
342298
$services = $configurator->services();
343299
344300
$services->set(UserValueResolver::class)
345-
->tag('controller.argument_value_resolver', ['priority' => 50])
301+
->tag('controller.argument_value_resolver', ['priority' => 150])
346302
;
347303
};
348304
@@ -351,26 +307,11 @@ the expected value is injected. The built-in ``RequestAttributeValueResolver``,
351307
which fetches attributes from the ``Request``, has a priority of ``100``. If your
352308
resolver also fetches ``Request`` attributes, set a priority of ``100`` or more.
353309
Otherwise, set a priority lower than ``100`` to make sure the argument resolver
354-
is not triggered when the ``Request`` attribute is present (for example, when
355-
passing the user along sub-requests).
310+
is not triggered when the ``Request`` attribute is present.
356311

357312
To ensure your resolvers are added in the right position you can run the following
358-
command to see which argument resolvers are present and in which order they run.
313+
command to see which argument resolvers are present and in which order they run:
359314

360315
.. code-block:: terminal
361316
362317
$ php bin/console debug:container debug.argument_resolver.inner --show-arguments
363-
364-
.. tip::
365-
366-
As you can see in the ``UserValueResolver::supports()`` method, the user
367-
may not be available (e.g. when the controller is not behind a firewall).
368-
In these cases, the resolver will not be executed. If no argument value
369-
is resolved, an exception will be thrown.
370-
371-
To prevent this, you can add a default value in the controller (e.g. ``User
372-
$user = null``). The ``DefaultValueResolver`` is executed as the last
373-
resolver and will use the default value if no value was already resolved.
374-
375-
.. _`#[ParamConverter]`: https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/converters.html
376-
.. _`yield`: https://www.php.net/manual/en/language.generators.syntax.php

0 commit comments

Comments
 (0)