Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine
This tutorial has a new version, check it out!

Route, Controllers & Responses!

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.

The page we're looking at right now... which is super fun... and even changes colors... is just here to say "Hello!". Symfony is rendering this because, in reality, our app doesn't have any real pages yet. Let's change that.

Route + Controller = Page

Every web framework... in any language... has the same main job: to give you a route & controller system: a 2-step system to build pages. A route defines the URL of the page and the controller is where we write PHP code to build that page, like the HTML or JSON.

Open up config/routes.yaml:

# path: /
# controller: App\Controller\DefaultController::index

Hey! We already have an example! Uncomment that. If you're not familiar with YAML, it's super friendly: it's a key-value config format that's separated by colons. Indentation is also important.

This creates a single route whose URL is /. The controller points to a function that will build this page... really, it points to a method on a class. Overall, this route says:

when the user goes to the homepage, please execute the index method on the DefaultController class.

Oh, and you can ignore that index key at the top of the YAML: that's an internal name for the route... and it's not important yet.

Our App

The project we're building is called "Cauldron Overflow". We originally wanted to create a site where developers could ask questions and other developers answered them but... someone beat us to it... by... like 10 years. So like all impressive startups, we're pivoting! We've noticed a lot of wizards accidentally blowing themselves up... or conjuring fire-breathing dragons when they meant to create a small fire for roasting marshmallows. And so... Cauldron Overflow is here to become the place for witches and wizards to ask and answer questions about magical misadventures.

Creating a Controller

On the homepage, we will eventually list some of the most recent questions. So let's change the controller class to QuestionController and the method to homepage.

path: /
controller: App\Controller\QuestionController::homepage

Ok, route done: it defines the URL and points to the controller that will build the page. Now... we need to create that controller! Inside the src/ directory, there's already a Controller/ directory... but it's empty. I'll right click on this and select "New PHP class". Call it QuestionController.

Namespaces & the src/ Directory

Ooh, check this out. It pre-filled the namespace! That's awesome! This is thanks to the Composer PhpStorm configuration we did in the last chapter.

Here's the deal: every class we create in the src/ directory will need a namespace. And... for reasons that aren't super important, the namespace must be App\ followed whatever directory the file lives in. Because we're creating this file in the Controller/ directory, its namespace must be App\Controller. PhpStorm will pre-fill this every time.

... lines 1 - 2
namespace App\Controller;
... lines 4 - 6
class QuestionController
... lines 9 - 12

Perfect! Now, because in routes.yaml we decided to call the method homepage, create that here: public function homepage().

... lines 1 - 2
namespace App\Controller;
... lines 4 - 6
class QuestionController
public function homepage()
... line 11

Controllers Return a Response

And.. congratulations! You are inside of a controller function, which is also sometimes called an "action"... to confuse things. Our job here is simple: to build the page. We can write any code we need to do that - like to make database queries, cache things, perform API calls, mine cryptocurrencies... whatever. The only rule is that a controller function must return a Symfony Response object.

Say return new Response(). PhpStorm tries to auto-complete this... but there are multiple Response classes in our app. The one we want is from Symfony\Component\HttpFoundation. HttpFoundation is one of the most important parts - or "components" - in Symfony. Hit tab to auto-complete it.

But stop! Did you see that? Because we let PhpStorm auto-complete that class for us, it wrote Response, but it also added the use statement for this class at the top of the file! That is one of the best features of PhpStorm and I'm going to use it a lot. You will constantly see me type a class and allow PhpStorm to auto-complete it so that it adds the use statement to the top of the file for me.

Inside new Response(), add some text:

What a bewitching controller we have conjured!

... lines 1 - 2
namespace App\Controller;
use Symfony\Component\HttpFoundation\Response;
class QuestionController
public function homepage()
return new Response('What a bewitching controller we have conjured!');

And... done! We just created our first page! Let's try it! When we go to the homepage, it should execute our controller function... which returns the message.

Find your browser. We're already on the homepage... so just refresh. Say hello to our very first page. I know, it's not much to look at yet, but we've already covered the most foundational part of Symfony: the route and controller system.

Next, let's make our route fancier by using something called annotations. We'll also create a second page with a route that matches a wildcard path.

Leave a comment!

This tutorial also works great for Symfony 6!

What PHP libraries does this tutorial use?

// composer.json
    "require": {
        "php": "^7.3.0 || ^8.0.0",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "easycorp/easy-log-handler": "^1.0.7", // v1.0.9
        "sensio/framework-extra-bundle": "^6.0", // v6.2.1
        "symfony/asset": "5.0.*", // v5.0.11
        "symfony/console": "5.0.*", // v5.0.11
        "symfony/debug-bundle": "5.0.*", // v5.0.11
        "symfony/dotenv": "5.0.*", // v5.0.11
        "symfony/flex": "^1.3.1", // v1.17.5
        "symfony/framework-bundle": "5.0.*", // v5.0.11
        "symfony/monolog-bundle": "^3.0", // v3.5.0
        "symfony/profiler-pack": "*", // v1.0.5
        "symfony/routing": "5.1.*", // v5.1.11
        "symfony/twig-pack": "^1.0", // v1.0.1
        "symfony/var-dumper": "5.0.*", // v5.0.11
        "symfony/webpack-encore-bundle": "^1.7", // v1.8.0
        "symfony/yaml": "5.0.*" // v5.0.11
    "require-dev": {
        "symfony/profiler-pack": "^1.0" // v1.0.5