Scroll down to the script below, click on any sentence (including terminal blocks!) to jump to that spot in the video!
kernel.request event is dispatched, it checks if
$event->hasResponse() and, if that's true... it returns the response! Well, it calls
$this->filterResponse() and returns that - but that's not too important. We'll see that method later. The point is: the function ends. The response is returned immediately before our controller is executed or anything else.
This is kinda cool. If a listener to
kernel.request somehow already has enough information to return a response... it can do that! It's not super common, it could be used for security or a maintenance page... but hey! Let's try it ourselves!
src/EventListener/UserAgentSubscriber.php, we can say
$event->setResponse(). Not all event classes have this
setResponse() method - but
RequestEvent does. Then say
new Response() and set a very important message.
|... lines 1 - 6|
|... lines 8 - 9|
|class UserAgentSubscriber implements EventSubscriberInterface|
|... lines 12 - 18|
|public function onKernelRequest(RequestEvent $event)|
|'Ah, ah, ah: you didn\'t say the magic word'|
|... lines 24 - 28|
|... lines 30 - 36|
Now when we refresh... yay! Nedry killed every page!
Remove that code... and then I'll close a few files.
HttpKernel, so far we've dispatch one event.
RouterListener listened to that event and modified the request attributes.
Let's keep following the code. This next line is interesting: inside the
if we have:
$controller = $this->resolver->getController(). This "resolver" thing is a "controller resolver". At a high level, it's beautiful. We know that we will eventually need to execute a controller that will create the Response. This class is entirely responsible for determining that controller: we pass it the request and - somehow - it gives us back a controller, which can be any callable.
How can we figure out what class
$this->resolver is? Well... of course, there's always the handy
dd($this->resolver), which... tells us that this is an instance of
TraceableControllerResolver. By the way, whenever you see a class called "Traceable" something, this is almost definitely an object that is decorating the real object in the
dev environment in order to give us some debugging info. The real controller resolver is inside:
Another way to figure this out - maybe a slightly nerdier way for a deep-dive course - is with smart use of the
debug:container command. The
$resolver is one of the arguments to the constructor on top: it's argument 2. We can see the interface type-hint, but not the concrete class.
Scroll back down, then move over to your terminal and run
php bin/console debug:container http_kernel - that's the service id of HttpKernel -
This tells me that the second argument is
debug.controller_resolver. Ok, let's run this command again to get more info about that:
php bin/console debug:container debug.controller_resolver --show-arguments
This uses Symfony's service decoration - a topic we'll see in our next deep-dive tutorial. But basically, when a service is decorated, the original, "inner" service's id will be
debug.controller_resolver.inner. So... run
debug:container with that!
php bin/console debug:container debug.controller_resolver.inner --show-arguments
And here it is: the true controller resolver class is... what we already know: it's called
ControllerResolver and lives inside FrameworkBundle.
So... let's open that up and see what it looks like! I'll hit Shift+Shift and look for
ControllerResolver.php. Oh, there are two of them: the one from
FrameworkBundle and another from
HttpKernel. So... there's some inheritance going on: the
FrameworkBundle extends a
ContainerControllerResolver... which extends the
ControllerResolver from HttpKernel. The class from
FrameworkBundle doesn't contain anything important that we need to look at. So I'm actually going to open
ContainerControllerResolver first. And... yep! Its base class is
ControllerResolver, which lives in the same namespace. Hold Command or Ctrl and click that class to open it.
Ok, time to see what's going on!
HttpKernel called the
getController() method. Let's go see what that looks like!
getController() method is passed the
Request and... oh! Check it out! The first thing it does is fetch
_controller from the request attributes! So why is
_controller the "magic" key you can use in your YAML route? It's because of this line right here: the
ControllerResolver looks for
Ultimately, what this method needs to return is some sort of callable. For us, it will be a method inside an object, but it can also be a number of other things, like an anonymous function. Let's see what our
$controller looks like at this point.
dd($controller), then move back to your browser and refresh.
|... lines 1 - 23|
|class ControllerResolver implements ControllerResolverInterface|
|... lines 26 - 35|
|public function getController(Request $request)|
|... lines 38 - 45|
|... lines 47 - 96|
|... lines 98 - 222|
Ah yes: for us,
$controller is the normal string format we've been seeing: the full controller class,
::, then the method name.
dd() and let's trace down on the code. This has a bunch of if statements, which are all basically trying to figure out if the controller is maybe already a callable. It checks if it's an array and if the 0 and 1 elements are set - because that's a callable format. We're also not an object... and our string is not a function name. Basically, our controller is not already a "callable".
So ultimately, we fall down to
$callable = $this->createController(). Somehow this function converts our string into something that can be invoked. How? Let's find out next.