03.

Routes, Controllers, Pages, oh my!

Share this awesome video!

|

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
}
}

Tip

When you auto-complete the @Route annotation, be sure to choose the one from Symfony\Component\Routing - the one we chose is now deprecated. Both work the same.

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!