Routing

Routing

When your application receives a request, it calls a controller action to generate the response. The routing configuration defines which action to run for each incoming URL. It also provides other useful features, like generating SEO-friendly URLs (e.g. /read/intro-to-symfony instead of index.php?article_id=57).

Creating Routes

Routes can be configured in YAML, XML, PHP or using either attributes or annotations. All formats provide the same features and performance, so choose your favorite. Symfony recommends attributes because it’s convenient to put the route and controller in the same place.

Creating Routes as Attributes or Annotations

On PHP 8, you can use native attributes to configure routes right away. On PHP 7, where attributes are not available, you can use annotations instead, provided by the Doctrine Annotations library.

In case you want to use annotations instead of attributes, run this command once in your application to enable them:

  1. $ composer require doctrine/annotations

New in version 5.2: The ability to use PHP attributes to configure routes was introduced in Symfony 5.2. Prior to this, Doctrine Annotations were the only way to annotate controller actions with routing configuration.

This command also creates the following configuration file:

  1. # config/routes/annotations.yaml
  2. controllers:
  3. resource: ../../src/Controller/
  4. type: annotation
  5. kernel:
  6. resource: ../../src/Kernel.php
  7. type: annotation

This configuration tells Symfony to look for routes defined as annotations in any PHP class stored in the src/Controller/ directory.

Suppose you want to define a route for the /blog URL in your application. To do so, create a controller class like the following:

  • Annotations

    1. // src/Controller/BlogController.php
    2. namespace App\Controller;
    3. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    4. use Symfony\Component\Routing\Annotation\Route;
    5. class BlogController extends AbstractController
    6. {
    7. /**
    8. * @Route("/blog", name="blog_list")
    9. */
    10. public function list()
    11. {
    12. // ...
    13. }
    14. }
  • Attributes

    1. // src/Controller/BlogController.php
    2. namespace App\Controller;
    3. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    4. use Symfony\Component\Routing\Annotation\Route;
    5. class BlogController extends AbstractController
    6. {
    7. #[Route('/blog', name: 'blog_list')]
    8. public function list()
    9. {
    10. // ...
    11. }
    12. }

This configuration defines a route called blog_list that matches when the user requests the /blog URL. When the match occurs, the application runs the list() method of theBlogController` class.

Note

The query string of a URL is not considered when matching routes. In this example, URLs like /blog?foo=bar and /blog?foo=bar&bar=foo will also match the blog_list route.

Caution

If you define multiple PHP classes in the same file, Symfony only loads the routes of the first class, ignoring all the other routes.

The route name (blog_list) is not important for now, but it will be essential later when generating URLs. You only have to keep in mind that each route name must be unique in the application.

Creating Routes in YAML, XML or PHP Files

Instead of defining routes in the controller classes, you can define them in a separate YAML, XML or PHP file. The main advantage is that they don’t require any extra dependency. The main drawback is that you have to work with multiple files when checking the routing of some controller action.

The following example shows how to define in YAML/XML/PHP a route called blog_list that associates the /blog URL with the list() action of theBlogController`:

  • YAML

    1. # config/routes.yaml
    2. blog_list:
    3. path: /blog
    4. # the controller value has the format 'controller_class::method_name'
    5. controller: App\Controller\BlogController::list
    6. # if the action is implemented as the __invoke() method of the
    7. # controller class, you can skip the '::method_name' part:
    8. # controller: App\Controller\BlogController
  • XML

    1. <!-- config/routes.xml -->
    2. <?xml version="1.0" encoding="UTF-8" ?>
    3. <routes xmlns="http://symfony.com/schema/routing"
    4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    5. xsi:schemaLocation="http://symfony.com/schema/routing
    6. https://symfony.com/schema/routing/routing-1.0.xsd">
    7. <!-- the controller value has the format 'controller_class::method_name' -->
    8. <route id="blog_list" path="/blog"
    9. controller="App\Controller\BlogController::list"/>
    10. <!-- if the action is implemented as the __invoke() method of the
    11. controller class, you can skip the '::method_name' part:
    12. controller="App\Controller\BlogController"/> -->
    13. </routes>
  • PHP

    1. // config/routes.php
    2. use App\Controller\BlogController;
    3. use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
    4. return function (RoutingConfigurator $routes) {
    5. $routes->add('blog_list', '/blog')
    6. // the controller value has the format [controller_class, method_name]
    7. ->controller([BlogController::class, 'list'])
    8. // if the action is implemented as the __invoke() method of the
    9. // controller class, you can skip the 'method_name' part:
    10. // ->controller(BlogController::class)
    11. ;
    12. };

New in version 5.1: Starting from Symfony 5.1, by default Symfony only loads the routes defined in YAML format. If you define routes in XML and/or PHP formats, update the src/Kernel.php file to add support for the .xml and .php file extensions.

Matching HTTP Methods

By default, routes match any HTTP verb (GET, POST, PUT, etc.) Use the methods option to restrict the verbs each route should respond to:

  • Annotations

    1. // src/Controller/BlogApiController.php
    2. namespace App\Controller;
    3. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    4. use Symfony\Component\HttpFoundation\Response;
    5. use Symfony\Component\Routing\Annotation\Route;
    6. class BlogApiController extends AbstractController
    7. {
    8. /**
    9. * @Route("/api/posts/{id}", methods={"GET","HEAD"})
    10. */
    11. public function show(int $id): Response
    12. {
    13. // ... return a JSON response with the post
    14. }
    15. /**
    16. * @Route("/api/posts/{id}", methods={"PUT"})
    17. */
    18. public function edit(int $id): Response
    19. {
    20. // ... edit a post
    21. }
    22. }
  • Attributes

    1. // src/Controller/BlogApiController.php
    2. namespace App\Controller;
    3. // ...
    4. class BlogApiController extends AbstractController
    5. {
    6. #[Route('/api/posts/{id}', methods: ['GET', 'HEAD'])]
    7. public function show(int $id): Response
    8. {
    9. // ... return a JSON response with the post
    10. }
    11. #[Route('/api/posts/{id}', methods: ['PUT'])]
    12. public function edit(int $id): Response
    13. {
    14. // ... edit a post
    15. }
    16. }
  • YAML

    1. # config/routes.yaml
    2. api_post_show:
    3. path: /api/posts/{id}
    4. controller: App\Controller\BlogApiController::show
    5. methods: GET|HEAD
    6. api_post_edit:
    7. path: /api/posts/{id}
    8. controller: App\Controller\BlogApiController::edit
    9. methods: PUT
  • XML

    1. <!-- config/routes.xml -->
    2. <?xml version="1.0" encoding="UTF-8" ?>
    3. <routes xmlns="http://symfony.com/schema/routing"
    4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    5. xsi:schemaLocation="http://symfony.com/schema/routing
    6. https://symfony.com/schema/routing/routing-1.0.xsd">
    7. <route id="api_post_show" path="/api/posts/{id}"
    8. controller="App\Controller\BlogApiController::show"
    9. methods="GET|HEAD"/>
    10. <route id="api_post_edit" path="/api/posts/{id}"
    11. controller="App\Controller\BlogApiController::edit"
    12. methods="PUT"/>
    13. </routes>
  • PHP

    1. // config/routes.php
    2. use App\Controller\BlogApiController;
    3. use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
    4. return function (RoutingConfigurator $routes) {
    5. $routes->add('api_post_show', '/api/posts/{id}')
    6. ->controller([BlogApiController::class, 'show'])
    7. ->methods(['GET', 'HEAD'])
    8. ;
    9. $routes->add('api_post_edit', '/api/posts/{id}')
    10. ->controller([BlogApiController::class, 'edit'])
    11. ->methods(['PUT'])
    12. ;
    13. };

Tip

HTML forms only support GET and POST methods. If you’re calling a route with a different method from an HTML form, add a hidden field called _method with the method to use (e.g. <input type="hidden" name="_method" value="PUT"/>). If you create your forms with Symfony Forms this is done automatically for you.

Matching Expressions

Use the condition option if you need some route to match based on some arbitrary matching logic:

  • Annotations

    1. // src/Controller/DefaultController.php
    2. namespace App\Controller;
    3. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    4. use Symfony\Component\HttpFoundation\Response;
    5. use Symfony\Component\Routing\Annotation\Route;
    6. class DefaultController extends AbstractController
    7. {
    8. /**
    9. * @Route(
    10. * "/contact",
    11. * name="contact",
    12. * condition="context.getMethod() in ['GET', 'HEAD'] and request.headers.get('User-Agent') matches '/firefox/i'"
    13. * )
    14. *
    15. * expressions can also include configuration parameters:
    16. * condition: "request.headers.get('User-Agent') matches '%app.allowed_browsers%'"
    17. */
    18. public function contact(): Response
    19. {
    20. // ...
    21. }
    22. }
  • Attributes

    1. // src/Controller/DefaultController.php
    2. namespace App\Controller;
    3. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    4. use Symfony\Component\HttpFoundation\Response;
    5. use Symfony\Component\Routing\Annotation\Route;
    6. class DefaultController extends AbstractController
    7. {
    8. #[Route(
    9. '/contact',
    10. name: 'contact',
    11. condition: "context.getMethod() in ['GET', 'HEAD'] and request.headers.get('User-Agent') matches '/firefox/i'",
    12. )]
    13. // expressions can also include config parameters:
    14. // condition: "request.headers.get('User-Agent') matches '%app.allowed_browsers%'"
    15. public function contact(): Response
    16. {
    17. // ...
    18. }
    19. }
  • YAML

    1. # config/routes.yaml
    2. contact:
    3. path: /contact
    4. controller: 'App\Controller\DefaultController::contact'
    5. condition: "context.getMethod() in ['GET', 'HEAD'] and request.headers.get('User-Agent') matches '/firefox/i'"
    6. # expressions can also include configuration parameters:
    7. # condition: "request.headers.get('User-Agent') matches '%app.allowed_browsers%'"
  • XML

    1. <!-- config/routes.xml -->
    2. <?xml version="1.0" encoding="UTF-8" ?>
    3. <routes xmlns="http://symfony.com/schema/routing"
    4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    5. xsi:schemaLocation="http://symfony.com/schema/routing
    6. https://symfony.com/schema/routing/routing-1.0.xsd">
    7. <route id="contact" path="/contact" controller="App\Controller\DefaultController::contact">
    8. <condition>context.getMethod() in ['GET', 'HEAD'] and request.headers.get('User-Agent') matches '/firefox/i'</condition>
    9. <!-- expressions can also include configuration parameters: -->
    10. <!-- <condition>request.headers.get('User-Agent') matches '%app.allowed_browsers%'</condition> -->
    11. </route>
    12. </routes>
  • PHP

    1. // config/routes.php
    2. use App\Controller\DefaultController;
    3. use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
    4. return function (RoutingConfigurator $routes) {
    5. $routes->add('contact', '/contact')
    6. ->controller([DefaultController::class, 'contact'])
    7. ->condition('context.getMethod() in ["GET", "HEAD"] and request.headers.get("User-Agent") matches "/firefox/i"')
    8. // expressions can also include configuration parameters:
    9. // 'request.headers.get("User-Agent") matches "%app.allowed_browsers%"'
    10. ;
    11. };

