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 Symfony 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 path 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 path 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 blog_show is the
internal name of the route, which doesn't have any meaning yet and just needs
to be unique. Later, you'll use it to generate URLs.

If you don't want to use annotations, because you don't like them or because
you don't want to depend on the SensioFrameworkExtraBundle, you can also use
Yaml, XML or PHP. In these formats, 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, in this case the
AppBundle\Controller\BlogController::showAction method.

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 Symfony 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 Symfony 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 Symfony front controller (e.g. app.php);

The Symfony 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 Symfony 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
AppBundle:Main:homepage controller. The _controller string is
translated by Symfony into an actual PHP function and executed. That process
will be explained shortly in the Controller Naming Pattern section.

The path 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 path 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.

URL

Route

Parameters

/blog

blog

{page} = 1

/blog/1

blog

{page} = 1

/blog/2

blog

{page} = 2

Caution

Of course, you can have more than one optional placeholder (e.g.
/blog/{slug}/{page}), but everything after an optional placeholder must
be optional. For example, /{page}/blog is a valid path, but page
will always be required (i.e. simply /blog will not match this route).

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 or route conditions
(see Completely Customized Route Matching with Conditions). The routes in this example would work
perfectly if the /blog/{page} path 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

/blog/2-my-blog-post

blog_show

{slug} = 2-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 paths (/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.

As you've seen, a route can be made to match only certain routing wildcards
(via regular expressions), HTTP methods, or host names. But the routing system
can be extended to have an almost infinite flexibility using conditions:

The condition is an expression, and you can learn more about its syntax
here: The Expression Syntax. With this, the route
won't match unless the HTTP method is either GET or HEAD and if the User-Agent
header matches firefox.

You can do any complex logic you need in the expression by leveraging two
variables that are passed into the expression:

context

An instance of RequestContext, which
holds the most fundamental information about the route being matched.

As you've seen, this route will only match if the {_locale} 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 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: AppBundle\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). However, if you use
routing annotations, you'll need to point the router to the controllers with
the annotations. This can be done by "importing" directories into the routing
configuration:

YAML

1
2
3
4

# app/config/routing.ymlapp:resource:"@AppBundle/Controller/"type:annotation# required to enable the Annotation reader for this resource

XML

1
2
3
4
5
6
7
8
9
10

<!-- app/config/routing.xml --><?xml version="1.0" encoding="UTF-8" ?><routesxmlns="http://symfony.com/schema/routing"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd"><!-- the type is required to enable the annotation reader for this resource --><importresource="@AppBundle/Controller/"type="annotation"/></routes>

PHP

1
2
3
4
5
6
7
8
9
10
11

// app/config/routing.phpuseSymfony\Component\Routing\RouteCollection;$collection=newRouteCollection();$collection->addCollection(// second argument is the type, which is required to enable// the annotation reader for this resource$loader->import("@AppBundle/Controller/","annotation"));return$collection;

Note

When importing resources from YAML, the key (e.g. app) 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 a directory, where the @AppBundle shortcut syntax resolves to
the full path of the AppBundle. When pointing to a directory, all files in that
directory are parsed and put into the routing.

Note

You can also include other routing configuration files, this is often used
to import the routing of third party bundles:

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 debug:router console command. Execute
the command by running the following from the root of your project.

1

$ php app/console debug:router

New in version 2.6: Prior to Symfony 2.6, this command was called 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 bidirectional system: mapping the URL to a controller+parameters and
a route+parameters back to a URL. The
match() and
generate() methods form this bidirectional
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 path 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 front-end 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 automatically
detected using the current Request object. When generating absolute
URLs from outside the web context (for instance in a console command) this
doesn't work. See How to Generate URLs and Send Emails from the Console to learn how to
solve this problem.

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 bidirectional mechanism, meaning that it
should also be used to generate URLs.