If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeWe 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.
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.
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.
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.
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.
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.
"Houston: no signs of life"
Start the conversation!
// composer.json
{
"require": {
"php": ">=8.1",
"ext-iconv": "*",
"antishov/doctrine-extensions-bundle": "^1.4", // v1.4.3
"aws/aws-sdk-php": "^3.87", // 3.133.20
"composer/package-versions-deprecated": "^1.11", // 1.11.99
"doctrine/annotations": "^1.0", // 1.12.1
"doctrine/doctrine-bundle": "^2.0", // 2.2.3
"doctrine/doctrine-migrations-bundle": "^1.3|^2.0", // 2.2.2
"doctrine/orm": "^2.5.11", // 2.8.2
"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.9.0
"knplabs/knp-paginator-bundle": "^5.0", // v5.4.2
"knplabs/knp-snappy-bundle": "^1.6", // v1.7.1
"knplabs/knp-time-bundle": "^1.8", // v1.16.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.5.0
"oneup/flysystem-bundle": "^3.0", // 3.7.0
"php-http/guzzle6-adapter": "^2.0", // v2.0.2
"phpdocumentor/reflection-docblock": "^5.2", // 5.2.2
"sensio/framework-extra-bundle": "^5.1", // v5.6.1
"symfony/asset": "5.0.*", // v5.0.11
"symfony/console": "5.0.*", // v5.0.11
"symfony/dotenv": "5.0.*", // v5.0.11
"symfony/flex": "^1.9", // v1.17.5
"symfony/form": "5.0.*", // v5.0.11
"symfony/framework-bundle": "5.0.*", // v5.0.11
"symfony/mailer": "5.0.*", // v5.0.11
"symfony/messenger": "5.0.*", // v5.0.11
"symfony/monolog-bundle": "^3.5", // v3.6.0
"symfony/property-access": "5.0.*|| 5.1.*", // v5.1.11
"symfony/property-info": "5.0.*|| 5.1.*", // v5.1.10
"symfony/routing": "5.1.*", // v5.1.11
"symfony/security-bundle": "5.0.*", // v5.0.11
"symfony/sendgrid-mailer": "5.0.*", // v5.0.11
"symfony/serializer": "5.0.*|| 5.1.*", // v5.1.10
"symfony/twig-bundle": "5.0.*", // v5.0.11
"symfony/validator": "5.0.*", // v5.0.11
"symfony/webpack-encore-bundle": "^1.4", // v1.11.1
"symfony/yaml": "5.0.*", // v5.0.11
"twig/cssinliner-extra": "^2.12", // v2.14.3
"twig/extensions": "^1.5", // v1.5.4
"twig/extra-bundle": "^2.12|^3.0", // v3.3.0
"twig/inky-extra": "^2.12", // v2.14.3
"twig/twig": "^2.12|^3.0" // v2.14.4
},
"require-dev": {
"doctrine/doctrine-fixtures-bundle": "^3.0", // 3.4.0
"fakerphp/faker": "^1.13", // v1.13.0
"symfony/browser-kit": "5.0.*", // v5.0.11
"symfony/debug-bundle": "5.0.*", // v5.0.11
"symfony/maker-bundle": "^1.0", // v1.29.1
"symfony/phpunit-bridge": "5.0.*", // v5.0.11
"symfony/stopwatch": "^5.1", // v5.1.11
"symfony/var-dumper": "5.0.*", // v5.0.11
"symfony/web-profiler-bundle": "^5.0" // v5.0.11
}
}