The value of the condition option is any valid ExpressionLanguage expression and can use any of these variables created by Symfony:

context

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

request

The Symfony Request object that represents the current request.

Behind the scenes, expressions are compiled down to raw PHP. Because of this, using the condition key causes no extra overhead beyond the time it takes for the underlying PHP to execute.

Caution

Conditions are not taken into account when generating URLs (which is explained later in this article).

Debugging Routes

As your application grows, you’ll eventually have a lot of routes. Symfony includes some commands to help you debug routing issues. First, the debug:router command lists all your application routes in the same order in which Symfony evaluates them:

  1. $ php bin/console debug:router
  2. ---------------- ------- ------- ----- --------------------------------------------
  3. Name Method Scheme Host Path
  4. ---------------- ------- ------- ----- --------------------------------------------
  5. homepage ANY ANY ANY /
  6. contact GET ANY ANY /contact
  7. contact_process POST ANY ANY /contact
  8. article_show ANY ANY ANY /articles/{_locale}/{year}/{title}.{_format}
  9. blog ANY ANY ANY /blog/{page}
  10. blog_show ANY ANY ANY /blog/{slug}
  11. ---------------- ------- ------- ----- --------------------------------------------

Pass the name (or part of the name) of some route to this argument to print the route details:

  1. $ php bin/console debug:router app_lucky_number
  2. +-------------+---------------------------------------------------------+
  3. | Property | Value |
  4. +-------------+---------------------------------------------------------+
  5. | Route Name | app_lucky_number |
  6. | Path | /lucky/number/{max} |
  7. | ... | ... |
  8. | Options | compiler_class: Symfony\Component\Routing\RouteCompiler |
  9. | | utf8: true |
  10. +-------------+---------------------------------------------------------+

The other command is called router:match and it shows which route will match the given URL. It’s useful to find out why some URL is not executing the controller action that you expect:

  1. $ php bin/console router:match /lucky/number/8
  2. [OK] Route "app_lucky_number" matches

Route Parameters

The previous examples defined routes where the URL never changes (e.g. /blog). However, it’s common to define routes where some parts are variable. For example, the URL to display some blog post will probably include the title or slug (e.g. /blog/my-first-post or /blog/all-about-symfony).

In Symfony routes, variable parts are wrapped in { ... } and they must have a unique name. For example, the route to display the blog post contents is defined as /blog/{slug}:

  • Annotations

    1. // src/Controller/BlogController.php
    2. namespace App\Controller;
    3. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    4. use Symfony\Component\HttpFoundation\Response;
    5. use Symfony\Component\Routing\Annotation\Route;
    6. class BlogController extends AbstractController
    7. {
    8. // ...
    9. /**
    10. * @Route("/blog/{slug}", name="blog_show")
    11. */
    12. public function show(string $slug): Response
    13. {
    14. // $slug will equal the dynamic part of the URL
    15. // e.g. at /blog/yay-routing, then $slug='yay-routing'
    16. // ...
    17. }
    18. }
  • Attributes

    1. // src/Controller/BlogController.php
    2. namespace App\Controller;
    3. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    4. use Symfony\Component\HttpFoundation\Response;
    5. use Symfony\Component\Routing\Annotation\Route;
    6. class BlogController extends AbstractController
    7. {
    8. // ...
    9. #[Route('/blog/{slug}', name: 'blog_show')]
    10. public function show(string $slug): Response
    11. {
    12. // $slug will equal the dynamic part of the URL
    13. // e.g. at /blog/yay-routing, then $slug='yay-routing'
    14. // ...
    15. }
    16. }
  • YAML

    1. # config/routes.yaml
    2. blog_show:
    3. path: /blog/{slug}
    4. controller: App\Controller\BlogController::show
  • XML

    1. <!-- config/routes.xml -->
    2. <?xml version="1.0" encoding="UTF-8" ?>
    3. <routes xmlns="http://symfony.com/schema/routing"
    4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    5. xsi:schemaLocation="http://symfony.com/schema/routing
    6. https://symfony.com/schema/routing/routing-1.0.xsd">
    7. <route id="blog_show" path="/blog/{slug}"
    8. controller="App\Controller\BlogController::show"/>
    9. </routes>
  • PHP

    1. // config/routes.php
    2. use App\Controller\BlogController;
    3. use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
    4. return function (RoutingConfigurator $routes) {
    5. $routes->add('blog_show', '/blog/{slug}')
    6. ->controller([BlogController::class, 'show'])
    7. ;
    8. };

The name of the variable part ({slug} in this example) is used to create a PHP variable where that route content is stored and passed to the controller. If a user visits the /blog/my-first-post URL, Symfony executes the show() method in theBlogControllerclass and passes a$slug = ‘my-first-post’argument to theshow() method.

Routes can define any number of parameters, but each of them can only be used once on each route (e.g. /blog/posts-about-{category}/page/{pageNumber}).

Parameters Validation

Imagine that your application has a blog_show route (URL: /blog/{slug}) and a blog_list route (URL: /blog/{page}). Given that route parameters accept any value, there’s no way to differentiate both routes.

If the user requests /blog/my-first-post, both routes will match and Symfony will use the route which was defined first. To fix this, add some validation to the {page} parameter using the requirements option:

  • Annotations

    1. // src/Controller/BlogController.php
    2. namespace App\Controller;
    3. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    4. use Symfony\Component\HttpFoundation\Response;
    5. use Symfony\Component\Routing\Annotation\Route;
    6. class BlogController extends AbstractController
    7. {
    8. /**
    9. * @Route("/blog/{page}", name="blog_list", requirements={"page"="\d+"})
    10. */
    11. public function list(int $page): Response
    12. {
    13. // ...
    14. }
    15. /**
    16. * @Route("/blog/{slug}", name="blog_show")
    17. */
    18. public function show(string $slug): Response
    19. {
    20. // ...
    21. }
    22. }
  • Attributes

    1. // src/Controller/BlogController.php
    2. namespace App\Controller;
    3. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    4. use Symfony\Component\HttpFoundation\Response;
    5. use Symfony\Component\Routing\Annotation\Route;
    6. class BlogController extends AbstractController
    7. {
    8. #[Route('/blog/{page}', name: 'blog_list', requirements: ['page' => '\d+'])]
    9. public function list(int $page): Response
    10. {
    11. // ...
    12. }
    13. #[Route('/blog/{slug}', name: 'blog_show')]
    14. public function show($slug): Response
    15. {
    16. // ...
    17. }
    18. }
  • YAML

    1. # config/routes.yaml
    2. blog_list:
    3. path: /blog/{page}
    4. controller: App\Controller\BlogController::list
    5. requirements:
    6. page: '\d+'
    7. blog_show:
    8. path: /blog/{slug}
    9. controller: App\Controller\BlogController::show
  • XML

    1. <!-- config/routes.xml -->
    2. <?xml version="1.0" encoding="UTF-8" ?>
    3. <routes xmlns="http://symfony.com/schema/routing"
    4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    5. xsi:schemaLocation="http://symfony.com/schema/routing
    6. https://symfony.com/schema/routing/routing-1.0.xsd">
    7. <route id="blog_list" path="/blog/{page}" controller="App\Controller\BlogController::list">
    8. <requirement key="page">\d+</requirement>
    9. </route>
    10. <route id="blog_show" path="/blog/{slug}"
    11. controller="App\Controller\BlogController::show"/>
    12. </routes>
  • PHP

    1. // config/routes.php
    2. use App\Controller\BlogController;
    3. use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
    4. return function (RoutingConfigurator $routes) {
    5. $routes->add('blog_list', '/blog/{page}')
    6. ->controller([BlogController::class, 'list'])
    7. ->requirements(['page' => '\d+'])
    8. ;
    9. $routes->add('blog_show', '/blog/{slug}')
    10. ->controller([BlogController::class, 'show'])
    11. ;
    12. // ...
    13. };

The requirements option defines the PHP regular expressions that route parameters must match for the entire route to match. In this example, \d+ is a regular expression that matches a digit of any length. Now:

URLRouteParameters
/blog/2blog_list$page = 2
/blog/my-first-postblog_show$slug = my-first-post

Tip

