routing

Nice URLs are a must for any serious Web application. Does that mean something like index. PHP? The ugly URL article_id=57 should be replaced by /read/intro-to-symfony.

It’s more important to be flexible. If you need to change /blog to /news, what do you need to do? How many links do you need to search and update to make this change? If you are using Symfony routing, the change will be simple.

Create routing

A route is a mapping from a URL to a controller, if you want a route that exactly matches /blog and more dynamic routes that match anything like /blog/my-post and /blog/all-about-symfony URLs.

Routing is available in YAML, XML and PHP.All formats provide the same functionality and performance, so choose your preferred format. If you’re choosing PHP Annotations, run this command in your application to add support for them:

$ composer require annotations

Now you can configure the route:

Annotations

// src/Controller/BlogController.php namespace App\Controller; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\Routing\Annotation\Route; class BlogController extends AbstractController { /** * Matches /blog exactly * * @Route("/blog", name="blog_list") */ public function list() { // ... } /** * Matches /blog/* * * @Route("/blog/{slug}", name="blog_show") */ public function show($slug) { // $slug will equal the dynamic part of the URL // e.g. at /blog/yay-routing, then $slug='yay-routing' // ... }}

YAML

# config/routes.yaml
blog_list:
    path:     /blog
    controller: App\Controller\BlogController::list

blog_show:
    path:     /blog/{slug}
    controller: App\Controller\BlogController::show
    

XML

<! -- config/routes.xml --> <? The XML version = "1.0" encoding = "utf-8"? > <routes xmlns="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 route id="blog_list" controller="App\Controller\BlogController::list" path="/blog" > <! -- settings --> </route> <route id="blog_show" controller="App\Controller\BlogController::show" path="/blog/{slug}"> <! -- settings --> </route> </routes>

PHP

// config/routes.php
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
use App\Controller\BlogController;

$routes = new RouteCollection();
$routes->add('blog_list', new Route('/blog', array(
    '_controller' => [BlogController::class, 'list']
)));
$routes->add('blog_show', new Route('/blog/{slug}', array(
    '_controller' => [BlogController::class, 'show']
)));

return $routes;

