Routing

Beautiful URLs are an absolute must for any serious web application. This
means leaving behind ugly URLs like index.php?article_id=57 in favor
of something like /read/intro-to-symfony.

Having flexibility is even more important. What if you need to change the
URL of a page from /blog to /news? How many links should you need to
hunt down and update to make the change? If you're using Symfony's router,
the change is simple.

The Symfony2 router lets you define creative URLs that you map to different
areas of your application. By the end of this chapter, you'll be able to:

A route is a map from a URL pattern to a controller. For example, suppose
you want to match any URL like /blog/my-post or /blog/all-about-symfony
and send it to a controller that can look up and render that blog entry.
The route is simple:

The pattern defined by the blog_show route acts like /blog/* where
the wildcard is given the name slug. For the URL /blog/my-blog-post,
the slug variable gets a value of my-blog-post, which is available
for you to use in your controller (keep reading).

The _controller parameter is a special key that tells Symfony which controller
should be executed when a URL matches this route. The _controller string
is called the logical name. It follows a
pattern that points to a specific PHP class and method:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

// src/Acme/BlogBundle/Controller/BlogController.phpnamespaceAcme\BlogBundle\Controller;useSymfony\Bundle\FrameworkBundle\Controller\Controller;classBlogControllerextendsController{publicfunctionshowAction($slug){// use the $slug variable to query the database$blog=...;return$this->render('AcmeBlogBundle:Blog:show.html.twig',array('blog'=>$blog,));}}

Congratulations! You've just created your first route and connected it to
a controller. Now, when you visit /blog/my-post, the showAction controller
will be executed and the $slug variable will be equal to my-post.

This is the goal of the Symfony2 router: to map the URL of a request to a
controller. Along the way, you'll learn all sorts of tricks that make mapping
even the most complex URLs easy.

When a request is made to your application, it contains an address to the
exact "resource" that the client is requesting. This address is called the
URL, (or URI), and could be /contact, /blog/read-me, or anything
else. Take the following HTTP request for example:

1

GET /blog/my-blog-post

The goal of the Symfony2 routing system is to parse this URL and determine
which controller should be executed. The whole process looks like this:

The request is handled by the Symfony2 front controller (e.g. app.php);

The Symfony2 core (i.e. Kernel) asks the router to inspect the request;

The router matches the incoming URL to a specific route and returns information
about the route, including the controller that should be executed;

The Symfony2 Kernel executes the controller, which ultimately returns
a Response object.

The routing layer is a tool that translates the incoming URL into a specific
controller to execute.

Symfony loads all the routes for your application from a single routing configuration
file. The file is usually app/config/routing.yml, but can be configured
to be anything (including an XML or PHP file) via the application configuration
file:

Even though all routes are loaded from a single file, it's common practice
to include additional routing resources. To do so, just point out in the
main routing configuration file which external files should be included.
See the Including External Routing Resources section for more
information.

This route matches the homepage (/) and maps it to the AcmeDemoBundle:Main:homepage
controller. The _controller string is translated by Symfony2 into an
actual PHP function and executed. That process will be explained shortly
in the Controller Naming Pattern section.

The pattern will match anything that looks like /blog/*. Even better,
the value matching the {slug} placeholder will be available inside your
controller. In other words, if the URL is /blog/hello-world, a $slug
variable, with a value of hello-world, will be available in the controller.
This can be used, for example, to load the blog post matching that string.

The pattern will not, however, match simply /blog. That's because,
by default, all placeholders are required. This can be changed by adding
a placeholder value to the defaults array.

So far, this route is as simple as possible - it contains no placeholders
and will only match the exact URL /blog. But what if you need this route
to support pagination, where /blog/2 displays the second page of blog
entries? Update the route to have a new {page} placeholder:

Like the {slug} placeholder before, the value matching {page} will
be available inside your controller. Its value can be used to determine which
set of blog posts to display for the given page.

But hold on! Since placeholders are required by default, this route will
no longer match on simply /blog. Instead, to see page 1 of the blog,
you'd need to use the URL /blog/1! Since that's no way for a rich web
app to behave, modify the route to make the {page} parameter optional.
This is done by including it in the defaults collection:

By adding page to the defaults key, the {page} placeholder is no
longer required. The URL /blog will match this route and the value of
the page parameter will be set to 1. The URL /blog/2 will also
match, giving the page parameter a value of 2. Perfect.

/blog

{page} = 1

/blog/1

{page} = 1

/blog/2

{page} = 2

Tip

Routes with optional parameters at the end will not match on requests
with a trailing slash (i.e. /blog/ will not match, /blog will match).

Can you spot the problem? Notice that both routes have patterns that match
URLs that look like /blog/*. The Symfony router will always choose the
first matching route it finds. In other words, the blog_show route
will never be matched. Instead, a URL like /blog/my-blog-post will match
the first route (blog) and return a nonsense value of my-blog-post
to the {page} parameter.

URL

route

parameters

/blog/2

blog

{page} = 2

/blog/my-blog-post

blog

{page} = my-blog-post

The answer to the problem is to add route requirements. The routes in this
example would work perfectly if the /blog/{page} pattern only matched
URLs where the {page} portion is an integer. Fortunately, regular expression
requirements can easily be added for each parameter. For example:

The \d+ requirement is a regular expression that says that the value of
the {page} parameter must be a digit (i.e. a number). The blog route
will still match on a URL like /blog/2 (because 2 is a number), but it
will no longer match a URL like /blog/my-blog-post (because my-blog-post
is not a number).

As a result, a URL like /blog/my-blog-post will now properly match the
blog_show route.

URL

route

parameters

/blog/2

blog

{page} = 2

/blog/my-blog-post

blog_show

{slug} = my-blog-post

Earlier Routes always Win

What this all means is that the order of the routes is very important.
If the blog_show route were placed above the blog route, the
URL /blog/2 would match blog_show instead of blog since the
{slug} parameter of blog_show has no requirements. By using proper
ordering and clever requirements, you can accomplish just about anything.

Since the parameter requirements are regular expressions, the complexity
and flexibility of each requirement is entirely up to you. Suppose the homepage
of your application is available in two different languages, based on the
URL:

In addition to the URL, you can also match on the method of the incoming
request (i.e. GET, HEAD, POST, PUT, DELETE). Suppose you have a contact form
with two controllers - one for displaying the form (on a GET request) and one
for processing the form when it's submitted (on a POST request). This can
be accomplished with the following route configuration:

Despite the fact that these two routes have identical patterns (/contact),
the first route will match only GET requests and the second route will match
only POST requests. This means that you can display the form and submit the
form via the same URL, while using distinct controllers for the two actions.

Note

If no _method requirement is specified, the route will match on
all methods.

Like the other requirements, the _method requirement is parsed as a regular
expression. To match GETorPOST requests, you can use GET|POST.

As you've seen, this route will only match if the {culture} portion of
the URL is either en or fr and if the {year} is a number. This
route also shows how you can use a dot between placeholders instead of
a slash. URLs matching this route might look like:

/articles/en/2010/my-post

/articles/fr/2010/my-post.rss

/articles/en/2013/my-latest-post.html

The Special _format Routing Parameter

This example also highlights the special _format routing parameter.
When using this parameter, the matched value becomes the "request format"
of the Request object. Ultimately, the request format is used for such
things such as setting the Content-Type of the response (e.g. a json
request format translates into a Content-Type of application/json).
It can also be used in the controller to render a different template for
each value of _format. The _format parameter is a very powerful way
to render the same content in different formats.

As you've seen, each routing parameter or default value is eventually available
as an argument in the controller method. Additionally, there are three parameters
that are special: each adds a unique piece of functionality inside your application:

_controller: As you've seen, this parameter is used to determine which
controller is executed when the route is matched;

Every route must have a _controller parameter, which dictates which
controller should be executed when that route is matched. This parameter
uses a simple string pattern called the logical controller name, which
Symfony maps to a specific PHP method and class. The pattern has three parts,
each separated by a colon:

Notice that Symfony adds the string Controller to the class name (Blog
=> BlogController) and Action to the method name (show => showAction).

You could also refer to this controller using its fully-qualified class name
and method: Acme\BlogBundle\Controller\BlogController::showAction.
But if you follow some simple conventions, the logical name is more concise
and allows more flexibility.

Note

In addition to using the logical name or the fully-qualified class name,
Symfony supports a third way of referring to a controller. This method
uses just one colon separator (e.g. service_name:indexAction) and
refers to the controller as a service (see How to define Controllers as Services).

The route parameters (e.g. {slug}) are especially important because
each is made available as an argument to the controller method:

1
2
3
4

publicfunctionshowAction($slug){// ...}

In reality, the entire defaults collection is merged with the parameter
values to form a single array. Each key of that array is available as an
argument on the controller.

In other words, for each argument of your controller method, Symfony looks
for a route parameter of that name and assigns its value to that argument.
In the advanced example above, any combination (in any order) of the following
variables could be used as arguments to the showAction() method:

All routes are loaded via a single configuration file - usually app/config/routing.yml
(see Creating Routes above). Commonly, however, you'll want to load routes
from other places, like a routing file that lives inside a bundle. This can
be done by "importing" that file:

When importing resources from YAML, the key (e.g. acme_hello) is meaningless.
Just be sure that it's unique so no other lines override it.

The resource key loads the given routing resource. In this example the
resource is the full path to a file, where the @AcmeHelloBundle shortcut
syntax resolves to the path of that bundle. The imported file might look
like this:

While adding and customizing routes, it's helpful to be able to visualize
and get detailed information about your routes. A great way to see every route
in your application is via the router:debug console command. Execute
the command by running the following from the root of your project.

1

$ php app/console router:debug

This command will print a helpful list of all the configured routes in
your application:

The routing system should also be used to generate URLs. In reality, routing
is a bi-directional system: mapping the URL to a controller+parameters and
a route+parameters back to a URL. The
match() and
generate() methods form this bi-directional
system. Take the blog_show example route from earlier:

To generate a URL, you need to specify the name of the route (e.g. blog_show)
and any wildcards (e.g. slug = my-blog-post) used in the pattern for
that route. With this information, any URL can easily be generated:

In an upcoming section, you'll learn how to generate URLs from inside templates.

Tip

If the frontend of your application uses AJAX requests, you might want
to be able to generate URLs in JavaScript based on your routing configuration.
By using the FOSJsRoutingBundle, you can do exactly that:

The host that's used when generating an absolute URL is the host of
the current Request object. This is detected automatically based
on server information supplied by PHP. When generating absolute URLs for
scripts run from the command line, you'll need to manually set the desired
host on the RequestContext object:

Routing is a system for mapping the URL of incoming requests to the controller
function that should be called to process the request. It both allows you
to specify beautiful URLs and keeps the functionality of your application
decoupled from those URLs. Routing is a two-way mechanism, meaning that it
should also be used to generate URLs.