Route requirements (and route paths too) can include configuration parameters, which is useful to define complex regular expressions once and reuse them in multiple routes.

Tip

Parameters also support PCRE Unicode properties, which are escape sequences that match generic character types. For example, \p{Lu} matches any uppercase character in any language, \p{Greek} matches any Greek character, etc.

Note

When using regular expressions in route parameters, you can set the utf8 route option to true to make any . character match any UTF-8 characters instead of just a single byte.

If you prefer, requirements can be inlined in each parameter using the syntax {parameter_name<requirements>}. This feature makes configuration more concise, but it can decrease route readability when requirements are complex:

  • Annotations

    1. // src/Controller/BlogController.php
    2. namespace App\Controller;
    3. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    4. use Symfony\Component\HttpFoundation\Response;
    5. use Symfony\Component\Routing\Annotation\Route;
    6. class BlogController extends AbstractController
    7. {
    8. /**
    9. * @Route("/blog/{page<\d+>}", name="blog_list")
    10. */
    11. public function list(int $page): Response
    12. {
    13. // ...
    14. }
    15. }
  • Attributes

    1. // src/Controller/BlogController.php
    2. namespace App\Controller;
    3. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    4. use Symfony\Component\HttpFoundation\Response;
    5. use Symfony\Component\Routing\Annotation\Route;
    6. class BlogController extends AbstractController
    7. {
    8. #[Route('/blog/{page<\d+>}', name: 'blog_list')]
    9. public function list(int $page): Response
    10. {
    11. // ...
    12. }
    13. }
  • YAML

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

    1. <!-- config/routes.xml -->
    2. <?xml version="1.0" encoding="UTF-8" ?>
    3. <routes xmlns="http://symfony.com/schema/routing"
    4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    5. xsi:schemaLocation="http://symfony.com/schema/routing
    6. https://symfony.com/schema/routing/routing-1.0.xsd">
    7. <route id="blog_list" path="/blog/{page<\d+>}"
    8. controller="App\Controller\BlogController::list"/>
    9. <!-- ... -->
    10. </routes>
  • PHP

    1. // config/routes.php
    2. use App\Controller\BlogController;
    3. use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
    4. return function (RoutingConfigurator $routes) {
    5. $routes->add('blog_list', '/blog/{page<\d+>}')
    6. ->controller([BlogController::class, 'list'])
    7. ;
    8. // ...
    9. };

Optional Parameters

In the previous example, the URL of blog_list is /blog/{page}. If users visit /blog/1, it will match. But if they visit /blog, it will not match. As soon as you add a parameter to a route, it must have a value.

You can make blog_list once again match when the user visits /blog by adding a default value for the {page} parameter. When using annotations or attributes, default values are defined in the arguments of the controller action. In the other configuration formats they are defined with the defaults option:

  • Annotations

    1. // src/Controller/BlogController.php
    2. namespace App\Controller;
    3. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    4. use Symfony\Component\HttpFoundation\Response;
    5. use Symfony\Component\Routing\Annotation\Route;
    6. class BlogController extends AbstractController
    7. {
    8. /**
    9. * @Route("/blog/{page}", name="blog_list", requirements={"page"="\d+"})
    10. */
    11. public function list(int $page = 1): Response
    12. {
    13. // ...
    14. }
    15. }
  • Attributes

    1. // src/Controller/BlogController.php
    2. namespace App\Controller;
    3. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    4. use Symfony\Component\HttpFoundation\Response;
    5. use Symfony\Component\Routing\Annotation\Route;
    6. class BlogController extends AbstractController
    7. {
    8. #[Route('/blog/{page}', name: 'blog_list', requirements: ['page' => '\d+'])]
    9. public function list(int $page = 1): Response
    10. {
    11. // ...
    12. }
    13. }
  • YAML

    1. # config/routes.yaml
    2. blog_list:
    3. path: /blog/{page}
    4. controller: App\Controller\BlogController::list
    5. defaults:
    6. page: 1
    7. requirements:
    8. page: '\d+'
    9. blog_show:
    10. # ...
  • XML

    1. <!-- config/routes.xml -->
    2. <?xml version="1.0" encoding="UTF-8" ?>
    3. <routes xmlns="http://symfony.com/schema/routing"
    4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    5. xsi:schemaLocation="http://symfony.com/schema/routing
    6. https://symfony.com/schema/routing/routing-1.0.xsd">
    7. <route id="blog_list" path="/blog/{page}" controller="App\Controller\BlogController::list">
    8. <default key="page">1</default>
    9. <requirement key="page">\d+</requirement>
    10. </route>
    11. <!-- ... -->
    12. </routes>
  • PHP

    1. // config/routes.php
    2. use App\Controller\BlogController;
    3. use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
    4. return function (RoutingConfigurator $routes) {
    5. $routes->add('blog_list', '/blog/{page}')
    6. ->controller([BlogController::class, 'list'])
    7. ->defaults(['page' => 1])
    8. ->requirements(['page' => '\d+'])
    9. ;
    10. };

Now, when the user visits /blog, the blog_list route will match and $page will default to a value of 1.

Caution

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

If you want to always include some default value in the generated URL (for example to force the generation of /blog/1 instead of /blog in the previous example) add the ! character before the parameter name: /blog/{!page}

As it happens with requirements, default values can also be inlined in each parameter using the syntax {parameter_name?default_value}. This feature is compatible with inlined requirements, so you can inline both in a single parameter:

  • Annotations

    1. // src/Controller/BlogController.php
    2. namespace App\Controller;
    3. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    4. use Symfony\Component\HttpFoundation\Response;
    5. use Symfony\Component\Routing\Annotation\Route;
    6. class BlogController extends AbstractController
    7. {
    8. /**
    9. * @Route("/blog/{page<\d+>?1}", name="blog_list")
    10. */
    11. public function list(int $page): Response
    12. {
    13. // ...
    14. }
    15. }
  • Attributes

    1. // src/Controller/BlogController.php
    2. namespace App\Controller;
    3. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    4. use Symfony\Component\HttpFoundation\Response;
    5. use Symfony\Component\Routing\Annotation\Route;
    6. class BlogController extends AbstractController
    7. {
    8. #[Route('/blog/{page<\d+>?1}', name: 'blog_list')]
    9. public function list(int $page): Response
    10. {
    11. // ...
    12. }
    13. }
  • YAML

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

    1. <!-- config/routes.xml -->
    2. <?xml version="1.0" encoding="UTF-8" ?>
    3. <routes xmlns="http://symfony.com/schema/routing"
    4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    5. xsi:schemaLocation="http://symfony.com/schema/routing
    6. https://symfony.com/schema/routing/routing-1.0.xsd">
    7. <route id="blog_list" path="/blog/{page<\d+>?1}"
    8. controller="App\Controller\BlogController::list"/>
    9. <!-- ... -->
    10. </routes>
  • PHP

    1. // config/routes.php
    2. use App\Controller\BlogController;
    3. use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
    4. return function (RoutingConfigurator $routes) {
    5. $routes->add('blog_list', '/blog/{page<\d+>?1}')
    6. ->controller([BlogController::class, 'list'])
    7. ;
    8. };

Tip

To give a null default value to any parameter, add nothing after the ? character (e.g. /blog/{page?}).

Priority Parameter

New in version 5.1: The priority parameter was introduced in Symfony 5.1

Symfony evaluates routes in the order they are defined. If the path of a route matches many different patterns, it might prevent other routes from being matched. In YAML and XML you can move the route definitions up or down in the configuration file to control their priority. In routes defined as PHP annotations or attributes this is much harder to do, so you can set the optional priority parameter in those routes to control their priority:

  • Annotations

    1. // src/Controller/BlogController.php
    2. namespace App\Controller;
    3. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    4. use Symfony\Component\Routing\Annotation\Route;
    5. class BlogController extends AbstractController
    6. {
    7. /**
    8. * This route has a greedy pattern and is defined first.
    9. *
    10. * @Route("/blog/{slug}", name="blog_show")
    11. */
    12. public function show(string $slug)
    13. {
    14. // ...
    15. }
    16. /**
    17. * This route could not be matched without defining a higher priority than 0.
    18. *
    19. * @Route("/blog/list", name="blog_list", priority=2)
    20. */
    21. public function list()
    22. {
    23. // ...
    24. }
    25. }
  • Attributes

    1. // src/Controller/BlogController.php
    2. namespace App\Controller;
    3. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    4. use Symfony\Component\Routing\Annotation\Route;
    5. class BlogController extends AbstractController
    6. {
    7. /**
    8. * This route has a greedy pattern and is defined first.
    9. */
    10. #[Route('/blog/{slug}', name: 'blog_show')]
    11. public function show(string $slug)
    12. {
    13. // ...
    14. }
    15. /**
    16. * This route could not be matched without defining a higher priority than 0.
    17. */
    18. #[Route('/blog/list', name: 'blog_list', priority: 2)]
    19. public function list()
    20. {
    21. // ...
    22. }
    23. }

The priority parameter expects an integer value. Routes with higher priority are sorted before routes with lower priority. The default value when it is not defined is 0.

Parameter Conversion

A common routing need is to convert the value stored in some parameter (e.g. an integer acting as the user ID) into another value (e.g. the object that represents the user). This feature is called “param converter” and is only available when using annotations to define routes.

To add support for “param converters” we need SensioFrameworkExtraBundle:

  1. $ composer require sensio/framework-extra-bundle

