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 SubscribeOk, let's remember what we've done so far... because it's really not that much.
We dispatched an event, found the controller, dispatched another event, found the arguments, and... guess what? Now we're dispatching another event called KernelEvents::CONTROLLER_ARGUMENTS
, which resolves to the string kernel.controller_arguments
.
There are no core, important listeners to this: it's just another hook point. The only difference between this event and kernel.controller
is that this event has access to the arguments. So if you needed to do something that's based on what arguments we're about to pass to the controller, this is your event. Listeners to this event also have the ability to change the controller or the arguments via setter methods on the event class.
What's next? It's the moment we've all be waiting for. Drum roll.. we call the controller! Yes! We knew that the code that executed the controller lived somewhere... and here it is. It's delightfully simple: $controller(...$arguments)
. I love that.
And of course, what does our Symfony controller always return? A Response
. Unless... it doesn't. It turns out, your controller does not actually need to return a Response
object. If it returns something else, then you end up inside the next if statement. And... what does Symfony do? You can kinda start guessing whenever I ask that question. The answer is pretty much always: Symfony dispatches an event. This time it's KernelEvents::VIEW
, which means kernel.view
.
When Symfony dispatches this event, it's basically saying:
Yea... so, this controller returned something that's not a response... and I kinda need a response that I can send back to the user. Can any listeners to this event, somehow, transform whatever the controller returned into a response? That would be schweet!
This is, kind of, the "view", or "V" in a true MVC framework. Normally our controller return a response directly. But instead, you could, for example, return... an Article
entity! You would return a "model". Then, you could write a listener to this event that transforms that Article
entity into HTML by rendering the template.
This event isn't used anywhere in Symfony's core, but it is used extensively inside API Platform. Internally, their controllers return objects. Then, they have a listener - actually a few that work together - that transforms that object into JSON, XML or whatever format the user is requesting.
After dispatching the event, it checks to see if the event has a response. Hold Command or Ctrl and click to open the ViewEvent
class. If you listen to this event, you have access to what the controller returned. Then... I'm going to open the parent class... a listener can call setResponse()
with whatever Response
it created.
So if one of the listeners sets the response, then $event->hasResponse()
will be true and... we're saved! We do now have a response. But if none of the listeners are able to set a response... sad trombone... then finally, Symfony panics! It says that controller must return a Response
object, but it returned something different. And then, one of my favorite parts of this whole system: if null === $response
, it politely adds:
Did you forget to add a return statement somewhere in your controller?
Ah yes, I have forgotten that, many times. Let's forget it now! Add a return
in our show()
action, then spin over, refresh and... enjoy the error!
... lines 1 - 13 | |
class ArticleController extends AbstractController | |
{ | |
... lines 16 - 45 | |
public function show($slug, SlackClient $slack, ArticleRepository $articleRepository) | |
{ | |
return; | |
... lines 49 - 61 | |
} | |
... lines 63 - 75 | |
} |
Go back and remove that.
At this point, we definitely have a Response
object: either our controller returned a Response
or a listener to kernel.view
was able to create one. So finally, down here... we're done! We made it! We return $this->filterResponse($response)
. What does that method do? Do you remember what you're always supposed to answer when I ask that question? Yep, it dispatches another event! Let's look into that next and dig into a few really cool listeners on it, including one that takes us into the topic of the "request format".
"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
}
}