Skip to content
  • About
    • What is Symfony?
    • Community
    • News
    • Contributing
    • Support
  • Documentation
    • Symfony Docs
    • Symfony Book
    • Screencasts
    • Symfony Bundles
    • Symfony Cloud
    • Training
  • Services
    • 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
    • SensioLabs Professional services to help you with Symfony
    • 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 argument value resolvers, you can extend this functionality.

Built-In Value Resolvers

Symfony ships with the following value resolvers in the HttpKernel component:

RequestAttributeValueResolver
Attempts to find a request attribute that matches the name of the argument.
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.
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 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.
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 the object that represents the current user whenever a controller method type-hints an argument with the User class:

1
2
3
4
5
6
7
8
9
10
11
12
13
// src/Controller/UserController.php
namespace App\Controller;

use App\Entity\User;
use Symfony\Component\HttpFoundation\Response;

class UserController
{
    public function index(User $user)
    {
        return new Response('Hello '.$user->getUserIdentifier().'!');
    }
}

Beware that this feature is already provided by the @ParamConverter annotation from the SensioFrameworkExtraBundle. If you have that bundle installed in your project, add this config to disable the auto-conversion of type-hinted method arguments:

1
2
3
4
5
# config/packages/sensio_framework_extra.yaml
sensio_framework_extra:
    request:
        converters: true
        auto_convert: false
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- config/packages/sensio_framework_extra.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"
    xmlns:sensio-framework-extra="http://symfony.com/schema/dic/symfony_extra"
    xsi:schemaLocation="http://symfony.com/schema/dic/services
        https://symfony.com/schema/dic/services/services-1.0.xsd
        http://symfony.com/schema/dic/symfony_extra
        https://symfony.com/schema/dic/symfony_extra/symfony_extra-1.0.xsd">

    <sensio-framework-extra:config>
        <request converters="true" auto-convert="false"/>
    </sensio-framework-extra:config>
</container>
1
2
3
4
5
6
7
// config/packages/sensio_framework_extra.php
$container->loadFromExtension('sensio_framework_extra', [
    'request' => [
        'converters' => true,
        'auto_convert' => false,
    ],
]);

Adding a new value resolver requires creating a class that implements ArgumentValueResolverInterface and defining a service for it. The interface defines two methods:

supports()
This method is used to check whether the value resolver supports the given argument. resolve() will only be called 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 ArgumentMetadata instance. This object contains all information retrieved from the method signature for the current argument.

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:

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
32
// src/ArgumentResolver/UserValueResolver.php
namespace App\ArgumentResolver;

use App\Entity\User;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
use Symfony\Component\Security\Core\Security;

class UserValueResolver implements ArgumentValueResolverInterface
{
    private $security;

    public function __construct(Security $security)
    {
        $this->security = $security;
    }

    public function supports(Request $request, ArgumentMetadata $argument): bool
    {
        if (User::class !== $argument->getType()) {
            return false;
        }

        return $this->security->getUser() instanceof User;
    }

    public function resolve(Request $request, ArgumentMetadata $argument): iterable
    {
        yield $this->security->getUser();
    }
}

In order to get the actual User object in your argument, the given value must fulfill the following requirements:

  • An argument must be type-hinted as User in your action method signature;
  • The value must be an instance of the User class.

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 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\ArgumentResolver\UserValueResolver:
        tags:
            - { name: controller.argument_value_resolver, priority: 50 }
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\ArgumentResolver\UserValueResolver">
            <tag name="controller.argument_value_resolver" priority="50"/>
        </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\ArgumentResolver\UserValueResolver;

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

    $services->set(UserValueResolver::class)
        ->tag('controller.argument_value_resolver', ['priority' => 50])
    ;
};

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 (for example, when passing the user along sub-requests).

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

Tip

As you can see in the UserValueResolver::supports() method, the user may not be available (e.g. when the controller is not behind a firewall). In these cases, the resolver will not be executed. If no argument value is resolved, an exception will be thrown.

To prevent this, you can add a default value in the controller (e.g. User $user = null). The DefaultValueResolver is executed as the last resolver and will use the default value if no value was already resolved.

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 5.4 is backed by

    Show your Sylius expertise

    Show your Sylius expertise

    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 Petar Obradović, a Symfony contributor

    Thanks Petar Obradović for being a Symfony contributor

    1 commit • 43 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 Meilisearch