Now, keep the previous route configuration, but change the arguments of the controller action. Instead of string $slug, add BlogPost $post:

  1. // src/Controller/BlogController.php
  2. namespace App\Controller;
  3. use App\Entity\BlogPost;
  4. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  5. use Symfony\Component\HttpFoundation\Response;
  6. use Symfony\Component\Routing\Annotation\Route;
  7. class BlogController extends AbstractController
  8. {
  9. // ...
  10. /**
  11. * @Route("/blog/{slug}", name="blog_show")
  12. */
  13. public function show(BlogPost $post): Response
  14. {
  15. // $post is the object whose slug matches the routing parameter
  16. // ...
  17. }
  18. }

If your controller arguments include type-hints for objects (BlogPost in this case), the “param converter” makes a database request to find the object using the request parameters (slug in this case). If no object is found, Symfony generates a 404 response automatically.

Read the full param converter documentation to learn about the converters provided by Symfony and how to configure them.

Special Parameters

In addition to your own parameters, routes can include any of the following special parameters created by Symfony:

_controller

This parameter is used to determine which controller and action is executed when the route is matched.

_format

The matched value is used to set the “request format” of the Request object. This is used for such things as setting the Content-Type of the response (e.g. a json format translates into a Content-Type of application/json).

_fragment

Used to set the fragment identifier, which is the optional last part of a URL that starts with a # character and is used to identify a portion of a document.

_locale

Used to set the locale on the request.

You can include these attributes (except _fragment) both in individual routes and in route imports. Symfony defines some special attributes with the same name (except for the leading underscore) so you can define them easier:

  • Annotations

    1. // src/Controller/ArticleController.php
    2. namespace App\Controller;
    3. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    4. use Symfony\Component\HttpFoundation\Response;
    5. use Symfony\Component\Routing\Annotation\Route;
    6. class ArticleController extends AbstractController
    7. {
    8. /**
    9. * @Route(
    10. * "/articles/{_locale}/search.{_format}",
    11. * locale="en",
    12. * format="html",
    13. * requirements={
    14. * "_locale": "en|fr",
    15. * "_format": "html|xml",
    16. * }
    17. * )
    18. */
    19. public function search(): Response
    20. {
    21. }
    22. }
  • Attributes

    1. // src/Controller/ArticleController.php
    2. namespace App\Controller;
    3. // ...
    4. class ArticleController extends AbstractController
    5. {
    6. #[Route(
    7. path: '/articles/{_locale}/search.{_format}',
    8. locale: 'en',
    9. format: 'html',
    10. requirements: [
    11. '_locale' => 'en|fr',
    12. '_format' => 'html|xml',
    13. ],
    14. )]
    15. public function search(): Response
    16. {
    17. }
    18. }
  • YAML

    1. # config/routes.yaml
    2. article_search:
    3. path: /articles/{_locale}/search.{_format}
    4. controller: App\Controller\ArticleController::search
    5. locale: en
    6. format: html
    7. requirements:
    8. _locale: en|fr
    9. _format: html|xml
  • XML

    1. <!-- config/routes.xml -->
    2. <?xml version="1.0" encoding="UTF-8" ?>
    3. <routes xmlns="http://symfony.com/schema/routing"
    4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    5. xsi:schemaLocation="http://symfony.com/schema/routing
    6. https://symfony.com/schema/routing/routing-1.0.xsd">
    7. <route id="article_search"
    8. path="/articles/{_locale}/search.{_format}"
    9. controller="App\Controller\ArticleController::search"
    10. locale="en"
    11. format="html">
    12. <requirement key="_locale">en|fr</requirement>
    13. <requirement key="_format">html|xml</requirement>
    14. </route>
    15. </routes>
  • PHP

    1. // config/routes.php
    2. namespace Symfony\Component\Routing\Loader\Configurator;
    3. use App\Controller\ArticleController;
    4. return function (RoutingConfigurator $routes) {
    5. $routes->add('article_show', '/articles/{_locale}/search.{_format}')
    6. ->controller([ArticleController::class, 'search'])
    7. ->locale('en')
    8. ->format('html')
    9. ->requirements([
    10. '_locale' => 'en|fr',
    11. '_format' => 'html|xml',
    12. ])
    13. ;
    14. };

Extra Parameters

In the defaults option of a route you can optionally define parameters not included in the route configuration. This is useful to pass extra arguments to the controllers of the routes:

  • Annotations

    1. // src/Controller/BlogController.php
    2. namespace App\Controller;
    3. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    4. use Symfony\Component\HttpFoundation\Response;
    5. use Symfony\Component\Routing\Annotation\Route;
    6. class BlogController extends AbstractController
    7. {
    8. /**
    9. * @Route("/blog/{page}", name="blog_index", defaults={"page": 1, "title": "Hello world!"})
    10. */
    11. public function index(int $page, string $title): Response
    12. {
    13. // ...
    14. }
    15. }
  • Attributes

    1. // src/Controller/BlogController.php
    2. namespace App\Controller;
    3. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    4. use Symfony\Component\HttpFoundation\Response;
    5. use Symfony\Component\Routing\Annotation\Route;
    6. class BlogController extends AbstractController
    7. {
    8. #[Route('/blog/{page}', name: 'blog_index', defaults: ['page' => 1, 'title' => 'Hello world!'])]
    9. public function index(int $page, string $title): Response
    10. {
    11. // ...
    12. }
    13. }
  • YAML

    1. # config/routes.yaml
    2. blog_index:
    3. path: /blog/{page}
    4. controller: App\Controller\BlogController::index
    5. defaults:
    6. page: 1
    7. title: "Hello world!"
  • XML

    1. <!-- config/routes.xml -->
    2. <?xml version="1.0" encoding="UTF-8" ?>
    3. <routes xmlns="http://symfony.com/schema/routing"
    4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    5. xsi:schemaLocation="http://symfony.com/schema/routing
    6. https://symfony.com/schema/routing/routing-1.0.xsd">
    7. <route id="blog_index" path="/blog/{page}" controller="App\Controller\BlogController::index">
    8. <default key="page">1</default>
    9. <default key="title">Hello world!</default>
    10. </route>
    11. </routes>
  • PHP

    1. // config/routes.php
    2. use App\Controller\BlogController;
    3. use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
    4. return function (RoutingConfigurator $routes) {
    5. $routes->add('blog_index', '/blog/{page}')
    6. ->controller([BlogController::class, 'index'])
    7. ->defaults([
    8. 'page' => 1,
    9. 'title' => 'Hello world!',
    10. ])
    11. ;
    12. };

Slash Characters in Route Parameters

Route parameters can contain any values except the / slash character, because that’s the character used to separate the different parts of the URLs. For example, if the token value in the /share/{token} route contains a / character, this route won’t match.

A possible solution is to change the parameter requirements to be more permissive:

  • Annotations

    1. // src/Controller/DefaultController.php
    2. namespace App\Controller;
    3. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    4. use Symfony\Component\HttpFoundation\Response;
    5. use Symfony\Component\Routing\Annotation\Route;
    6. class DefaultController extends AbstractController
    7. {
    8. /**
    9. * @Route("/share/{token}", name="share", requirements={"token"=".+"})
    10. */
    11. public function share($token): Response
    12. {
    13. // ...
    14. }
    15. }
  • Attributes

    1. // src/Controller/DefaultController.php
    2. namespace App\Controller;
    3. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    4. use Symfony\Component\HttpFoundation\Response;
    5. use Symfony\Component\Routing\Annotation\Route;
    6. class DefaultController extends AbstractController
    7. {
    8. #[Route('/share/{token}', name: 'share', requirements: ['token' => '.+'])]
    9. public function share($token): Response
    10. {
    11. // ...
    12. }
    13. }
  • YAML

    1. # config/routes.yaml
    2. share:
    3. path: /share/{token}
    4. controller: App\Controller\DefaultController::share
    5. requirements:
    6. token: .+
  • XML

    1. <!-- config/routes.xml -->
    2. <?xml version="1.0" encoding="UTF-8" ?>
    3. <routes xmlns="http://symfony.com/schema/routing"
    4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    5. xsi:schemaLocation="http://symfony.com/schema/routing
    6. https://symfony.com/schema/routing/routing-1.0.xsd">
    7. <route id="share" path="/share/{token}" controller="App\Controller\DefaultController::share">
    8. <requirement key="token">.+</requirement>
    9. </route>
    10. </routes>
  • PHP

    1. // config/routes.php
    2. use App\Controller\DefaultController;
    3. use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
    4. return function (RoutingConfigurator $routes) {
    5. $routes->add('share', '/share/{token}')
    6. ->controller([DefaultController::class, 'share'])
    7. ->requirements([
    8. 'token' => '.+',
    9. ])
    10. ;
    11. };

Note

If the route defines several parameters and you apply this permissive regular expression to all of them, you might get unexpected results. For example, if the route definition is /share/{path}/{token} and both path and token accept /, then token will only get the last part and the rest is matched by path.

Note

