Extending Action Argument Resolving
Warning: You are browsing the documentation for Symfony 6.2, which is no longer maintained.
Read the updated version of this page for Symfony 7.2 (the current stable version).
Extending Action Argument Resolving
In the controller guide, you've learned that you can get the
Request object via an argument in
your controller. This argument has to be type-hinted by the Request
class
in order to be recognized. This is done via the
ArgumentResolver. By
creating and registering custom value resolvers, you can extend this
functionality.
Built-In Value Resolvers
Symfony ships with the following value resolvers in the HttpKernel component:
- BackedEnumValueResolver
-
Attempts to resolve a backed enum case from a route path parameter that matches the name of the argument. Leads to a 404 Not Found response if the value isn't a valid backing value for the enum type.
For example, if your backed enum is:
1 2 3 4 5 6 7 8 9
namespace App\Model; enum Suit: string { case Hearts = 'H'; case Diamonds = 'D'; case Clubs = 'C'; case Spades = 'S'; }
And your controller contains the following:
1 2 3 4 5 6 7 8 9 10
class CardController { #[Route('/cards/{suit}')] public function list(Suit $suit): Response { // ... } // ... }
When requesting the
/cards/H
URL, the$suit
variable will store theSuit::Hearts
case.Furthermore, you can limit route parameter's allowed values to only one (or more) with
EnumRequirement
:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
use Symfony\Component\Routing\Requirement\EnumRequirement; // ... class CardController { #[Route('/cards/{suit}', requirements: [ // this allows all values defined in the Enum 'suit' => new EnumRequirement(Suit::class), // this restricts the possible values to the Enum values listed here 'suit' => new EnumRequirement([Suit::Diamonds, Suit::Spades]), ])] public function list(Suit $suit): Response { // ... } // ... }
The example above allows requesting only
/cards/D
and/cards/S
URLs and leads to 404 Not Found response in two other cases.6.1
The
BackedEnumValueResolver
andEnumRequirement
were introduced in Symfony 6.1. - RequestAttributeValueResolver
- Attempts to find a request attribute that matches the name of the argument.
- DateTimeValueResolver
-
Attempts to find a request attribute that matches the name of the argument and injects a
DateTimeInterface
object if type-hinted with a class extendingDateTimeInterface
.By default any input that can be parsed as a date string by PHP is accepted. You can restrict how the input can be formatted with the MapDateTime attribute.
6.1
The
DateTimeValueResolver
and theMapDateTime
attribute were introduced in Symfony 6.1. - RequestValueResolver
-
Injects the current
Request
if type-hinted withRequest
or a class extendingRequest
. - ServiceValueResolver
- Injects a service if type-hinted with a valid service class or interface. This works like autowiring.
- SessionValueResolver
-
Injects the configured session class implementing
SessionInterface
if type-hinted withSessionInterface
or a class implementingSessionInterface
. - DefaultValueResolver
- Will set the default value of the argument if present and the argument is optional.
- UidValueResolver
-
Attempts to convert any UID values from a route path parameter into UID objects. Leads to a 404 Not Found response if the value isn't a valid UID.
For example, the following will convert the token parameter into a
UuidV4
object:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// src/Controller/DefaultController.php namespace App\Controller; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Uid\UuidV4; class DefaultController { #[Route('/share/{token}')] public function share(UuidV4 $token): Response { // ... } }
6.1
The
UidValueResolver
was introduced in Symfony 6.1. - VariadicValueResolver
- Verifies if the request 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.
In addition, some components, bridges and official bundles provide other value resolvers:
- UserValueResolver
-
Injects the object that represents the current logged in user if type-hinted with
UserInterface
. You can also type-hint your ownUser
class but you must then add the#[CurrentUser]
attribute to the argument. Default value can be set tonull
in case the controller can be accessed by anonymous users. It requires installing the SecurityBundle.If the argument is not nullable and there is no logged in user or the logged in user has a user class not matching the type-hinted class, an
AccessDeniedException
is thrown by the resolver to prevent access to the controller. - EntityValueResolver
-
Automatically query for an entity and pass it as an argument to your controller.
For example, the following will query the
Product
entity which has{id}
as primary key:1 2 3 4 5 6 7 8 9 10 11 12 13 14
// src/Controller/DefaultController.php namespace App\Controller; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; class DefaultController { #[Route('/product/{id}')] public function share(Product $product): Response { // ... } }
To learn more about the use of the
EntityValueResolver
, see the dedicated section Automatically Fetching Objects.6.2
The
EntityValueResolver
was introduced in Symfony 6.2. - PSR-7 Objects Resolver:
-
Injects a Symfony HttpFoundation
Request
object created from a PSR-7 object of type ServerRequestInterface, RequestInterface or MessageInterface. It requires installing the PSR-7 Bridge component.
Adding a Custom Value Resolver
In the next example, you'll create a value resolver to inject an ID value
object whenever a controller argument has a type implementing
IdentifierInterface
(e.g. BookingId
):
1 2 3 4 5 6 7 8 9 10 11 12 13
// src/Controller/BookingController.php
namespace App\Controller;
use App\Reservation\BookingId;
use Symfony\Component\HttpFoundation\Response;
class BookingController
{
public function index(BookingId $id): Response
{
// ... do something with $id
}
}
6.2
The ValueResolverInterface
was introduced in Symfony 6.2. Prior to
6.2, you had to use the
ArgumentValueResolverInterface,
which defines different methods.
Adding a new value resolver requires creating a class that implements ValueResolverInterface and defining a service for it.
This interface contains a resolve()
method, which is called for each
argument of the controller. It receives the current Request
object and an
ArgumentMetadata
instance, which contains all information from the method signature.
The resolve()
method should return either an empty array (if it cannot resolve
this argument) or an array with the resolved value(s). Usually arguments are
resolved as a single value, but variadic arguments require resolving multiple
values. That's why you must always return an array, even for single values:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
// src/ValueResolver/IdentifierValueResolver.php
namespace App\ValueResolver;
use App\IdentifierInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Controller\ValueResolverInterface;
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
class BookingIdValueResolver implements ValueResolverInterface
{
public function resolve(Request $request, ArgumentMetadata $argument): iterable
{
// get the argument type (e.g. BookingId)
$argumentType = $argument->getType();
if (
!$argumentType
|| !is_subclass_of($argumentType, IdentifierInterface::class, true)
) {
return [];
}
// get the value from the request, based on the argument name
$value = $request->attributes->get($argument->getName());
if (!is_string($value)) {
return [];
}
// create and return the value object
return [$argumentType::fromString($value)];
}
}
This method first checks whether it can resolve the value:
- The argument must be type-hinted with a class implementing a custom
IdentifierInterface
; - The argument name (e.g.
$id
) must match the name of a request attribute (e.g. using a/booking/{id}
route placeholder).
When those requirements are met, the method creates a new instance of the custom value object and returns it as the value for this argument.
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 controller.argument_value_resolver
and adding a priority:
1 2 3 4 5 6 7 8 9 10
# config/services.yaml
services:
_defaults:
# ... be sure autowiring is enabled
autowire: true
# ...
App\ValueResolver\BookingIdValueResolver:
tags:
- { name: controller.argument_value_resolver, priority: 150 }
While adding a priority is optional, it's recommended to add one to make sure
the expected value is injected. The built-in RequestAttributeValueResolver
,
which fetches attributes from the Request
, has a priority of 100
. If your
resolver also fetches Request
attributes, set a priority of 100
or more.
Otherwise, set a priority lower than 100
to make sure the argument resolver
is not triggered when the Request
attribute is present.
To ensure your resolvers are added in the right position you can run the following command to see which argument resolvers are present and in which order they run:
1
$ php bin/console debug:container debug.argument_resolver.inner --show-arguments