Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Calling the Controller & View Event

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $10.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Ok, let's remember what we've done so far... because it's really not that much.

The kernel.controller_arguments Event

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.

Calling the Controller

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.

The kernel.view Event

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".

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