If the route includes the special {_format} parameter, you shouldn’t use the .+ requirement for the parameters that allow slashes. For example, if the pattern is /share/{token}.{_format} and {token} allows any character, the /share/foo/bar.json URL will consider foo/bar.json as the token and the format will be empty. This can be solved by replacing the .+ requirement by [^.]+` to allow any character except dots.

Route Groups and Prefixes

It’s common for a group of routes to share some options (e.g. all routes related to the blog start with /blog) That’s why Symfony includes a feature to share route configuration.

When defining routes as attributes or annotations, put the common configuration in the #[Route] attribute (or @Route annotation) of the controller class. In other routing formats, define the common configuration using options when importing the routes.

  • Annotations

    1. // src/Controller/BlogController.php
    2. namespace App\Controller;
    3. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    4. use Symfony\Component\HttpFoundation\Response;
    5. use Symfony\Component\Routing\Annotation\Route;
    6. /**
    7. * @Route("/blog", requirements={"_locale": "en|es|fr"}, name="blog_")
    8. */
    9. class BlogController extends AbstractController
    10. {
    11. /**
    12. * @Route("/{_locale}", name="index")
    13. */
    14. public function index(): Response
    15. {
    16. // ...
    17. }
    18. /**
    19. * @Route("/{_locale}/posts/{slug}", name="show")
    20. */
    21. public function show(Post $post): Response
    22. {
    23. // ...
    24. }
    25. }
  • Attributes

    1. // src/Controller/BlogController.php
    2. namespace App\Controller;
    3. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    4. use Symfony\Component\HttpFoundation\Response;
    5. use Symfony\Component\Routing\Annotation\Route;
    6. #[Route('/blog', requirements: ['_locale' => 'en|es|fr'], name: 'blog_')]
    7. class BlogController extends AbstractController
    8. {
    9. #[Route('/{_locale}', name: 'index')]
    10. public function index(): Response
    11. {
    12. // ...
    13. }
    14. #[Route('/{_locale}/posts/{slug}', name: 'show')]
    15. public function show(Post $post): Response
    16. {
    17. // ...
    18. }
    19. }
  • YAML

    1. # config/routes/annotations.yaml
    2. controllers:
    3. resource: '../../src/Controller/'
    4. type: annotation
    5. # this is added to the beginning of all imported route URLs
    6. prefix: '/blog'
    7. # this is added to the beginning of all imported route names
    8. name_prefix: 'blog_'
    9. # these requirements are added to all imported routes
    10. requirements:
    11. _locale: 'en|es|fr'
    12. # An imported route with an empty URL will become "/blog/"
    13. # Uncomment this option to make that URL "/blog" instead
    14. # trailing_slash_on_root: false
    15. # you can optionally exclude some files/subdirectories when loading annotations
    16. # exclude: '../../src/Controller/{DebugEmailController}.php'
  • XML

    1. <!-- config/routes/annotations.xml -->
    2. <?xml version="1.0" encoding="UTF-8" ?>
    3. <routes xmlns="http://symfony.com/schema/routing"
    4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    5. xsi:schemaLocation="http://symfony.com/schema/routing
    6. https://symfony.com/schema/routing/routing-1.0.xsd">
    7. <!--
    8. the 'prefix' value is added to the beginning of all imported route URLs
    9. the 'name-prefix' value is added to the beginning of all imported route names
    10. the 'exclude' option defines the files or subdirectories ignored when loading annotations
    11. -->
    12. <import resource="../../src/Controller/"
    13. type="annotation"
    14. prefix="/blog"
    15. name-prefix="blog_"
    16. exclude="../../src/Controller/{DebugEmailController}.php">
    17. <!-- these requirements are added to all imported routes -->
    18. <requirement key="_locale">en|es|fr</requirement>
    19. </import>
    20. <!-- An imported route with an empty URL will become "/blog/"
    21. Uncomment this option to make that URL "/blog" instead -->
    22. <import resource="../../src/Controller/" type="annotation"
    23. prefix="/blog"
    24. trailing-slash-on-root="false">
    25. <!-- ... -->
    26. </import>
    27. </routes>
  • PHP

    1. // config/routes/annotations.php
    2. use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
    3. return function (RoutingConfigurator $routes) {
    4. // use the optional fourth argument of import() to exclude some files
    5. // or subdirectories when loading annotations
    6. $routes->import('../../src/Controller/', 'annotation')
    7. // this is added to the beginning of all imported route URLs
    8. ->prefix('/blog')
    9. // An imported route with an empty URL will become "/blog/"
    10. // Pass FALSE as the second argument to make that URL "/blog" instead
    11. // ->prefix('/blog', false)
    12. // this is added to the beginning of all imported route names
    13. ->namePrefix('blog_')
    14. // these requirements are added to all imported routes
    15. ->requirements(['_locale' => 'en|es|fr'])
    16. // you can optionally exclude some files/subdirectories when loading annotations
    17. ->exclude('../../src/Controller/{DebugEmailController}.php')
    18. ;
    19. };

In this example, the route of the index() action will be calledblog_indexand its URL will be/blog/{_locale}. The route of theshow() action will be called blog_show and its URL will be /blog/{_locale}/posts/{slug}. Both routes will also validate that the _locale parameter matches the regular expression defined in the class annotation.

See also

Symfony can import routes from different sources and you can even create your own route loader.

Getting the Route Name and Parameters

The Request object created by Symfony stores all the route configuration (such as the name and parameters) in the “request attributes”. You can get this information in a controller via the Request object:

  1. // src/Controller/BlogController.php
  2. namespace App\Controller;
  3. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  4. use Symfony\Component\HttpFoundation\Request;
  5. use Symfony\Component\HttpFoundation\Response;
  6. use Symfony\Component\Routing\Annotation\Route;
  7. class BlogController extends AbstractController
  8. {
  9. /**
  10. * @Route("/blog", name="blog_list")
  11. */
  12. public function list(Request $request): Response
  13. {
  14. $routeName = $request->attributes->get('_route');
  15. $routeParameters = $request->attributes->get('_route_params');
  16. // use this to get all the available attributes (not only routing ones):
  17. $allAttributes = $request->attributes->all();
  18. // ...
  19. }
  20. }

You can get this information in services too injecting the request_stack service to get the Request object in a service. In templates, use the Twig global app variable to get the request and its attributes:

  1. {% set route_name = app.request.attributes.get('_route') %}
  2. {% set route_parameters = app.request.attributes.get('_route_params') %}
  3. {# use this to get all the available attributes (not only routing ones) #}
  4. {% set all_attributes = app.request.attributes.all %}

Special Routes

Symfony defines some special controllers to render templates and redirect to other routes from the route configuration so you don’t have to create a controller action.

Rendering a Template Directly from a Route

Read the section about rendering a template from a route in the main article about Symfony templates.

Redirecting to URLs and Routes Directly from a Route

Use the RedirectController to redirect to other routes and URLs:

  • YAML

    1. # config/routes.yaml
    2. doc_shortcut:
    3. path: /doc
    4. controller: Symfony\Bundle\FrameworkBundle\Controller\RedirectController
    5. defaults:
    6. route: 'doc_page'
    7. # optionally you can define some arguments passed to the route
    8. page: 'index'
    9. version: 'current'
    10. # redirections are temporary by default (code 302) but you can make them permanent (code 301)
    11. permanent: true
    12. # add this to keep the original query string parameters when redirecting
    13. keepQueryParams: true
    14. # add this to keep the HTTP method when redirecting. The redirect status changes
    15. # * for temporary redirects, it uses the 307 status code instead of 302
    16. # * for permanent redirects, it uses the 308 status code instead of 301
    17. keepRequestMethod: true
    18. legacy_doc:
    19. path: /legacy/doc
    20. controller: Symfony\Bundle\FrameworkBundle\Controller\RedirectController
    21. defaults:
    22. # this value can be an absolute path or an absolute URL
    23. path: 'https://legacy.example.com/doc'
    24. permanent: true
  • XML

    1. <!-- config/routes.xml -->
    2. <?xml version="1.0" encoding="UTF-8" ?>
    3. <routes xmlns="http://symfony.com/schema/routing"
    4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    5. xsi:schemaLocation="http://symfony.com/schema/routing
    6. https://symfony.com/schema/routing/routing-1.0.xsd">
    7. <route id="doc_shortcut" path="/doc"
    8. controller="Symfony\Bundle\FrameworkBundle\Controller\RedirectController">
    9. <default key="route">doc_page</default>
    10. <!-- optionally you can define some arguments passed to the route -->
    11. <default key="page">index</default>
    12. <default key="version">current</default>
    13. <!-- redirections are temporary by default (code 302) but you can make them permanent (code 301)-->
    14. <default key="permanent">true</default>
    15. <!-- add this to keep the original query string parameters when redirecting -->
    16. <default key="keepQueryParams">true</default>
    17. <!-- add this to keep the HTTP method when redirecting. The redirect status changes:
    18. * for temporary redirects, it uses the 307 status code instead of 302
    19. * for permanent redirects, it uses the 308 status code instead of 301 -->
    20. <default key="keepRequestMethod">true</default>
    21. </route>
    22. <route id="legacy_doc" path="/legacy/doc"
    23. controller="Symfony\Bundle\FrameworkBundle\Controller\RedirectController">
    24. <!-- this value can be an absolute path or an absolute URL -->
    25. <default key="path">https://legacy.example.com/doc</default>
    26. <!-- redirections are temporary by default (code 302) but you can make them permanent (code 301)-->
    27. <default key="permanent">true</default>
    28. </route>
    29. </routes>
  • PHP

    1. // config/routes.php
    2. use App\Controller\DefaultController;
    3. use Symfony\Bundle\FrameworkBundle\Controller\RedirectController;
    4. use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
    5. return function (RoutingConfigurator $routes) {
    6. $routes->add('doc_shortcut', '/doc')
    7. ->controller(RedirectController::class)
    8. ->defaults([
    9. 'route' => 'doc_page',
    10. // optionally you can define some arguments passed to the route
    11. 'page' => 'index',
    12. 'version' => 'current',
    13. // redirections are temporary by default (code 302) but you can make them permanent (code 301)
    14. 'permanent' => true,
    15. // add this to keep the original query string parameters when redirecting
    16. 'keepQueryParams' => true,
    17. // add this to keep the HTTP method when redirecting. The redirect status changes:
    18. // * for temporary redirects, it uses the 307 status code instead of 302
    19. // * for permanent redirects, it uses the 308 status code instead of 301
    20. 'keepRequestMethod' => true,
    21. ])
    22. ;
    23. $routes->add('legacy_doc', '/legacy/doc')
    24. ->controller(RedirectController::class)
    25. ->defaults([
    26. // this value can be an absolute path or an absolute URL
    27. 'path' => 'https://legacy.example.com/doc',
    28. // redirections are temporary by default (code 302) but you can make them permanent (code 301)
    29. 'permanent' => true,
    30. ])
    31. ;
    32. };

Tip

Symfony also provides some utilities to redirect inside controllers

Redirecting URLs with Trailing Slashes

Historically, URLs have followed the UNIX convention of adding trailing slashes for directories (e.g. https://example.com/foo/) and removing them to refer to files (https://example.com/foo). Although serving different contents for both URLs is OK, nowadays it’s common to treat both URLs as the same URL and redirect between them.

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

Route URLIf the requested URL is /fooIf the requested URL is /foo/
/fooIt matches (200 status response)It makes a 301 redirect to /foo
/foo/It makes a 301 redirect to /foo/It matches (200 status response)

Sub-Domain Routing

Routes can configure a host option to require that the HTTP host of the incoming requests matches some specific value. In the following example, both routes match the same path (/) but one of them only responds to a specific host name:

  • Annotations

    1. // src/Controller/MainController.php
    2. namespace App\Controller;
    3. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    4. use Symfony\Component\HttpFoundation\Response;
    5. use Symfony\Component\Routing\Annotation\Route;
    6. class MainController extends AbstractController
    7. {
    8. /**
    9. * @Route("/", name="mobile_homepage", host="m.example.com")
    10. */
    11. public function mobileHomepage(): Response
    12. {
    13. // ...
    14. }
    15. /**
    16. * @Route("/", name="homepage")
    17. */
    18. public function homepage(): Response
    19. {
    20. // ...
    21. }
    22. }
  • Attributes

    1. // src/Controller/MainController.php
    2. namespace App\Controller;
    3. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    4. use Symfony\Component\HttpFoundation\Response;
    5. use Symfony\Component\Routing\Annotation\Route;
    6. class MainController extends AbstractController
    7. {
    8. #[Route('/', name: 'mobile_homepage', host: 'm.example.com')]
    9. public function mobileHomepage(): Response
    10. {
    11. // ...
    12. }
    13. #[Route('/', name: 'homepage')]
    14. public function homepage(): Response
    15. {
    16. // ...
    17. }
    18. }
  • YAML

    1. # config/routes.yaml
    2. mobile_homepage:
    3. path: /
    4. host: m.example.com
    5. controller: App\Controller\MainController::mobileHomepage
    6. homepage:
    7. path: /
    8. controller: App\Controller\MainController::homepage
  • XML

    1. <!-- config/routes.xml -->
    2. <?xml version="1.0" encoding="UTF-8" ?>
    3. <routes xmlns="http://symfony.com/schema/routing"
    4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    5. xsi:schemaLocation="http://symfony.com/schema/routing
    6. https://symfony.com/schema/routing/routing-1.0.xsd">
    7. <route id="mobile_homepage"
    8. path="/"
    9. host="m.example.com"
    10. controller="App\Controller\MainController::mobileHomepage"/>
    11. <route id="homepage" path="/" controller="App\Controller\MainController::homepage"/>
    12. </routes>
  • PHP

    1. // config/routes.php
    2. use App\Controller\MainController;
    3. use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
    4. return function (RoutingConfigurator $routes) {
    5. $routes->add('mobile_homepage', '/')
    6. ->controller([MainController::class, 'mobileHomepage'])
    7. ->host('m.example.com')
    8. ;
    9. $routes->add('homepage', '/')
    10. ->controller([MainController::class, 'homepage'])
    11. ;
    12. };

The value of the host option can include parameters (which is useful in multi-tenant applications) and these parameters can be validated too with requirements:

  • Annotations

    1. // src/Controller/MainController.php
    2. namespace App\Controller;
    3. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    4. use Symfony\Component\HttpFoundation\Response;
    5. use Symfony\Component\Routing\Annotation\Route;
    6. class MainController extends AbstractController
    7. {
    8. /**
    9. * @Route(
    10. * "/",
    11. * name="mobile_homepage",
    12. * host="{subdomain}.example.com",
    13. * defaults={"subdomain"="m"},
    14. * requirements={"subdomain"="m|mobile"}
    15. * )
    16. */
    17. public function mobileHomepage(): Response
    18. {
    19. // ...
    20. }
    21. /**
    22. * @Route("/", name="homepage")
    23. */
    24. public function homepage(): Response
    25. {
    26. // ...
    27. }
    28. }
  • Attributes

    1. // src/Controller/MainController.php
    2. namespace App\Controller;
    3. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    4. use Symfony\Component\HttpFoundation\Response;
    5. use Symfony\Component\Routing\Annotation\Route;
    6. class MainController extends AbstractController
    7. {
    8. #[Route(
    9. '/',
    10. name: 'mobile_homepage',
    11. host: '{subdomain}.example.com',
    12. defaults: ['subdomain' => 'm'],
    13. requirements: ['subdomain' => 'm|mobile'],
    14. )]
    15. public function mobileHomepage(): Response
    16. {
    17. // ...
    18. }
    19. #[Route('/', name: 'homepage')]
    20. public function homepage(): Response
    21. {
    22. // ...
    23. }
    24. }
  • YAML

    1. # config/routes.yaml
    2. mobile_homepage:
    3. path: /
    4. host: "{subdomain}.example.com"
    5. controller: App\Controller\MainController::mobileHomepage
    6. defaults:
    7. subdomain: m
    8. requirements:
    9. subdomain: m|mobile
    10. homepage:
    11. path: /
    12. controller: App\Controller\MainController::homepage
  • XML

    1. <!-- config/routes.xml -->
    2. <?xml version="1.0" encoding="UTF-8" ?>
    3. <routes xmlns="http://symfony.com/schema/routing"
    4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    5. xsi:schemaLocation="http://symfony.com/schema/routing
    6. https://symfony.com/schema/routing/routing-1.0.xsd">
    7. <route id="mobile_homepage"
    8. path="/"
    9. host="{subdomain}.example.com"
    10. controller="App\Controller\MainController::mobileHomepage">
    11. <default key="subdomain">m</default>
    12. <requirement key="subdomain">m|mobile</requirement>
    13. </route>
    14. <route id="homepage" path="/" controller="App\Controller\MainController::homepage"/>
    15. </routes>
  • PHP

    1. // config/routes.php
    2. use App\Controller\MainController;
    3. use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
    4. return function (RoutingConfigurator $routes) {
    5. $routes->add('mobile_homepage', '/')
    6. ->controller([MainController::class, 'mobileHomepage'])
    7. ->host('{subdomain}.example.com')
    8. ->defaults([
    9. 'subdomain' => 'm',
    10. ])
    11. ->requirements([
    12. 'subdomain' => 'm|mobile',
    13. ])
    14. ;
    15. $routes->add('homepage', '/')
    16. ->controller([MainController::class, 'homepage'])
    17. ;
    18. };

In the above example, the subdomain parameter defines a default value because otherwise you need to include a subdomain value each time you generate a URL using these routes.

Tip

You can also set the host option when importing routes to make all of them require that host name.

Note

When using sub-domain routing, you must set the Host HTTP headers in functional tests or routes won’t match:

  1. $crawler = $client->request(
  2. 'GET',
  3. '/',
  4. [],
  5. [],
  6. ['HTTP_HOST' => 'm.example.com']
  7. // or get the value from some configuration parameter:
  8. // ['HTTP_HOST' => 'm.'.$client->getContainer()->getParameter('domain')]
  9. );

Tip

You can also use the inline defaults and requirements format in the host option: {subdomain<m|mobile>?m}.example.com

New in version 5.2: Inline parameter default values support in hosts were introduced in Symfony 5.2. Prior to Symfony 5.2, they were supported in the path only.

Localized Routes (i18n)

If your application is translated into multiple languages, each route can define a different URL per each translation locale. This avoids the need for duplicating routes, which also reduces the potential bugs:

  • Annotations

    1. // src/Controller/CompanyController.php
    2. namespace App\Controller;
    3. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    4. use Symfony\Component\HttpFoundation\Response;
    5. use Symfony\Component\Routing\Annotation\Route;
    6. class CompanyController extends AbstractController
    7. {
    8. /**
    9. * @Route({
    10. * "en": "/about-us",
    11. * "nl": "/over-ons"
    12. * }, name="about_us")
    13. */
    14. public function about(): Response
    15. {
    16. // ...
    17. }
    18. }
  • Attributes

    1. // src/Controller/CompanyController.php
    2. namespace App\Controller;
    3. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    4. use Symfony\Component\HttpFoundation\Response;
    5. use Symfony\Component\Routing\Annotation\Route;
    6. class CompanyController extends AbstractController
    7. {
    8. #[Route(path: [
    9. 'en' => '/about-us',
    10. 'nl' => '/over-ons'
    11. ], name: 'about_us')]
    12. public function about(): Response
    13. {
    14. // ...
    15. }
    16. }
  • YAML

    1. # config/routes.yaml
    2. about_us:
    3. path:
    4. en: /about-us
    5. nl: /over-ons
    6. controller: App\Controller\CompanyController::about
  • XML

    1. <!-- config/routes.xml -->
    2. <?xml version="1.0" encoding="UTF-8" ?>
    3. <routes xmlns="http://symfony.com/schema/routing"
    4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    5. xsi:schemaLocation="http://symfony.com/schema/routing
    6. https://symfony.com/schema/routing/routing-1.0.xsd">
    7. <route id="about_us" controller="App\Controller\CompanyController::about">
    8. <path locale="en">/about-us</path>
    9. <path locale="nl">/over-ons</path>
    10. </route>
    11. </routes>
  • PHP

    1. // config/routes.php
    2. use App\Controller\CompanyController;
    3. use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
    4. return function (RoutingConfigurator $routes) {
    5. $routes->add('about_us', [
    6. 'en' => '/about-us',
    7. 'nl' => '/over-ons',
    8. ])
    9. ->controller([CompanyController::class, 'about'])
    10. ;
    11. };

Note

When using PHP attributes for localized routes, you have to use the path named parameter to specify the array of paths.

When a localized route is matched, Symfony uses the same locale automatically during the entire request.

Tip

When the application uses full “language + territory” locales (e.g. fr_FR, fr_BE), if the URLs are the same in all related locales, routes can use only the language part (e.g. fr) to avoid repeating the same URLs.

A common requirement for internationalized applications is to prefix all routes with a locale. This can be done by defining a different prefix for each locale (and setting an empty prefix for your default locale if you prefer it):

  • YAML

    1. # config/routes/annotations.yaml
    2. controllers:
    3. resource: '../../src/Controller/'
    4. type: annotation
    5. prefix:
    6. en: '' # don't prefix URLs for English, the default locale
    7. nl: '/nl'
  • XML

    1. <!-- config/routes/annotations.xml -->
    2. <?xml version="1.0" encoding="UTF-8" ?>
    3. <routes xmlns="http://symfony.com/schema/routing"
    4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    5. xsi:schemaLocation="http://symfony.com/schema/routing
    6. https://symfony.com/schema/routing/routing-1.0.xsd">
    7. <import resource="../../src/Controller/" type="annotation">
    8. <!-- don't prefix URLs for English, the default locale -->
    9. <prefix locale="en"></prefix>
    10. <prefix locale="nl">/nl</prefix>
    11. </import>
    12. </routes>
  • PHP

    1. // config/routes/annotations.php
    2. use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
    3. return function (RoutingConfigurator $routes) {
    4. $routes->import('../../src/Controller/', 'annotation')
    5. ->prefix([
    6. // don't prefix URLs for English, the default locale
    7. 'en' => '',
    8. 'nl' => '/nl',
    9. ])
    10. ;
    11. };

Another common requirement is to host the website on a different domain according to the locale. This can be done by defining a different host for each locale.

New in version 5.1: The ability to define an array of hosts was introduced in Symfony 5.1.

  • YAML

    1. # config/routes/annotations.yaml
    2. controllers:
    3. resource: '../../src/Controller/'
    4. type: annotation
    5. host:
    6. en: 'https://www.example.com'
    7. nl: 'https://www.example.nl'
  • XML

    1. <!-- config/routes/annotations.xml -->
    2. <?xml version="1.0" encoding="UTF-8" ?>
    3. <routes xmlns="http://symfony.com/schema/routing"
    4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    5. xsi:schemaLocation="http://symfony.com/schema/routing
    6. https://symfony.com/schema/routing/routing-1.0.xsd">
    7. <import resource="../../src/Controller/" type="annotation">
    8. <host locale="en">https://www.example.com</host>
    9. <host locale="nl">https://www.example.nl</host>
    10. </import>
    11. </routes>
  • PHP

    1. // config/routes/annotations.php
    2. use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
    3. return function (RoutingConfigurator $routes) {
    4. $routes->import('../../src/Controller/', 'annotation')
    5. ->host([
    6. 'en' => 'https://www.example.com',
    7. 'nl' => 'https://www.example.nl',
    8. ])
    9. ;
    10. };

Stateless Routes

New in version 5.1: The stateless option was introduced in Symfony 5.1.

Sometimes, when an HTTP response should be cached, it is important to ensure that can happen. However, whenever a session is started during a request, Symfony turns the response into a private non-cacheable response.

For details, see HTTP Cache.

Routes can configure a stateless boolean option in order to declare that the session shouldn’t be used when matching a request:

  • Annotations

    1. // src/Controller/MainController.php
    2. namespace App\Controller;
    3. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    4. use Symfony\Component\Routing\Annotation\Route;
    5. class MainController extends AbstractController
    6. {
    7. /**
    8. * @Route("/", name="homepage", stateless=true)
    9. */
    10. public function homepage()
    11. {
    12. // ...
    13. }
    14. }
  • Attributes

    1. // src/Controller/MainController.php
    2. namespace App\Controller;
    3. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    4. use Symfony\Component\Routing\Annotation\Route;
    5. class MainController extends AbstractController
    6. {
    7. #[Route('/', name: 'homepage', stateless: true)]
    8. public function homepage()
    9. {
    10. // ...
    11. }
    12. }
  • YAML

    1. # config/routes.yaml
    2. homepage:
    3. controller: App\Controller\MainController::homepage
    4. path: /
    5. stateless: true
  • XML

    1. <!-- config/routes.xml -->
    2. <?xml version="1.0" encoding="UTF-8" ?>
    3. <routes xmlns="http://symfony.com/schema/routing"
    4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    5. xsi:schemaLocation="http://symfony.com/schema/routing
    6. https://symfony.com/schema/routing/routing-1.0.xsd">
    7. <route id="homepage" controller="App\Controller\MainController::homepage" path="/" stateless="true"/>
    8. </routes>
  • PHP

    1. // config/routes.php
    2. use App\Controller\MainController;
    3. use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
    4. return function (RoutingConfigurator $routes) {
    5. $routes->add('homepage', '/')
    6. ->controller([MainController::class, 'homepage'])
    7. ->stateless()
    8. ;
    9. };

Now, if the session is used, the application will report it based on your kernel.debug parameter: * enabled: will throw an Symfony\Component\HttpKernel\Exception\UnexpectedSessionUsageException exception * disabled: will log a warning

It will help you understand and hopefully fixing unexpected behavior in your application.

Generating URLs

Routing systems are bidirectional: 1) they associate URLs with controllers (as explained in the previous sections); 2) they generate URLs for a given route. Generating URLs from routes allows you to not write the <a href="..."> values manually in your HTML templates. Also, if the URL of some route changes, you only have to update the route configuration and all links will be updated.

To generate a URL, you need to specify the name of the route (e.g. blog_show) and the values of the parameters defined by the route (e.g. slug = my-blog-post).

For that reason each route has an internal name that must be unique in the application. If you don’t set the route name explicitly with the name option, Symfony generates an automatic name based on the controller and action.

Generating URLs in Controllers

If your controller extends from the AbstractController, use the `generateUrl() helper:

  1. // src/Controller/BlogController.php
  2. namespace App\Controller;
  3. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  4. use Symfony\Component\HttpFoundation\Response;
  5. use Symfony\Component\Routing\Annotation\Route;
  6. use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
  7. class BlogController extends AbstractController
  8. {
  9. /**
  10. * @Route("/blog", name="blog_list")
  11. */
  12. public function list(): Response
  13. {
  14. // generate a URL with no route arguments
  15. $signUpPage = $this->generateUrl('sign_up');
  16. // generate a URL with route arguments
  17. $userProfilePage = $this->generateUrl('user_profile', [
  18. 'username' => $user->getUserIdentifier(),
  19. ]);
  20. // generated URLs are "absolute paths" by default. Pass a third optional
  21. // argument to generate different URLs (e.g. an "absolute URL")
  22. $signUpPage = $this->generateUrl('sign_up', [], UrlGeneratorInterface::ABSOLUTE_URL);
  23. // when a route is localized, Symfony uses by default the current request locale
  24. // pass a different '_locale' value if you want to set the locale explicitly
  25. $signUpPageInDutch = $this->generateUrl('sign_up', ['_locale' => 'nl']);
  26. // ...
  27. }
  28. }