Thanks to these two routes:

  • If the user visits/blog, matches the first route configuration andlist()Will be carried out;
  • If the user visits/blog/*, matches the second route configuration andshow()Will be executed because the routing path is/blog/{slug}, so$slugVariable is passed to the value that matchesshow(). For example, if the user accesses/blog/yay-routing, then$slugWill be equal to theyay-routing .

Whenever there is a {placeholder} in the route path, that part becomes a wildcard: it will match any value. Your controller now also has a parameter named $placeholder (wildcard and parameter name must match).

Each route also has an internal name: blog_list and blog_show. These can be anything (as long as each is unique) and need not have any special meaning. You’ll use them later to generate URLs.

Routing in other formats

The @route above each method is called the annotation. If you’d rather configure the Route using YAML, XML, or PHP, that’s fine! Simply create a new routing file (for example, Routes.xml) and Symfony automatically uses it.

Localized routing (i18n)

Routes can be localized to provide a unique path for each region. Symfony provides an easy way to declare localized routes without duplication.

Annotations

// src/Controller/CompanyController.php namespace App\Controller; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\Routing\Annotation\Route; class CompanyController extends AbstractController { /** * @Route({ * "nl": "/over-ons", * "en": "/about-us" * }, name="about_us") */ public function about() { // ... }}

YAML

# config/routes.yaml
about_us:
    path:
        nl: /over-ons
        en: /about-us
    controller: App\Controller\CompanyController::about
    

XML

<! -- config/routes.xml --> <? The XML version = "1.0" encoding = "utf-8"? > <routes xmlns="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 route id="about_us" controller="App\Controller\CompanyController::about"> <path locale="nl">/over-ons</path> <path locale="en">/about-us</path> </route> </routes>

PHP

// config/routes.php
namespace Symfony\Component\Routing\Loader\Configurator;

return function (RoutingConfigurator $routes) {
    $routes->add('about_us', ['nl' => '/over-ons', 'en' => '/about-us'])
        ->controller('App\Controller\CompanyController::about');
};

When a localized route matches, Symfony automatically identifies which region of the routing Settings should be used during the request. Defining a route in this way avoids the need for duplicate registration of routes and minimizes the risk of any errors caused by inconsistencies in definition.

Prefixing all routes is a common requirement for internationalized applications. This can be done by defining a different path prefix for each locale (you can set an empty prefix for the default language if you wish):

YAML

# config/routes/annotations.yaml controllers: resource: '.. /.. /src/Controller/' type: annotation prefix: en: '' # don't prefix URLs for English, the default locale nl: '/nl'

Add the {wildcard} condition

Imagine that the blog_list route would contain a paging list of blog topics that would contain URLs for pages 2 and 3, such as /blog/2 and /blog/3. If you change the path to /blog/{page}, you will run into a problem:

  • blog_list: /blog/{page}Will match/blog/*;
  • blog_show: /blog/{slug}Will still match/blog/*;

When the two routes match the same URL, the first route loaded wins. Unfortunately, this means that /blog/yay-routing will match blog_list.

To solve this problem, add a {page} wildcard to match only numbers:

Annotations

// src/Controller/BlogController.php namespace App\Controller; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\Routing\Annotation\Route; class BlogController extends AbstractController { /** * @Route("/blog/{page}", name="blog_list", requirements={"page"="\d+"}) */ public function list($page) { // ... } /** * @Route("/blog/{slug}", name="blog_show") */ public function show($slug) { // ... }}

YAML

# config/routes.yaml
blog_list:
    path:      /blog/{page}
    controller: App\Controller\BlogController::list
    requirements:
        page: '\d+'

blog_show:
    # ...

XML

<! -- config/routes.xml --> <? The XML version = "1.0" encoding = "utf-8"? > <routes xmlns="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 route id="blog_list" path="/blog/{page}" controller="App\Controller\BlogController::list"> <requirement key="page">\d+</requirement> </route> <! -... --> </routes>

PHP

// config/routes.php
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
use App\Controller\BlogController;

$routes = new RouteCollection();
$routes->add('blog_list', new Route('/blog/{page}', array(
    '_controller' => [BlogController::class, 'list'],
), array(
    'page' => '\d+'
)));

// ...

return $routes;

\d+ is a regular expression that matches a number of any length. Now:

URL Route Parameters
/blog/2 blog_list $page = 2
/blog/yay-routing blog_show $slug = yay-routing

If you wish, you can use the syntax {holder_name

} in each placeholder. This feature makes configuration more concise, but it reduces route readability when the requirements are complex:

Annotations

// src/Controller/BlogController.php namespace App\Controller; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\Routing\Annotation\Route; class BlogController extends AbstractController { /** * @Route("/blog/{page<\d+>}", name="blog_list") */ public function list($page) { // ... }}

YAML

# config/routes.yaml
blog_list:
    path:      /blog/{page<\d+>}
    controller: App\Controller\BlogController::list

XML

<! -- config/routes.xml --> <? The XML version = "1.0" encoding = "utf-8"? > <routes xmlns="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 route id="blog_list" path="/blog/{page<\d+>}" controller="App\Controller\BlogController::list" /> <! -... --> </routes>

PHP

// config/routes.php
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
use App\Controller\BlogController;

$routes = new RouteCollection();
$routes->add('blog_list', new Route('/blog/{page<\d+>}', array(
    '_controller' => [BlogController::class, 'list'],
)));

// ...

return $routes;

For additional routing conditions, such as HTTP methods, host names, and dynamic expressions, see How to Define Route Requirements

Give {placeholder} a default value

In the previous example, the path to blog_list is /blog/{page}. If the user accesses /blog/1, it will match. If the user accesses /blog, there will be no match. Whenever {placeholder} is added to the routing path, it must have a value.

So how do you get the blog_list to match again when the user visits /blog? By adding a default value:

Annotations

// src/Controller/BlogController.php namespace App\Controller; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\Routing\Annotation\Route; class BlogController extends AbstractController { /** * @Route("/blog/{page}", name="blog_list", requirements={"page"="\d+"}) */ public function list($page = 1) { // ... }}

YAML

# config/routes.yaml
blog_list:
    path:      /blog/{page}
    controller: App\Controller\BlogController::list
    defaults:
        page: 1
    requirements:
        page: '\d+'

blog_show:
    # ...
    

XML

<! -- config/routes.xml --> <? The XML version = "1.0" encoding = "utf-8"? > <routes xmlns="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 route id="blog_list" path="/blog/{page}" controller="App\Controller\BlogController::list"> <default key="page">1</default> <requirement key="page">\d+</requirement> </route> <! -... --> </routes>

PHP

// config/routes.php
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
use App\Controller\BlogController;

$routes = new RouteCollection();
$routes->add('blog_list', new Route(
    '/blog/{page}',
    array(
        '_controller' => [BlogController::class, 'list'],
        'page'        => 1,
    ),
    array(
        'page' => '\d+'
    )
));

// ...

return $routes;

Now, when the user accesses /blog, the blog_list route matches, and the $page route parameter defaults to 1.

As with the {wildcard} condition, use the syntax {holder_name? DEFAULT_VALUE} can also inline default values in each placeholder. This feature is compatible with inline conditions, so you can inline in a placeholder:

Annotations

// src/Controller/BlogController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;

class BlogController extends AbstractController
{
    /**
     * @Route("/blog/{page<\d+>?1}", name="blog_list")
     */
    public function list($page)
    {
        // ...
    }
}

YAML

# config/routes.yaml blog_list: path: /blog/{page<\d+>? 1} controller: App\Controller\BlogController::list

XML

<! -- config/routes.xml --> <? The XML version = "1.0" encoding = "utf-8"? > <routes xmlns="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 route id="blog_list" path="/blog/{page <\d+>?1}" controller="App\Controller\BlogController::list" /> <! -... --> </routes>

PHP

// config/routes.php use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\Route; use App\Controller\BlogController; $routes = new RouteCollection(); $routes->add('blog_list', new Route('/blog/{page<\d+>? 1}', array( '_controller' => [BlogController::class, 'list'], ))); / /... return $routes;

The value of the placeholder variable is if
nullVariable, need to be added at the end of the wildcard
?Character. (For example
/blog/{page? }).

All Routing List

As your application becomes more robust, a lot of routes will eventually be defined! To see everything, run the command:

$ php bin/console debug:router ------------------------------ -------- ------------------------------------- Name Method  Path ------------------------------ -------- ------------------------------------- app_lucky_number ANY /lucky/number/{max} ... ------------------------------ -------- -------------------------------------

Advanced Routing Examples

See an advanced example:

Annotations

// src/Controller/ArticleController.php

// ...
class ArticleController extends AbstractController
{
    /**
     * @Route(
     *     "/articles/{_locale}/{year}/{slug}.{_format}",
     *     defaults={"_format": "html"},
     *     requirements={
     *         "_locale": "en|fr",
     *         "_format": "html|rss",
     *         "year": "\d+"
     *     }
     * )
     */
    public function show($_locale, $year, $slug)
    {
    }
}

YAML

# config/routes.yaml
article_show:
  path:     /articles/{_locale}/{year}/{slug}.{_format}
  controller: App\Controller\ArticleController::show
  defaults:
      _format: html
  requirements:
      _locale:  en|fr
      _format:  html|rss
      year:     \d+

XML

<! -- config/routes.xml --> <? The XML version = "1.0" encoding = "utf-8"? > <routes xmlns="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 route id="article_show" path="/articles/{_locale}/{year}/{slug}.{_format}" controller="App\Controller\ArticleController::show"> <default key="_format">html</default> <requirement key="_locale">en|fr</requirement> <requirement key="_format">html|rss</requirement> <requirement key="year">\d+</requirement> </route> </routes>

PHP

// config/routes.php
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
use App\Controller\ArticleController;

$routes = new RouteCollection();
$routes->add(
    'article_show',
    new Route('/articles/{_locale}/{year}/{slug}.{_format}', array(
        '_controller' => [ArticleController::class, 'show'],
        '_format'     => 'html',
    ), array(
        '_locale' => 'en|fr',
        '_format' => 'html|rss',
        'year'    => '\d+',
    ))
);

return $routes;

As you can see, this path matches only if the {_locale} part of the URL is en or fr and {year} is a number. The example also shows how to replace /. Between placeholders with. The following URLs match:

  • /articles/en/2010/my-post
  • /articles/fr/2010/my-post.rss
  • /articles/en/2013/my-latest-post.html

_formatRouting parameters

The example highlights the _Format special routing parameter, which, when used, makes the matching value the “Request format” of the Request object.

Finally, the request format is used to set things like returning the Content-Type (for example, a JSON request format converts the Content-Type to application/ JSON).

Special routing parameter

As you can see, each route parameter or default value can ultimately be used as a parameter to a controller method. In addition, there are four special parameters: Each parameter has a unique function in the application:

_controller

The controller to execute when determining a route match

_format

Used to format the request (read more)

_fragment

Use to set the Fragment Identifier, the last optional part of the URL, beginning with the # character, to identify a section of the document.

_locale

Used to set the region on the request (read more)

The trailing slash redirects the URL

Historically, URLs have followed the UNIX convention of adding a trailing slash to the path (for example, https://example.com/foo/), which is referred to as a file (a) when the slash is removed. While it is possible to serve different content for two URLs, it is now common to treat both URLs as the same URL and redirect between them.

Symfony follows this logic and redirects between URLs with and without slashes (but only for GET and HEAD requests):

Route path If the requested URL is /foo If the requested URL is /foo/
/foo It matches (200 status response) It makes a 301 redirect to /foo
/foo/ It makes a 301 redirect to /foo/ It matches (200 status response)

If your application works for each path (
/foo
/foo/If a different route is defined, no automatic redirection will occur and the correct route will always match.

Automatic 301 redirection from /foo/ to /foo was introduced in Symfony4.1. In previous versions of Symfony, the response was 404.

Controller naming pattern

The format of the controller in the route is very simple Controller_class ::METHOD.

To refer to an action that is implemented as the __invoke() method of a controller class, you do not have to pass the method name, but can just use the fully qualified class name (e.g. AppControllerBlogController).

To generate the URL

The routing system can also generate URLs. In fact, routing is a two-way system: URLs are mapped to the controller and routes are resolved back to URLs.

To generate URLs, you need to specify the name of the route (such as blog_show) and any wildcards used in the path of the route (such as slug = my-blog-post). With this information, you can easily generate any URL:

class MainController extends AbstractController { public function show($slug) { // ... // /blog/my-blog-post $url = $this->generateUrl( 'blog_show', array('slug' => 'my-blog-post') ); }}

If you need to generate the URL from the service, inject the URLGeneratorInterface service.

// src/Service/SomeService.php use Symfony\Component\Routing\Generator\UrlGeneratorInterface; class SomeService { private $router; public function __construct(UrlGeneratorInterface $router) { $this->router = $router; } public function someMethod() { $url = $this->router->generate( 'blog_show', array('slug' => 'my-blog-post') ); / /... }}

Generate the URL using the query string

The generate() method takes a wildcard array to generate the URI. But if you pass extra values, they are added to the URI as a query string.

$this->router->generate('blog', array( 'page' => 2, 'category' => 'Symfony', )); // /blog/2? category=Symfony

Generate localized URLs

When routing is localized, Symfony defaults to using the current requested region to generate the URL. To generate URLs for different locales, you must pass _locale in the parameters array:

$this->router->generate('about_us', array(
    '_locale' => 'nl',
));
// generates: /over-ons

Generate the URL from the template

To generate URLs in Twig: see the Templates section. If you need to Generate URLs in JavaScript, see How to Generate Routing URLs in JavaScript

Generate absolute URLs

By default, a route will generate a relative URL (for example, /blog). In the controller, the UrlGeneratorInterface: : ABSOLUTE_URL passed to generateUrl () method of the third argument:

use Symfony\Component\Routing\Generator\UrlGeneratorInterface;

$this->generateUrl('blog_show', array('slug' => 'my-blog-post'), UrlGeneratorInterface::ABSOLUTE_URL);
// http://www.example.com/blog/my-blog-post

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 from the Console to learn how to solve this problem.

troubleshooting

Here are some common mistakes you might encounter when using routing:

Controller “AppControllerBlogController::show()” requires that you provide a value for the “$slug” argument.

This happens when your controller method has a parameter (such as $slug) :


public function show($slug)
{
    // ..
}

Your route does not have {slug} wildcards (e.g., /blog/show). Add {slug} to your routing path: /blog/show/{slug} or set a default value for the parameter (e.g. $slug = null).

Some mandatory parameters are missing (“slug”) to generate a URL for route “blog_show”.

This means that you are trying to generate the URL for the blog_show route, but you are not passing the slug value (which is required because there is a {slug} wildcard in the route path). To resolve this problem, pass the Slug value when generating the route:

$this->generateUrl('blog_show', array('slug' => 'slug-value'));

// or, in Twig
// {{ path('blog_show', {'slug': 'slug-value'}) }}