Routes, Controllers, Pages, oh my!

Let's create our first page! Actually, this is the main job of a framework: to give you a route and controller system. A route is configuration that defines the URL for a page and a controller is a function that we write that actually builds the content for that page.

And right now... our app is really small! Instead of weighing down your project with every possible feature you could ever need - after all, we're not in zero-gravity yet - a Symfony app is basically just a small route-controller system. Later, we'll install more features when we need them, like a warp drive! Those always come in handy. Adding more features is actually going to be pretty awesome. More on that later.

First Route & Controller

Open your app's main routing file: config/routes.yaml:

4 lines config/routes.yaml
#index:
# path: /
# controller: App\Controller\DefaultController::index

Hey! We already have an example! Uncomment that. Ignore the index key for now: that's the internal name of the route, but it's not important yet.

This says that when someone goes to the homepage - / - Symfony should execute an index() method in a DefaultController class. Change this to ArticleController and the method to homepage:

4 lines config/routes.yaml
index:
path: /
controller: App\Controller\ArticleController::homepage

And... yea! That's a route! Hi route! It defines the URL and tells Symfony what controller function to execute.

The controller class doesn't exist yet, so let's create it! Right-click on the Controller directory and go to "New" or press Cmd+N on a Mac. Choose "PHP Class". And, yes! Remember that Composer setup we did in Preferences? Thanks to that, PhpStorm correctly guesses the namespace! The force is strong with this one... The namespace for every class in src/ should be App plus whatever sub-directory it's in.

Name this ArticleController:

14 lines src/Controller/ArticleController.php
<?php
namespace App\Controller;
... lines 4 - 6
class ArticleController
{
... lines 9 - 12
}

And inside, add public function homepage():

14 lines src/Controller/ArticleController.php
... lines 1 - 2
namespace App\Controller;
... lines 4 - 6
class ArticleController
{
public function homepage()
{
... line 11
}
}

This function is the controller... and it's our place to build the page. To be more confusing, it's also called an "action", or "ghob" to its Klingon friends.

Anyways, we can do whatever we want here: make database queries, API calls, take soil samples looking for organic materials or render a template. There's just one rule: a controller must return a Symfony Response object.

So let's say: return new Response(): we want the one from HttpFoundation. Give it a calm message: OMG! My first page already! WOOO!:

14 lines src/Controller/ArticleController.php
... lines 1 - 2
namespace App\Controller;
use Symfony\Component\HttpFoundation\Response;
class ArticleController
{
public function homepage()
{
return new Response('OMG! My first page already! WOOO!');
}
}

Ahem. Oh, and check this out: when I let PhpStorm auto-complete the Response class it added this use statement to the top of the file automatically:

14 lines src/Controller/ArticleController.php
... lines 1 - 4
use Symfony\Component\HttpFoundation\Response;
... lines 6 - 14

You'll see me do that a lot. Good job Storm!

Let's try the page! Find your browser. Oh, this "Welcome" page only shows if you don't have any routes configured. Refresh! Yes! This is our page. Our first of many.

Annotation Routes

That was pretty easy, but it can be easier! Instead of creating our routes in YAML, let's use a cool feature called annotations. This is an extra feature, so we need to install it. Find your open terminal and run:

composer require annotations

Interesting... this annotations package actually installed sensio/framework-extra-bundle. We're going to talk about how that works very soon.

Now, about these annotation routes. Comment-out the YAML route:

4 lines config/routes.yaml
#index:
# path: /
# controller: App\Controller\ArticleController::homepage

Then, in ArticleController, above the controller method, add /**, hit enter, clear this out, and say @Route(). You can use either class - but make sure PhpStorm adds the use statement on top. Then add "/":

18 lines src/Controller/ArticleController.php
... lines 1 - 4
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
... lines 6 - 7
class ArticleController
{
/**
* @Route("/")
*/
public function homepage()
{
... line 15
}
}

That's it! The route is defined right above the controller, which is why I love annotation routes: everything is in one place. But don't trust me, find your browser and refresh. It's a traaaap! I mean, it works!

Tip

What exactly are annotations? They're PHP comments that are read as configuration.

Fancy Wildcard Routes

So what else can we do with routes? Create another public function called show(). I want this page to eventually display a full article. Give it a route: @Route("/news/why-asteroids-taste-like-bacon"):

26 lines src/Controller/ArticleController.php
... lines 1 - 4
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
... lines 6 - 7
class ArticleController
{
... lines 10 - 17
/**
* @Route("/news/why-asteroids-taste-like-bacon")
*/
public function show()
{
... line 23
}
}

Eventually, this is how we want our URLs to look. This is called a "slug", it's a URL version of the title. As usual, return a new Response('Future page to show one space article!'):