Note

If you pass to the `generateUrl() method some parameters that are not part of the route definition, they are included in the generated URL as a query string:

  1. $this->generateUrl('blog', ['page' => 2, 'category' => 'Symfony']);
  2. // the 'blog' route only defines the 'page' parameter; the generated URL is:
  3. // /blog/2?category=Symfony

Caution

While objects are converted to string when used as placeholders, they are not converted when used as extra parameters. So, if you’re passing an object (e.g. an Uuid) as value of an extra parameter, you need to explicitly convert it to a string:

  1. $this->generateUrl('blog', ['uuid' => (string) $entity->getUuid()]);

If your controller does not extend from AbstractController, you’ll need to fetch services in your controller and follow the instructions of the next section.

Generating URLs in Services

Inject the router Symfony service into your own services and use its generate() method. When using [service autowiring](https://symfony.com/doc/5.3/service_container/autowiring.html) you only need to add an argument in the service constructor and type-hint it with theSymfony\Component\Routing\Generator\UrlGeneratorInterface` class:

  1. // src/Service/SomeService.php
  2. namespace App\Service;
  3. use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
  4. class SomeService
  5. {
  6. private $router;
  7. public function __construct(UrlGeneratorInterface $router)
  8. {
  9. $this->router = $router;
  10. }
  11. public function someMethod()
  12. {
  13. // ...
  14. // generate a URL with no route arguments
  15. $signUpPage = $this->router->generate('sign_up');
  16. // generate a URL with route arguments
  17. $userProfilePage = $this->router->generate('user_profile', [
  18. 'username' => $user->getUserIdentifier(),
  19. ]);
  20. // generated URLs are "absolute paths" by default. Pass a third optional
  21. // argument to generate different URLs (e.g. an "absolute URL")
  22. $signUpPage = $this->router->generate('sign_up', [], UrlGeneratorInterface::ABSOLUTE_URL);
  23. // when a route is localized, Symfony uses by default the current request locale
  24. // pass a different '_locale' value if you want to set the locale explicitly
  25. $signUpPageInDutch = $this->router->generate('sign_up', ['_locale' => 'nl']);
  26. }
  27. }

