Argument Value Resolvers

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $10.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

We just learned that when Symfony tries to figure out what arguments to pass to your controller, it calls this ArgumentResolver::getArguments() method. It loops over the arguments one-by-one and then loops over these things called argumentValueResolvers, to see which one can figure out what to pass to each argument.

To see a list of all of the argument value resolvers, we ran:

php bin/console debug:container --tag=controller.argument_value_resolver

They're all decorated inside a TraceableValueResolver class, but you can see, kind of by their name, what's really inside. So, I want to know: what are all the possible arguments that I'm allowed to have on my controller? To find out, let's look inside the most important of these argument value resolvers.

RequestAttributeValueResolver: Wildcards as Arguments

Remove the dd() from ArgumentResolver. I'll hit Shift + Shift to open up my favorite resolver: RequestAttributeValueResolver.php.

Perfect. Remember, as it loops over the arguments, the first thing ArgumentResolver does is call supports() on each of these to figure out if this value resolver can help. It passes us the $request and the ArgumentMetadata.

To see how this works, dd() $request->attributes->all() and also $argument.

... lines 1 - 22
final class RequestAttributeValueResolver implements ArgumentValueResolverInterface
{
... lines 25 - 27
public function supports(Request $request, ArgumentMetadata $argument): bool
{
dd($request->attributes->all(), $argument);
... line 31
}
... lines 33 - 40
}

Because... check this out! This class uses the now-famous $request->attributes.

Move over and refresh the article show page. Beautiful. The request attributes have what we expect: the stuff from the router. And because this is the first time through the loop, it's asking us if we know what value to pass to the slug argument.

In the supports() logic, if you ignore the isVariadic() part - that's not too important - what this basically says is:

I can provide the value for the argument if the name of the argument - slug in this case - is inside $request->attributes.

Down in resolve()... yea! It literally returns $request->attributes->get($argument->getName()).

This is huge! The first thing we learn about Symfony routes and controllers is that if you have a {slug} wildcard in your route, you're allowed to have a $slug argument in your controller. Why does that work? Now we know! It's a two step process. First, the router puts all the wildcard values into $request->attributes. And second, this RequestAttributeValueResolver looks into $request->attributes using the argument name and returns the value if it's there. This class is what gives us this fundamentally important functionality.

RequestValueResolver: Request Argument

But that's not the only cool argument value resolver! Remove the dd() and... let's go open another one! I'll hit Shift + Shift to open a class called RequestValueResolver. There it is!

What are some other things that we know we are allowed to have as arguments? Let me find an example... hmm... I'll open up ArticleAdminController. Here we go: one of the other things you can do is add an argument that's type-hinted with the Request class. If we do that, we get the request.

How does this work? It's thanks to RequestValueResolver. This one is dead simple. It says:

Hey! If this argument is type-hinted with the Request class... or a sub-class... pass the request!

That's precisely what supports() checks for. And resolve() couldn't be shorter.

SessionValueResolver for SessionInterface Argument

Ok, what else is there? I'm going to go to the directory tree on top and double-click this ArgumentResolver folder. That moves us into this directory on the left... which is cool because this is full of other argument resolvers!

A few of these are similar to RequestValueResolver - like SessionValueResolver. You may or may not know this, but you can type-hint an argument with SessionInterface and you'll get the session. That works thanks to this resolver.

UserValueResolver for UserInterface Argument

Another resolver lives in a different directory - I'll hit Shift+Shift to open it: UserValueResolver.php. This resolver allows you to type-hint UserInterface on an argument to get your security User object.

The Amazing ServiceValueResolver

At this point, if we look back at ArticleController::show, we now know how the first argument works, but... we haven't seen a resolver yet that explains the next two. The second and third arguments are type-hinted with services. Where is the magic that allows us to type-hint a service in a controller method?

The answer to that is the ServiceValueResolver. It's such a cool class, that let's look at it in depth, next.

Leave a comment!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.3.0",
        "ext-iconv": "*",
        "antishov/doctrine-extensions-bundle": "^1.4", // v1.4.2
        "aws/aws-sdk-php": "^3.87", // 3.133.20
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "doctrine/doctrine-bundle": "^2.0", // 2.0.7
        "doctrine/doctrine-migrations-bundle": "^1.3|^2.0", // 2.1.2
        "doctrine/orm": "^2.5.11", // v2.7.1
        "easycorp/easy-log-handler": "^1.0", // v1.0.9
        "http-interop/http-factory-guzzle": "^1.0", // 1.0.0
        "knplabs/knp-markdown-bundle": "^1.7", // 1.8.1
        "knplabs/knp-paginator-bundle": "^5.0", // v5.1.1
        "knplabs/knp-snappy-bundle": "^1.6", // v1.7.0
        "knplabs/knp-time-bundle": "^1.8", // v1.11.0
        "league/flysystem-aws-s3-v3": "^1.0", // 1.0.24
        "league/flysystem-cached-adapter": "^1.0", // 1.0.9
        "league/html-to-markdown": "^4.8", // 4.9.1
        "liip/imagine-bundle": "^2.1", // 2.3.0
        "nexylan/slack-bundle": "^2.1", // v2.2.2
        "oneup/flysystem-bundle": "^3.0", // 3.4.0
        "php-http/guzzle6-adapter": "^2.0", // v2.0.1
        "sensio/framework-extra-bundle": "^5.1", // v5.5.3
        "symfony/asset": "5.0.*", // v5.0.4
        "symfony/console": "5.0.*", // v5.0.4
        "symfony/dotenv": "5.0.*", // v5.0.4
        "symfony/flex": "^1.9", // v1.9.10
        "symfony/form": "5.0.*", // v5.0.4
        "symfony/framework-bundle": "5.0.*", // v5.0.4
        "symfony/mailer": "5.0.*", // v5.0.4
        "symfony/messenger": "5.0.*", // v5.0.4
        "symfony/monolog-bundle": "^3.5", // v3.5.0
        "symfony/security-bundle": "5.0.*", // v5.0.4
        "symfony/sendgrid-mailer": "5.0.*", // v5.0.4
        "symfony/serializer-pack": "^1.0", // v1.0.2
        "symfony/twig-bundle": "5.0.*", // v5.0.4
        "symfony/twig-pack": "^1.0", // v1.0.0
        "symfony/validator": "5.0.*", // v5.0.4
        "symfony/webpack-encore-bundle": "^1.4", // v1.7.3
        "symfony/yaml": "5.0.*", // v5.0.4
        "twig/cssinliner-extra": "^2.12", // v2.12.5
        "twig/extensions": "^1.5", // v1.5.4
        "twig/inky-extra": "^2.12" // v2.12.5
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.0", // 3.3.0
        "fzaninotto/faker": "^1.7", // v1.9.1
        "symfony/browser-kit": "5.0.*", // v5.0.4
        "symfony/debug-bundle": "5.0.*", // v5.0.4
        "symfony/maker-bundle": "^1.0", // v1.14.3
        "symfony/phpunit-bridge": "5.0.*", // v5.0.4
        "symfony/profiler-pack": "^1.0", // v1.0.4
        "symfony/var-dumper": "5.0.*" // v5.0.4
    }
}