26 lines src/Controller/ArticleController.php
... lines 1 - 4
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Component\HttpFoundation\Response;
class ArticleController
{
... lines 10 - 17
/**
* @Route("/news/why-asteroids-taste-like-bacon")
*/
public function show()
{
return new Response('Future page to show one space article!');
}
}

Perfect! Copy that URL and try it in your browser. It works... but this sucks! I don't want to build a route and controller for every single article that lives in the database. Nope, we need a route that can match /news/ anything. How? Use {slug}:

29 lines src/Controller/ArticleController.php
... lines 1 - 7
class ArticleController
{
... lines 10 - 17
/**
* @Route("/news/{slug}")
*/
public function show($slug)
{
... lines 23 - 26
}
}

This route now matches /news/ anything: that {slug} is a wildcard. Oh, and the name slug could be anything. But whatever you choose now becomes available as an argument to your "ghob", I mean your action.

So let's refactor our success message to say:

Future page to show the article

And then that slug:

29 lines src/Controller/ArticleController.php
... lines 1 - 7
class ArticleController
{
... lines 10 - 17
/**
* @Route("/news/{slug}")
*/
public function show($slug)
{
return new Response(sprintf(
'Future page to show the article: "%s"',
$slug
));
}
}

Try it! Refresh the same URL. Yes! It matches the route and the slug prints! Change it to something else: /why-asteroids-taste-like-tacos. So delicious! Go back to bacon... because... ya know... everyone knows that's what asteroids really taste like.

And... yes! We're 3 chapters in and you now know the first half of Symfony: the route & controller system. Sure, you can do fancier things with routes, like match regular expressions, HTTP methods or host names - but that will all be pretty easy for you now.

It's time to move on to something really important: it's time to learn about Symfony Flex and the recipe system. Yum!

Leave a comment!

  • 2018-01-18 Victor Bocharsky

    Hey locad5 ,

    Take a look at makx21 answer, probably it will give you some clarification and maybe that's your case: https://knpuniversity.com/s... . It's also depends on what version of "apache-pack" you requires, it should be one of 1.0.x as I see from contrib repo: https://github.com/symfony/...

    Cheers!

  • 2018-01-18 Victor Bocharsky

    Hey makx21,

    Good point! Thanks for sharing it with us. You can set extra.symfony.allow-contrib to true manually or by following interactive questions during the Composer installation process.

    Cheers!

  • 2018-01-18 makx21

    This is a contributed recipe, you
    will need to allow flex to use them if you haven't previously.

    composer config extra.symfony.allow-contrib true

  • 2018-01-18 Diego Aguiar

    Hmm, interesting, I thought it was related to a composer's bug from a previous version, but you are using composer's latest version, so that's not the case.
    I wonder what caused that weird behaviour...

  • 2018-01-17 locad5

    Hey Diego Aguiar
    Composer version 1.6.2
    Mac OS High Sierra version 10.13.2

  • 2018-01-17 Diego Aguiar

    Hey locad5

    That's weird, can you tell me which composer version and which OS are you using?

  • 2018-01-17 locad5

    Hey weaverryan,
    Thank for your quick response, I already run composer symphony/apache-pack, but the .htaccess was not generated automatically. I put a basic .htaccess file inside /public folder and it works now.
    Thanks for your help
    Cheers!

  • 2018-01-17 weaverryan

    Hey locad5!

    I think you just may be missing the Apache rewrite rules that tell Apache to always use execute index.php file, regardless of the URL. Fortunately, Flex can add the .htaccess file you need automatically. Run composer require symfony/apache-pack.

    Let me know if that helps!

    Cheers!

  • 2018-01-17 locad5

    Hi,
    it works with http://localhost:8000/ and it's equivalent as http://localhost/advert/public for production apache environment, using PHP annotations or not.

    However, for any other routes like http://localhost/advert/public/goodbye, apache answers 'The requested URL /advert/public/goodbye was not found on this server.'

    Using this route:

    /**
    * @Route("/goodbye")
    */

    Do you have an idea ?
    Thanks

  • 2018-01-17 elbarto

    thanks for the answer :)

  • 2018-01-16 Diego Aguiar

    Hey @elbarto (or Bart Simpson :P)

    Doctrine devs never implemented it to work in both ways

    Cheers!

  • 2018-01-16 elbarto

    Hi,

    Shouldn't it work as well when we specify the route (using annotations) with simple quotes ?
    @Route('/' , name='homepage')

    because using simple quotes gives me an error : [Syntax Error] Expected PlainValue, got ''' at position 7 in method App\Controller\HomeController::homepage()

  • 2018-01-09 Diego Aguiar

    Hey Ivan Mauricio Rey

    This tutorial is still been released, i.e. only a few chapters has a video
    If you check in the chapter's list, it says "coming soon", but maybe we should make it more visible :)

    Cheers!

  • 2018-01-09 Ivan Mauricio Rey

    There is no video on this chapter?