Generating URLs in Templates

Read the section about creating links between pages in the main article about Symfony templates.

Generating URLs in JavaScript

If your JavaScript code is included in a Twig template, you can use the path() andurl() Twig functions to generate the URLs and store them in JavaScript variables. The `escape() filter is needed to escape any non-JavaScript-safe values:

  1. <script>
  2. const route = "{{ path('blog_show', {slug: 'my-blog-post'})|escape('js') }}";
  3. </script>

If you need to generate URLs dynamically or if you are using pure JavaScript code, this solution doesn’t work. In those cases, consider using the FOSJsRoutingBundle.

Generating URLs in Commands

Generating URLs in commands works the same as generating URLs in services. The only difference is that commands are not executed in the HTTP context. Therefore, if you generate absolute URLs, you’ll get http://localhost/ as the host name instead of your real host name.

The solution is to configure the default_uri option to define the “request context” used by commands when they generate URLs:

  • YAML

    1. # config/packages/routing.yaml
    2. framework:
    3. router:
    4. # ...
    5. default_uri: 'https://example.org/my/path/'
  • XML

    1. <!-- config/packages/routing.xml -->
    2. <?xml version="1.0" encoding="UTF-8" ?>
    3. <container xmlns="http://symfony.com/schema/dic/services"
    4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    5. xmlns:framework="http://symfony.com/schema/dic/symfony"
    6. xsi:schemaLocation="http://symfony.com/schema/dic/services
    7. https://symfony.com/schema/dic/services/services-1.0.xsd
    8. http://symfony.com/schema/dic/symfony
    9. https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
    10. <framework:config>
    11. <framework:router default-uri="https://example.org/my/path/">
    12. <!-- ... -->
    13. </framework:router>
    14. </framework:config>
    15. </container>
  • PHP

    1. // config/packages/routing.php
    2. use Symfony\Config\FrameworkConfig;
    3. return static function (FrameworkConfig $framework) {
    4. $framework->router()->defaultUri('https://example.org/my/path/');
    5. };

New in version 5.1: The default_uri option was introduced in Symfony 5.1.

Now you’ll get the expected results when generating URLs in your commands:

  1. // src/Command/SomeCommand.php
  2. namespace App\Command;
  3. use Symfony\Component\Console\Command\Command;
  4. use Symfony\Component\Console\Input\InputInterface;
  5. use Symfony\Component\Console\Output\OutputInterface;
  6. use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
  7. use Symfony\Component\Routing\RouterInterface;
  8. // ...
  9. class SomeCommand extends Command
  10. {
  11. private $router;
  12. public function __construct(RouterInterface $router)
  13. {
  14. parent::__construct();
  15. $this->router = $router;
  16. }
  17. protected function execute(InputInterface $input, OutputInterface $output): int
  18. {
  19. // generate a URL with no route arguments
  20. $signUpPage = $this->router->generate('sign_up');
  21. // generate a URL with route arguments
  22. $userProfilePage = $this->router->generate('user_profile', [
  23. 'username' => $user->getUserIdentifier(),
  24. ]);
  25. // generated URLs are "absolute paths" by default. Pass a third optional
  26. // argument to generate different URLs (e.g. an "absolute URL")
  27. $signUpPage = $this->router->generate('sign_up', [], UrlGeneratorInterface::ABSOLUTE_URL);
  28. // when a route is localized, Symfony uses by default the current request locale
  29. // pass a different '_locale' value if you want to set the locale explicitly
  30. $signUpPageInDutch = $this->router->generate('sign_up', ['_locale' => 'nl']);
  31. // ...
  32. }
  33. }

Note

By default, the URLs generated for web assets use the same default_uri value, but you can change it with the asset.request_context.base_path and asset.request_context.secure container parameters.

Checking if a Route Exists

In highly dynamic applications, it may be necessary to check whether a route exists before using it to generate a URL. In those cases, don’t use the getRouteCollection() method because that regenerates the routing cache and slows down the application.

Instead, try to generate the URL and catch the Symfony\Component\Routing\Exception\RouteNotFoundException thrown when the route doesn’t exist:

  1. use Symfony\Component\Routing\Exception\RouteNotFoundException;
  2. // ...
  3. try {
  4. $url = $this->router->generate($routeName, $routeParameters);
  5. } catch (RouteNotFoundException $e) {
  6. // the route is not defined...
  7. }

Forcing HTTPS on Generated URLs

By default, generated URLs use the same HTTP scheme as the current request. In console commands, where there is no HTTP request, URLs use http by default. You can change this per command (via the router’s `getContext() method) or globally with these configuration parameters:

  • YAML

    1. # config/services.yaml
    2. parameters:
    3. router.request_context.scheme: 'https'
    4. asset.request_context.secure: true
  • XML

    1. <!-- config/services.xml -->
    2. <?xml version="1.0" encoding="UTF-8" ?>
    3. <container xmlns="http://symfony.com/schema/dic/services"
    4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    5. xsi:schemaLocation="http://symfony.com/schema/dic/services
    6. https://symfony.com/schema/dic/services/services-1.0.xsd">
    7. <parameters>
    8. <parameter key="router.request_context.scheme">https</parameter>
    9. <parameter key="asset.request_context.secure">true</parameter>
    10. </parameters>
    11. </container>
  • PHP

    1. // config/services.php
    2. $container->setParameter('router.request_context.scheme', 'https');
    3. $container->setParameter('asset.request_context.secure', true);

