Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

RequestEvent & RouterListener

Video not working?

It looks like your browser may not support the H264 codec. If you're using Linux, try a different browser or try installing the gstreamer0.10-ffmpeg gstreamer0.10-plugins-good packages.

Thanks! This saves us from needing to use Flash or encode videos in multiple formats. And that let's us get back to making more videos :). But as always, please feel free to message us.

We've traced the code from the first file that's executed - public/index.php - all the way into this core HttpKernel class. Specifically, this handleRaw() method. These 50 lines of code are the entire framework. It somehow transforms a Request at the beginning into a Response at the end. The question is: how? What does it do to accomplish that?

The RequestStack

The first line uses something called a RequestStack: $this->requestStack->push($request).

The RequestStack is a small object that basically holds an array of Request objects. It's not important now, but we will talk about it later. Because, yes, as strange as it may seem, there is a concept inside Symfony of handling multiple Request objects at the same time. But don't worry about it now.

Dispatching RequestEvent (kernel.request)

The first really important thing is that Symfony dispatches an event. So, almost before doing anything else, the first event is triggered. It's called KernelEvents::REQUEST, which is a constant that really means the string: kernel.request. And... surprise! What type of event object is passed to these listeners? A RequestEvent.

Go back to your browser and refresh the page: it should be working now. Click any of the web debug toolbar links to jump into the profiler... and go to the Performance section.

As we talked about earlier, our controller is somewhere in the middle... and most of the things before and after the controller are listeners to different events. In fact, look at this one: a gray bar called kernel.request. We just saw where that's dispatched!

The Listeners to RequestEvent

This shows us how long it took to execute all of the listeners for this event. To get a better view, go back to the Events section of the profiler. Yep, the very first event that was triggered was kernel.request. And in this app, it has about 10 different listeners.

For the purposes of understanding how Symfony works, the majority of these listeners aren't very important. They do various things. Like, for example, this ValidateRequestListener checks that a few important parts of the request are correctly formatted. Basically, it will cause an error if it looks like a hacker is trying to manipulate the request, which is cool!

At the bottom of the listener list, check it out! There is our UserAgentSubscriber. It's last because it has the lowest listener priority.

Really, out of all these listeners, there is only one that is critically important to understanding how the framework works. It's RouterListener. If you ever wondered where the routing system is actually executed - at what point it looks at the request and tries to find a matching route - here is your answer: RouterListener.

Hello RouterListener

So... let's go see what it does! I'll hit Shift+Shift and type RouterListener.php. I'll click the "Include non-project" items box to see it.

Open this up! The first thing I want you to notice is that this looks a lot like our event subscriber. It implements EventSubscriberInterface and... if we find the getSubscribedEvents() method down here, it listens to a few events. The only one that's important for us is KernelEvents::Request, which, remember, really means kernel.request.

Find the onKernelRequest method... here it is. Skip down a little - for me, down to lines 111 to 115. This is where the router is executed: $this->matcher is the router. It's not really important which side of the if statement is actually executed: either way, this runs the routing.

So... my question is: what is the end result of executing the "match" functionality on the Router? At a high level, we understand the router: it looks at the current URL - and sometimes other parts of the request - and determines which route matches.

What does Routing Return?

But... what is this $parameters variable? What does the match() method return? Let's find out! Hit enter and dd($parameters).

... lines 1 - 96
public function onKernelRequest(RequestEvent $event)
{
... lines 99 - 108
try {
... lines 110 - 115
dd($parameters);
... lines 117 - 129
} catch (ResourceNotFoundException $e) {
... lines 131 - 141
}
}
... lines 144 - 177

Let's go! Find your browser and click back to get the homepage. I'm also going to open an article page in another tab... and then refresh.

Interesting: what we get back from the router is an array with two things inside: _route - that's the name of the matched route - and _controller, which is the full class and method name of the controller that's attached to the route.

Ok... what about the article show page? Move to that tab. Woh! We get the same thing - _route and _controller - but with one new item in the array: a slug key! As a reminder, if you go to src/Controller/ArticleController.php and find the show() method, its route has a wildcard called slug! So really, what the router returns is this: a combination of all the wildcard values in the route plus _controller and _route.

That's... mostly true. But come on! This is a deep-dive course! So next, let's look even deeper at routes and route defaults to learn the full story. We'll also look at what RouterListener does with this super-important array.

Leave a comment!

0
Login or Register to join the conversation
Cat in space

"Houston: no signs of life"
Start the conversation!

This tutorial also works well for Symfony 6!

What PHP libraries does this tutorial use?

// 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
    }
}
userVoice