Skip to content
  • About
    • What is Symfony?
    • Community
    • News
    • Contributing
    • Support
  • Documentation
    • Symfony Docs
    • Symfony Book
    • Screencasts
    • Symfony Bundles
    • Symfony Cloud
    • Training
  • Services
    • SensioLabs Professional services to help you with Symfony
    • Platform.sh for Symfony Best platform to deploy Symfony apps
    • SymfonyInsight Automatic quality checks for your apps
    • Symfony Certification Prove your knowledge and boost your career
    • Blackfire Profile and monitor performance of your apps
  • Other
  • Blog
  • Download
sponsored by SensioLabs
  1. Home
  2. Documentation
  3. Controller
  4. Extending Action Argument Resolving
  • Documentation
  • Book
  • Reference
  • Bundles
  • Cloud

Table of Contents

  • Built-In Value Resolvers
  • Adding a Custom Value Resolver

Extending Action Argument Resolving

Edit this page

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 the Suit::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 and EnumRequirement 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 extending DateTimeInterface.

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.

Tip

The DateTimeInterface object is generated with the Clock component. This. gives your full control over the date and time values the controller receives when testing your application and using the MockClock implementation.

6.1

The DateTimeValueResolver and the MapDateTime attribute were introduced in Symfony 6.1.

6.3

The use of the Clock component to generate the DateTimeInterface object was introduced in Symfony 6.3.

RequestValueResolver
Injects the current Request if type-hinted with Request or a class extending Request.
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 with SessionInterface or a class implementing SessionInterface.
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 own User class but you must then add the #[CurrentUser] attribute to the argument. Default value can be set to null 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): array
    {
        // 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 }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!-- 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
        https://symfony.com/schema/dic/services/services-1.0.xsd">

    <services>
        <!-- ... be sure autowiring is enabled -->
        <defaults autowire="true"/>
        <!-- ... -->

        <service id="App\ValueResolver\BookingIdValueResolver">
            <tag name="controller.argument_value_resolver" priority="150"/>
        </service>
    </services>

</container>
1
2
3
4
5
6
7
8
9
10
11
12
// config/services.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;

use App\ValueResolver\BookingIdValueResolver;

return static function (ContainerConfigurator $containerConfigurator) {
    $services = $containerConfigurator->services();

    $services->set(BookingIdValueResolver::class)
        ->tag('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
This work, including the code samples, is licensed under a Creative Commons BY-SA 3.0 license.
TOC
    Version
    We stand with Ukraine.
    Version:

    Symfony 6.3 is backed by

    Symfony 6.3 is backed by

    Symfony 6.3 is backed by

    Symfony 6.3 is backed by

    Take the exam at home

    Take the exam at home

    Check Code Performance in Dev, Test, Staging & Production

    Check Code Performance in Dev, Test, Staging & Production

    Symfony footer

    ↓ Our footer now uses the colors of the Ukrainian flag because Symfony stands with the people of Ukraine.

    Avatar of Tijs Verkoyen, a Symfony contributor

    Thanks Tijs Verkoyen for being a Symfony contributor

    1 commit • 67 lines changed

    View all contributors that help us make Symfony

    Become a Symfony contributor

    Be an active part of the community and contribute ideas, code and bug fixes. Both experts and newcomers are welcome.

    Learn how to contribute

    Symfony™ is a trademark of Symfony SAS. All rights reserved.

    • What is Symfony?

      • Symfony at a Glance
      • Symfony Components
      • Case Studies
      • Symfony Releases
      • Security Policy
      • Logo & Screenshots
      • Trademark & Licenses
      • symfony1 Legacy
    • Learn Symfony

      • Symfony Docs
      • Symfony Book
      • Reference
      • Bundles
      • Best Practices
      • Training
      • eLearning Platform
      • Certification
    • Screencasts

      • Learn Symfony
      • Learn PHP
      • Learn JavaScript
      • Learn Drupal
      • Learn RESTful APIs
    • Community

      • SymfonyConnect
      • Support
      • How to be Involved
      • Code of Conduct
      • Events & Meetups
      • Projects using Symfony
      • Downloads Stats
      • Contributors
      • Backers
    • Blog

      • Events & Meetups
      • A week of symfony
      • Case studies
      • Cloud
      • Community
      • Conferences
      • Diversity
      • Documentation
      • Living on the edge
      • Releases
      • Security Advisories
      • SymfonyInsight
      • Twig
      • SensioLabs
    • Services

      • SensioLabs services
      • Train developers
      • Manage your project quality
      • Improve your project performance
      • Host Symfony projects

      Deployed on

    Follow Symfony

    Search by Algolia