Outside of console commands, use the schemes option to define the scheme of each route explicitly:

  • Annotations

    1. // src/Controller/SecurityController.php
    2. namespace App\Controller;
    3. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    4. use Symfony\Component\HttpFoundation\Response;
    5. use Symfony\Component\Routing\Annotation\Route;
    6. class SecurityController extends AbstractController
    7. {
    8. /**
    9. * @Route("/login", name="login", schemes={"https"})
    10. */
    11. public function login(): Response
    12. {
    13. // ...
    14. }
    15. }
  • Attributes

    1. // src/Controller/SecurityController.php
    2. namespace App\Controller;
    3. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    4. use Symfony\Component\HttpFoundation\Response;
    5. use Symfony\Component\Routing\Annotation\Route;
    6. class SecurityController extends AbstractController
    7. {
    8. #[Route('/login', name: 'login', schemes: ['https'])]
    9. public function login(): Response
    10. {
    11. // ...
    12. }
    13. }
  • YAML

    1. # config/routes.yaml
    2. login:
    3. path: /login
    4. controller: App\Controller\SecurityController::login
    5. schemes: [https]
  • XML

    1. <!-- config/routes.xml -->
    2. <?xml version="1.0" encoding="UTF-8" ?>
    3. <routes xmlns="http://symfony.com/schema/routing"
    4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    5. xsi:schemaLocation="http://symfony.com/schema/routing https://symfony.com/schema/routing/routing-1.0.xsd">
    6. <route id="login" path="/login" schemes="https"
    7. controller="App\Controller\SecurityController::login"/>
    8. </routes>
  • PHP

    1. // config/routes.php
    2. use App\Controller\SecurityController;
    3. use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
    4. return function (RoutingConfigurator $routes) {
    5. $routes->add('login', '/login')
    6. ->controller([SecurityController::class, 'login'])
    7. ->schemes(['https'])
    8. ;
    9. };

The URL generated for the login route will always use HTTPS. This means that when using the `path() Twig function to generate URLs, you may get an absolute URL instead of a relative URL if the HTTP scheme of the original request is different from the scheme used by the route:

  1. {# if the current scheme is HTTPS, generates a relative URL: /login #}
  2. {{ path('login') }}
  3. {# if the current scheme is HTTP, generates an absolute URL to change
  4. the scheme: https://example.com/login #}
  5. {{ path('login') }}

The scheme requirement is also enforced for incoming requests. If you try to access the /login URL with HTTP, you will automatically be redirected to the same URL, but with the HTTPS scheme.

If you want to force a group of routes to use HTTPS, you can define the default scheme when importing them. The following example forces HTTPS on all routes defined as annotations:

  • YAML

    1. # config/routes/annotations.yaml
    2. controllers:
    3. resource: '../../src/Controller/'
    4. type: annotation
    5. defaults:
    6. schemes: [https]
  • XML

    1. <!-- config/routes/annotations.xml -->
    2. <?xml version="1.0" encoding="UTF-8" ?>
    3. <routes xmlns="http://symfony.com/schema/routing"
    4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    5. xsi:schemaLocation="http://symfony.com/schema/routing
    6. https://symfony.com/schema/routing/routing-1.0.xsd">
    7. <import resource="../../src/Controller/" type="annotation">
    8. <default key="schemes">HTTPS</default>
    9. </import>
    10. </routes>
  • PHP

    1. // config/routes/annotations.php
    2. use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
    3. return function (RoutingConfigurator $routes) {
    4. $routes->import('../../src/Controller/', 'annotation')
    5. ->schemes(['https'])
    6. ;
    7. };

Note

The Security component provides another way to enforce HTTP or HTTPS via the requires_channel setting.

Troubleshooting

Here are some common errors you might see while working with routing:

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

This happens when your controller method has an argument (e.g. $slug):

  1. public function show(string $slug): Response
  2. {
  3. // ...
  4. }

But your route path does not have a {slug} parameter (e.g. it is /blog/show). Add a {slug} to your route path: /blog/show/{slug} or give the argument a default value (i.e. $slug = null).

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

This means that you’re trying to generate a URL to the blog_show route but you are not passing a slug value (which is required, because it has a {slug} parameter in the route path). To fix this, pass a slug value when generating the route:

  1. $this->generateUrl('blog_show', ['slug' => 'slug-value']);

or, in Twig:

  1. {{ path('blog_show', {slug: 'slug-value'}) }}

Learn more about Routing

This work, including the code samples, is licensed under a Creative Commons BY-SA 3.0 license.