gstreamer0.10-ffmpeg
gstreamer0.10-plugins-good
packages.
This array is the end result of the route-matching process. Apparently, the router returns an array with the wildcard values from the route plus keys for the route and controller.
But... it's a bit more interesting than that. A great way to see how, is by playing with a route in YAML. Open up config/routes.yaml
. Uncomment the example route and change the path to /playing
. Now, on your browser, open another tab and go to https://localhost:8000/playing.
index: | |
path: /playing | |
... lines 3 - 4 |
That's exactly what we expected: _route
set to the route name and _controller
set to the controller string for that route.
But in reality, the controller
key in a YAML route is just a shortcut. Before Symfony 4, there was no controller
key. Nope, to define a controller you added a defaults
key and put an _controller
key below that.
index: | |
... line 2 | |
defaults: | |
_controller: App\Controller\DefaultController::index |
Move over and refresh now. Woh! We get the exact same array! Yep, the controller
key is really just a shortcut for setting an _controller
default value on the route.
This is actually an important point, but to see why, let's go a bit further. First, add a {id}
wildcard to the end of the path. Then, at your browser, add /5
to the end of the URL. And... yep! The array now has an id
key: no surprise.
index: | |
path: /playing/{id} | |
... lines 3 - 5 |
Normally, the purpose of defaults
on a route are to give a default value for a wildcard. If we say id: 10
... and then refresh, the array still contains 5 because that's what's in the URL. But thanks to the default, now we can just go to /playing
and... the id uses the default value 10
.
index: | |
... line 2 | |
defaults: | |
... line 4 | |
id: 10 |
Cool. But what if we just... invent a new key and put it here? Like totally_inventing_this_default
set to true
.
index: | |
... line 2 | |
defaults: | |
... lines 4 - 5 | |
totally_inventing_this_default: true |
This won't change how the route matches, but it will change what we get back in the array. Refresh. The totally_inventing_this_default
key is now inside the returned array!
So here's the full story of what the route matching process returns: it returns an array_merge
of the route defaults and any wildcard values in the route.... plus the _route
key... just in case that's handy.
With route annotations, it looks a bit different, but it's exactly the same. We can add a defaults
key and set foo
to bar
. Back in the browser, close the last tab and refresh the article show page. We suddenly have a foo
key! On the route, remove that defaults
stuff.
... lines 1 - 13 | |
class ArticleController extends AbstractController | |
{ | |
... lines 16 - 38 | |
/** | |
* @Route("/news/{slug}", name="article_show", defaults={"foo": "bar"}) | |
*/ | |
public function show(Article $article, SlackClient $slack) | |
... lines 43 - 64 | |
} |
So why is it so important to understand exactly what the route-matching process returns? We'll find out the full answer soon. But first... back in RouterListener
, what does this class do with the $parameters
array?
Remove the dd()
... and let's follow the logic. It does some logging and... here it is: $request->attributes->add($parameters)
. This is important.
Let's back up for a second: the Request
object has several public properties and all of them - except one! - correspond to something on the HTTP request. For example, $request->headers
holds the HTTP request headers, $request->cookies
holds the cookies, and there are others like $request->query
to read the query parameters. The point is: all of these refer to real "parts" of an HTTP request. You could talk to a Java developer about HTTP headers and they would know what you're referring to.
The one exception is $request->attributes
. This property does not correspond to any real part of the HTTP request. If you ask that same Java developer:
Hey! What are the attributes on your request?
They'll think you're nuts. Nope, the Request attributes are something totally invented by Symfony. The purpose of the request attributes is to be a place where you can store data about the request that's specific to your application. So, storing the controller, for example, is a perfect fit! That's completely a Symfony concept.
Anyways, the array of $parameters
from the router is added to the $request->attributes()
. What does that... do? Absolutely nothing. Soon, something else will use this data, but at this moment, this is just data sitting on the request.
It also sets another attribute _route_params
... but that's not really important.
Ok! RouterListener
done! Close that class, high-five your cat - and go back to HttpKernel
. As we saw, there are a lot of listeners to the kernel.request
event, but by far, the most important one is RouterListener
. So what changed in our system before and after this dispatch()
line? Basically... just the request attributes.
In fact, let's see this. Above dispatch, dump($request->attributes->all()
. Then copy that... dump after, and die
. Refresh the article show page. Yep! Before we dispatch the event, the attributes are empty. After? We have _route
, _controller
, slug
and hey! A few other things were added by other listeners related to security. That's not important for us - but still, interesting!
... lines 1 - 39 | |
class HttpKernel implements HttpKernelInterface, TerminableInterface | |
{ | |
... lines 42 - 114 | |
private function handleRaw(Request $request, int $type = self::MASTER_REQUEST): Response | |
{ | |
... lines 117 - 120 | |
dump($request->attributes->all()); | |
$this->dispatcher->dispatch($event, KernelEvents::REQUEST); | |
dump($request->attributes->all()); | |
die; | |
... lines 125 - 169 | |
} | |
... lines 171 - 284 | |
} |
Remove all that debug code.
Before we find out how the request attributes are used, I want to show you something kinda cool. We're going to look at a cache file: var/cache/dev
... and then url_matching_routes.php
.
This file is automatically generated by Symfony and is the end-result of all of the routes in our application. This file is insane. After reading our routes, Symfony generates a huge list of regular expressions and which route should match which part, and dumps them to this file. This is used by the route-matching process so that it's blazingly fast. It's... pretty amazing.
Anyways, next! Let's see the significance of those Request attributes by continuing to go through the handleRaw()
method.
// composer.json
{
"require": {
"php": "^7.4.0 || ^8.0.0",
"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
}
}