Symfony Routing and Controllers


In this video we take a look at how we can use Controllers and Routing inside a Symfony2.7 application to direct traffic around based on what URI the site user is requesting, and then 'control' what that user ultimately can see or do once the page renders.

You may have heard of the MVC / Model View Controller software architectural pattern and found yourself left with more questions than answers.

A Controller is a somewhat confusing term - the first time I heard of it I was left wondering what is being controlled, and why?

A Controller is an apparently relatively straightforward concept. It controls things.

We have discussed in the previous video how Symfony is primarily concerned with handling Requests, and returning Responses.

A controller takes Requests, does something with them, and then returns a Response. That is the job of the controller in a nutshell.

The something it does is specific to your application. If you need to register a new User, that's the something. If you need to delete a record from the database, that's the something.

Remember - a Controller is concerned with taking our Request and generating some sort of Response.

A Controller must always return a Response.

Control, Control, You Must Learn Control

With that said, a Controller is really only concerned with Controlling.

It should not be responsible for the doing.

Think of the Controller as the team manager, sat in his or her office, claiming a fat salary whilst (seemingly) doing fairly little. It is the responsibility of the team manager to make sure his team of workers know how to do all the individual tasks that make his / her department operate as smoothly as silk.

Of course, spend enough time in most organisations and you will inevitably find managers who are ahem low quality.

Take the concept of a micro-manager. This guy / girl can't help themselves from getting involved in all the specifics of their team members jobs. And sometimes other department members tasks as well.

This is a common problem with Controllers. You will hear terms like 'Controller Bloat' or 'Fat Controllers'. This is where a Controller action is doing too much.

The aim is for each Controller action to do as little as possible. The bare minimum to get the job done and no more.

It's well worth reading the official Symfony Best Practices guidelines for Controllers, paying attention to their 5-10-20 rule.

Define only 5 variables or less, contain 10 actions or less, and each action should be 20 lines or less.

It's not always possible, but having had the misfortune of seeing Controller actions of 1000 lines or more, let me give you the benefit of my experience by saying that is absolutely no fun for anyone.

Who Is Doing All The Work Then?

If Controllers are delegating everything to the worker bees, who are these worker bees and where can we find them?

Well, Symfony is all about Services.

Services are the things that actually do the work.

In our team manager / team member analogy, our team manager is the Controller, our team members are Services.

I don't cover Services in this video, but I have covered them before.

When first starting out experimenting with Symfony, don't bother with worrying about services. Stick your logic right there in the Controller. But as your project grows and becomes more serious, don't keep doing this as it's really bad practice.

Now, saying it's bad practice might not stop you. So let me put it another way.

If you do this, you will quickly find yourself building a system that stinks. No one will want to work on your code - and that includes you. It makes testing extremely difficult and that inevitably leads to a shonky application that breaks seemingly at random.

Seriously. Please! Don't.

Routing Tooting Cowboy

Controllers are all well and good, but how are these actions that we define actually invoked?

Or, to put it in none-geek-speak, how is Symfony able to figure out that when our web surfers (lol) visit a specific URI that they are going to be served by Action 1 on Controller X?

Well, thankfully this is dictated by us as developers. We specify routes.

There are a number of ways to create routes inside a Symfony application, but as of Symfony 2.7 the Best Practice guidelines suggest using the @Route annotation.

Say what now?

Yes, Symfony lingo if ever I heard it.

Annotations are a funny concept - not unique to Symfony, but heavily used throughout the Symfony project - that use comments to tell our Symfony application that things mean things.

An example illustrates this much better than words. But before we cover the example, it's important to say that this only works because of the default routing.yml file containing this as of Symfony2.7:

# app/config/routing.yml
app:
    resource: "@AppBundle/Controller/"
    type:     annotation

This little piece of code tells Symfony to look inside our src/AppBundle/Controller directory, find any valid Controllers, and read through to find any annotations containing our routes:

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;

// * snip - our Controller class definition etc*

/**
 * @Route("/category/", name = "category_list")
 */
public function someAction(Request $request)
{
    // some logic to find all our categories and display them all
}

Here we have a comment that is not a real comment. Well, it is a real comment, but it has additional functionality.

/**
 * @Route("/category/", name = "category_list")
 */

Behind the scenes, Symfony will read our Controller files (thanks to that app/config/routing.yml piece we saw earlier) and figure out what routes are available in our application.

In essence, that's how we map URI's to specific actions.

As in the video, we can use parameters in our route defintions to allow more fancy routing schemes:

/**
 * @Route("/category/{categorySlug}", name = "category_show")
 */
public function someAction(Request $request, $categorySlug)
{
    // *snip*
}

Assuming our site is test.com, this route would match:

// http://test.com/category/pickles
// http://test.com/category/eggs
// http://test.com/category/jam-on-toast

And pretty much anything else after the /category/ part, and then give it to us to use inside our action, so long as we put a matching variable name into the action's method parameters:

public function someAction(Request $request, $categorySlug)
// categorySlug is what we are interested in here

Clever, eh?

I think so.

You can be more specific about what should be matched - maybe only letters, maybe only numbers, maybe some other scheme that you see fit.

Command Line Help

A highly useful command to use on the command line when dealing with Controllers and Routing is:

php app/console debug:router

This will list all out the routes in your project.

If you ever can't remember a command line command, just do:

php app/console

And hit return, and you will be shown all the available commands in your Symfony project.

Episodes