Files
archived-symfony-docs/routing.rst
Javier Eguiluz d9a20c1575 Merge branch '6.4' into 7.3
* 6.4:
  [Routing] Default path for localized routes
2026-01-23 12:53:47 +01:00

3164 lines
113 KiB
ReStructuredText

Routing
=======
When your application receives a request, it calls a
:doc:`controller action </controller>` 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``).
.. _routing-creating-routes:
Creating Routes
---------------
Routes can be configured in YAML, XML, PHP or using attributes.
All formats provide the same features and performance, so choose
your favorite.
:ref:`Symfony recommends attributes <best-practice-controller-attributes>`
because it's convenient to put the route and controller in the same place.
.. _routing-route-attributes:
Creating Routes as Attributes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
PHP attributes allow you to define routes next to the code of the
:doc:`controllers </controller>` associated to those routes.
You need to add a bit of configuration to your project before using them. If your
project uses :ref:`Symfony Flex <symfony-flex>`, this file is already created for you.
Otherwise, create the following file manually:
.. code-block:: yaml
# config/routes/attributes.yaml
controllers:
resource:
path: ../../src/Controller/
namespace: App\Controller
type: attribute
kernel:
resource: App\Kernel
type: attribute
This configuration tells Symfony to look for routes defined as attributes on
classes declared in the ``App\Controller`` namespace and stored in the
``src/Controller/`` directory which follows the PSR-4 standard. The kernel can
act as a controller too, which is especially useful for small applications that
use Symfony as a microframework.
Suppose you want to define a route for the ``/blog`` URL in your application. To
do so, create a :doc:`controller class </controller>` like the following:
.. configuration-block::
.. code-block:: php-attributes
// src/Controller/BlogController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class BlogController extends AbstractController
{
#[Route('/blog', name: 'blog_list')]
public function list(): Response
{
// ...
}
}
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 the ``BlogController`` 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.
.. warning::
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 :ref:`generating URLs <routing-generating-urls>`. You only
have to remember 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
the ``BlogController``:
.. configuration-block::
.. code-block:: yaml
# config/routes.yaml
blog_list:
path: /blog
# the controller value has the format 'controller_class::method_name'
controller: App\Controller\BlogController::list
# if the action is implemented as the __invoke() method of the
# controller class, you can skip the '::method_name' part:
# controller: App\Controller\BlogController
.. code-block:: xml
<!-- config/routes.xml -->
<?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
https://symfony.com/schema/routing/routing-1.0.xsd">
<!-- the controller value has the format 'controller_class::method_name' -->
<route id="blog_list" path="/blog"
controller="App\Controller\BlogController::list"/>
<!-- if the action is implemented as the __invoke() method of the
controller class, you can skip the '::method_name' part:
controller="App\Controller\BlogController"/> -->
</routes>
.. code-block:: php
// config/routes.php
use App\Controller\BlogController;
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
return function (RoutingConfigurator $routes): void {
$routes->add('blog_list', '/blog')
// the controller value has the format [controller_class, method_name]
->controller([BlogController::class, 'list'])
// if the action is implemented as the __invoke() method of the
// controller class, you can skip the 'method_name' part:
// ->controller(BlogController::class)
;
};
.. note::
By default, Symfony loads the routes defined in both YAML and PHP formats.
If you define routes in XML format, you need to
:ref:`update the src/Kernel.php file <configuration-formats>`.
.. _routing-matching-http-methods:
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:
.. configuration-block::
.. code-block:: php-attributes
// src/Controller/BlogApiController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class BlogApiController extends AbstractController
{
#[Route('/api/posts/{id}', methods: ['GET', 'HEAD'])]
public function show(int $id): Response
{
// ... return a JSON response with the post
}
#[Route('/api/posts/{id}', methods: ['PUT'])]
public function edit(int $id): Response
{
// ... edit a post
}
}
.. code-block:: yaml
# config/routes.yaml
api_post_show:
path: /api/posts/{id}
controller: App\Controller\BlogApiController::show
methods: GET|HEAD
api_post_edit:
path: /api/posts/{id}
controller: App\Controller\BlogApiController::edit
methods: PUT
.. code-block:: xml
<!-- config/routes.xml -->
<?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
https://symfony.com/schema/routing/routing-1.0.xsd">
<route id="api_post_show" path="/api/posts/{id}"
controller="App\Controller\BlogApiController::show"
methods="GET|HEAD"/>
<route id="api_post_edit" path="/api/posts/{id}"
controller="App\Controller\BlogApiController::edit"
methods="PUT"/>
</routes>
.. code-block:: php
// config/routes.php
use App\Controller\BlogApiController;
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
return function (RoutingConfigurator $routes): void {
$routes->add('api_post_show', '/api/posts/{id}')
->controller([BlogApiController::class, 'show'])
->methods(['GET', 'HEAD'])
;
$routes->add('api_post_edit', '/api/posts/{id}')
->controller([BlogApiController::class, 'edit'])
->methods(['PUT'])
;
};
.. 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 :doc:`Symfony Forms </forms>` this is done
automatically for you when the :ref:`framework.http_method_override <configuration-framework-http_method_override>`
option is ``true``.
Matching Environments
~~~~~~~~~~~~~~~~~~~~~
Use the ``env`` option to register a route only when the current
:ref:`configuration environment <configuration-environments>` matches the
given value:
.. configuration-block::
.. code-block:: php-attributes
// src/Controller/DefaultController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class DefaultController extends AbstractController
{
#[Route('/tools', name: 'tools', env: 'dev')]
public function developerTools(): Response
{
// ...
}
}
.. code-block:: yaml
# config/routes.yaml
when@dev:
tools:
path: /tools
controller: App\Controller\DefaultController::developerTools
.. code-block:: xml
<!-- config/routes.xml -->
<?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
https://symfony.com/schema/routing/routing-1.0.xsd">
<when env="dev">
<route id="tools" path="/tools" controller="App\Controller\DefaultController::developerTools"/>
</when>
</routes>
.. code-block:: php
// config/routes.php
use App\Controller\DefaultController;
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
return function (RoutingConfigurator $routes): void {
if('dev' === $routes->env()) {
$routes->add('tools', '/tools')
->controller([DefaultController::class, 'developerTools'])
;
}
};
.. _routing-matching-expressions:
Matching Expressions
~~~~~~~~~~~~~~~~~~~~
Use the ``condition`` option if you need some route to match based on some
arbitrary matching logic:
.. configuration-block::
.. code-block:: php-attributes
// src/Controller/DefaultController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class DefaultController extends AbstractController
{
#[Route(
'/contact',
name: 'contact',
condition: "context.getMethod() in ['GET', 'HEAD'] and request.headers.get('User-Agent') matches '/firefox/i'",
// expressions can also include config parameters:
// condition: "request.headers.get('User-Agent') matches '%app.allowed_browsers%'"
)]
public function contact(): Response
{
// ...
}
#[Route(
'/posts/{id}',
name: 'post_show',
// expressions can retrieve route parameter values using the "params" variable
condition: "params['id'] < 1000"
)]
public function showPost(int $id): Response
{
// ... return a JSON response with the post
}
}
.. code-block:: yaml
# config/routes.yaml
contact:
path: /contact
controller: App\Controller\DefaultController::contact
condition: "context.getMethod() in ['GET', 'HEAD'] and request.headers.get('User-Agent') matches '/firefox/i'"
# expressions can also include configuration parameters:
# condition: "request.headers.get('User-Agent') matches '%app.allowed_browsers%'"
# expressions can even use environment variables:
# condition: "context.getHost() == env('APP_MAIN_HOST')"
post_show:
path: /posts/{id}
controller: App\Controller\DefaultController::showPost
# expressions can retrieve route parameter values using the "params" variable
condition: "params['id'] < 1000"
.. code-block:: xml
<!-- config/routes.xml -->
<?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
https://symfony.com/schema/routing/routing-1.0.xsd">
<route id="contact" path="/contact" controller="App\Controller\DefaultController::contact">
<condition>context.getMethod() in ['GET', 'HEAD'] and request.headers.get('User-Agent') matches '/firefox/i'</condition>
<!-- expressions can also include configuration parameters: -->
<!-- <condition>request.headers.get('User-Agent') matches '%app.allowed_browsers%'</condition> -->
<!-- expressions can even use environment variables: -->
<!-- <condition>context.getHost() == env('APP_MAIN_HOST')</condition> -->
</route>
<route id="post_show" path="/posts/{id}" controller="App\Controller\DefaultController::showPost">
<!-- expressions can retrieve route parameter values using the "params" variable -->
<condition>params['id'] &lt; 1000</condition>
</route>
</routes>
.. code-block:: php
// config/routes.php
use App\Controller\DefaultController;
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
return function (RoutingConfigurator $routes): void {
$routes->add('contact', '/contact')
->controller([DefaultController::class, 'contact'])
->condition('context.getMethod() in ["GET", "HEAD"] and request.headers.get("User-Agent") matches "/firefox/i"')
// expressions can also include configuration parameters:
// ->condition('request.headers.get("User-Agent") matches "%app.allowed_browsers%"')
// expressions can even use environment variables:
// ->condition('context.getHost() == env("APP_MAIN_HOST")')
;
$routes->add('post_show', '/posts/{id}')
->controller([DefaultController::class, 'showPost'])
// expressions can retrieve route parameter values using the "params" variable
->condition('params["id"] < 1000')
;
};
The value of the ``condition`` option is an expression using any valid
:doc:`expression language syntax </reference/formats/expression_language>` and
can use any of these variables created by Symfony:
``context``
An instance of :class:`Symfony\\Component\\Routing\\RequestContext`,
which holds the most fundamental information about the route being matched.
``request``
The :ref:`Symfony Request <component-http-foundation-request>` object that
represents the current request.
``params``
An array of matched :ref:`route parameters <routing-route-parameters>` for
the current route.
You can also use these functions:
``env(string $name)``
Returns the value of a variable using :doc:`Environment Variable Processors </configuration/env_var_processors>`
``service(string $alias)``
Returns a routing condition service.
First, add the ``#[AsRoutingConditionService]`` attribute or ``routing.condition_service``
tag to the services that you want to use in route conditions::
use Symfony\Bundle\FrameworkBundle\Routing\Attribute\AsRoutingConditionService;
use Symfony\Component\HttpFoundation\Request;
#[AsRoutingConditionService(alias: 'route_checker')]
class RouteChecker
{
public function check(Request $request): bool
{
// ...
}
}
Then, use the ``service()`` function to refer to that service inside conditions::
// Controller (using an alias):
#[Route(condition: "service('route_checker').check(request)")]
// Or without alias:
#[Route(condition: "service('App\\\Service\\\RouteChecker').check(request)")]
Internally, 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.
.. warning::
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:
.. code-block:: terminal
$ php bin/console debug:router
---------------- ------- ------- ----- --------------------------------------------
Name Method Scheme Host Path
---------------- ------- ------- ----- --------------------------------------------
homepage ANY ANY ANY /
contact GET ANY ANY /contact
contact_process POST ANY ANY /contact
article_show ANY ANY ANY /articles/{_locale}/{year}/{title}.{_format}
blog ANY ANY ANY /blog/{page}
blog_show ANY ANY ANY /blog/{slug}
---------------- ------- ------- ----- --------------------------------------------
# pass this option to also display all the defined route aliases
$ php bin/console debug:router --show-aliases
# pass this option to only display routes that match the given HTTP method
# (you can use the special value ANY to see routes that match any method)
$ php bin/console debug:router --method=GET
$ php bin/console debug:router --method=ANY
.. versionadded:: 7.3
The ``--method`` option was introduced in Symfony 7.3.
Pass the name (or part of the name) of some route to this argument to print the
route details:
.. code-block:: terminal
$ php bin/console debug:router app_lucky_number
+-------------+---------------------------------------------------------+
| Property | Value |
+-------------+---------------------------------------------------------+
| Route Name | app_lucky_number |
| Path | /lucky/number/{max} |
| ... | ... |
| Options | compiler_class: Symfony\Component\Routing\RouteCompiler |
| | utf8: true |
+-------------+---------------------------------------------------------+
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:
.. code-block:: terminal
$ php bin/console router:match /lucky/number/8
[OK] Route "app_lucky_number" matches
.. _routing-route-parameters:
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 ``{ }``.
For example, the route to display the blog post contents is defined as ``/blog/{slug}``:
.. configuration-block::
.. code-block:: php-attributes
// src/Controller/BlogController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class BlogController extends AbstractController
{
// ...
#[Route('/blog/{slug}', name: 'blog_show')]
public function show(string $slug): Response
{
// $slug will equal the dynamic part of the URL
// e.g. at /blog/yay-routing, then $slug='yay-routing'
// ...
}
}
.. code-block:: yaml
# config/routes.yaml
blog_show:
path: /blog/{slug}
controller: App\Controller\BlogController::show
.. code-block:: xml
<!-- config/routes.xml -->
<?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
https://symfony.com/schema/routing/routing-1.0.xsd">
<route id="blog_show" path="/blog/{slug}"
controller="App\Controller\BlogController::show"/>
</routes>
.. code-block:: php
// config/routes.php
use App\Controller\BlogController;
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
return function (RoutingConfigurator $routes): void {
$routes->add('blog_show', '/blog/{slug}')
->controller([BlogController::class, 'show'])
;
};
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 the ``BlogController`` class and passes a ``$slug = 'my-first-post'``
argument to the ``show()`` 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}``).
.. _routing-requirements:
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:
.. configuration-block::
.. code-block:: php-attributes
// src/Controller/BlogController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class BlogController extends AbstractController
{
#[Route('/blog/{page}', name: 'blog_list', requirements: ['page' => '\d+'])]
public function list(int $page): Response
{
// ...
}
#[Route('/blog/{slug}', name: 'blog_show')]
public function show(string $slug): Response
{
// ...
}
}
.. code-block:: yaml
# config/routes.yaml
blog_list:
path: /blog/{page}
controller: App\Controller\BlogController::list
requirements:
page: '\d+'
blog_show:
path: /blog/{slug}
controller: App\Controller\BlogController::show
.. code-block:: xml
<!-- config/routes.xml -->
<?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
https://symfony.com/schema/routing/routing-1.0.xsd">
<route id="blog_list" path="/blog/{page}" controller="App\Controller\BlogController::list">
<requirement key="page">\d+</requirement>
</route>
<route id="blog_show" path="/blog/{slug}"
controller="App\Controller\BlogController::show"/>
</routes>
.. code-block:: php
// config/routes.php
use App\Controller\BlogController;
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
return static function (RoutingConfigurator $routes): void {
$routes->add('blog_list', '/blog/{page}')
->controller([BlogController::class, 'list'])
->requirements(['page' => '\d+'])
;
$routes->add('blog_show', '/blog/{slug}')
->controller([BlogController::class, 'show'])
;
// ...
};
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:
======================== ============= ===============================
URL Route Parameters
======================== ============= ===============================
``/blog/2`` ``blog_list`` ``$page`` = ``2``
``/blog/my-first-post`` ``blog_show`` ``$slug`` = ``my-first-post``
======================== ============= ===============================
.. tip::
The :class:`Symfony\\Component\\Routing\\Requirement\\Requirement` enum
contains a collection of commonly used regular-expression constants such as
digits, dates and UUIDs which can be used as route parameter requirements.
.. configuration-block::
.. code-block:: php-attributes
// src/Controller/BlogController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Routing\Requirement\Requirement;
class BlogController extends AbstractController
{
#[Route('/blog/{page}', name: 'blog_list', requirements: ['page' => Requirement::DIGITS])]
public function list(int $page): Response
{
// ...
}
}
.. code-block:: yaml
# config/routes.yaml
blog_list:
path: /blog/{page}
controller: App\Controller\BlogController::list
requirements:
page: !php/const Symfony\Component\Routing\Requirement\Requirement::DIGITS
.. code-block:: php
// config/routes.php
use App\Controller\BlogController;
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
use Symfony\Component\Routing\Requirement\Requirement;
return static function (RoutingConfigurator $routes): void {
$routes->add('blog_list', '/blog/{page}')
->controller([BlogController::class, 'list'])
->requirements(['page' => Requirement::DIGITS])
;
// ...
};
.. tip::
Route requirements (and route paths too) can include
:ref:`configuration parameters <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 characters, etc.
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:
.. configuration-block::
.. code-block:: php-attributes
// src/Controller/BlogController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class BlogController extends AbstractController
{
#[Route('/blog/{page<\d+>}', name: 'blog_list')]
public function list(int $page): Response
{
// ...
}
}
.. code-block:: yaml
# config/routes.yaml
blog_list:
path: /blog/{page<\d+>}
controller: App\Controller\BlogController::list
.. code-block:: xml
<!-- config/routes.xml -->
<?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
https://symfony.com/schema/routing/routing-1.0.xsd">
<route id="blog_list" path="/blog/{page<\d+>}"
controller="App\Controller\BlogController::list"/>
<!-- ... -->
</routes>
.. code-block:: php
// config/routes.php
use App\Controller\BlogController;
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
return static function (RoutingConfigurator $routes): void {
$routes->add('blog_list', '/blog/{page<\d+>}')
->controller([BlogController::class, 'list'])
;
// ...
};
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 attributes,
default values are defined in the arguments of the controller action. In the
other configuration formats they are defined with the ``defaults`` option:
.. configuration-block::
.. code-block:: php-attributes
// src/Controller/BlogController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class BlogController extends AbstractController
{
#[Route('/blog/{page}', name: 'blog_list', requirements: ['page' => '\d+'])]
public function list(int $page = 1): Response
{
// ...
}
}
.. code-block:: yaml
# config/routes.yaml
blog_list:
path: /blog/{page}
controller: App\Controller\BlogController::list
defaults:
page: 1
requirements:
page: '\d+'
blog_show:
# ...
.. code-block:: xml
<!-- config/routes.xml -->
<?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
https://symfony.com/schema/routing/routing-1.0.xsd">
<route id="blog_list" path="/blog/{page}" controller="App\Controller\BlogController::list">
<default key="page">1</default>
<requirement key="page">\d+</requirement>
</route>
<!-- ... -->
</routes>
.. code-block:: php
// config/routes.php
use App\Controller\BlogController;
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
return static function (RoutingConfigurator $routes): void {
$routes->add('blog_list', '/blog/{page}')
->controller([BlogController::class, 'list'])
->defaults(['page' => 1])
->requirements(['page' => '\d+'])
;
};
Now, when the user visits ``/blog``, the ``blog_list`` route will match and
``$page`` will default to a value of ``1``.
.. tip::
The default value is allowed to not match the requirement.
.. warning::
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:
.. configuration-block::
.. code-block:: php-attributes
// src/Controller/BlogController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class BlogController extends AbstractController
{
#[Route('/blog/{page<\d+>?1}', name: 'blog_list')]
public function list(int $page): Response
{
// ...
}
}
.. code-block:: yaml
# config/routes.yaml
blog_list:
path: /blog/{page<\d+>?1}
controller: App\Controller\BlogController::list
.. code-block:: xml
<!-- config/routes.xml -->
<?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
https://symfony.com/schema/routing/routing-1.0.xsd">
<route id="blog_list" path="/blog/{page<\d+>?1}"
controller="App\Controller\BlogController::list"/>
<!-- ... -->
</routes>
.. code-block:: php
// config/routes.php
use App\Controller\BlogController;
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
return static function (RoutingConfigurator $routes): void {
$routes->add('blog_list', '/blog/{page<\d+>?1}')
->controller([BlogController::class, 'list'])
;
};
.. tip::
To give a ``null`` default value to any parameter, add nothing after the
``?`` character (e.g. ``/blog/{page?}``). If you do this, don't forget to
update the types of the related controller arguments to allow passing
``null`` values (e.g. replace ``int $page`` by ``?int $page``).
Priority Parameter
~~~~~~~~~~~~~~~~~~
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
attributes this is much harder to do, so you can set the
optional ``priority`` parameter in those routes to control their priority:
.. configuration-block::
.. code-block:: php-attributes
// src/Controller/BlogController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class BlogController extends AbstractController
{
/**
* This route has a greedy pattern and is defined first.
*/
#[Route('/blog/{slug}', name: 'blog_show')]
public function show(string $slug): Response
{
// ...
}
/**
* This route could not be matched without defining a higher priority than 0.
*/
#[Route('/blog/list', name: 'blog_list', priority: 2)]
public function list(): Response
{
// ...
}
}
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 a "param converter".
Now, keep the previous route configuration, but change the arguments of the
controller action. Instead of ``string $slug``, add ``BlogPost $post``::
// src/Controller/BlogController.php
namespace App\Controller;
use App\Entity\BlogPost;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class BlogController extends AbstractController
{
// ...
#[Route('/blog/{slug:post}', name: 'blog_show')]
public function show(BlogPost $post): Response
{
// $post is the object whose slug matches the routing parameter
// ...
}
}
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.
The ``{slug:post}`` syntax maps the route parameter named ``slug`` to the controller
argument named ``$post``. It also hints the "param converter" to look up the
corresponding ``BlogPost`` object from the database using the slug.
.. versionadded:: 7.1
Route parameter mapping was introduced in Symfony 7.1.
When mapping multiple entities from route parameters, name collisions can occur.
In this example, the route tries to define two mappings: one for an author and one
for a category; both using the same ``name`` parameter. This isn't allowed because
the route ends up declaring ``name`` twice::
#[Route('/search-book/{name:author}/{name:category}')]
Such routes should instead be defined using the following syntax::
#[Route('/search-book/{authorName:author.name}/{categoryName:category.name}')]
This way, the route parameter names are unique (``authorName`` and ``categoryName``),
and the "param converter" can correctly map them to controller arguments (``$author``
and ``$category``), loading them both by their name.
.. versionadded:: 7.3
This more advanced style of route parameter mapping was introduced in Symfony 7.3.
More advanced mappings can be achieved using the ``#[MapEntity]`` attribute.
Check out the :ref:`Doctrine param conversion documentation <doctrine-entity-value-resolver>`
to learn how to customize the database queries used to fetch the object from the route
parameter.
Backed Enum Parameters
~~~~~~~~~~~~~~~~~~~~~~
You can use PHP `backed enumerations`_ as route parameters because Symfony will
convert them automatically to their scalar values.
.. code-block:: php-attributes
// src/Controller/OrderController.php
namespace App\Controller;
use App\Enum\OrderStatusEnum;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class OrderController extends AbstractController
{
#[Route('/orders/list/{status}', name: 'list_orders_by_status')]
public function list(OrderStatusEnum $status = OrderStatusEnum::Paid): Response
{
// ...
}
}
Special Parameters
~~~~~~~~~~~~~~~~~~
In addition to your own parameters, routes can include any of the following
special parameters created by Symfony:
.. _routing-format-parameter:
.. _routing-locale-parameter:
``_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 :ref:`locale <translation-locale-url>` 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:
.. configuration-block::
.. code-block:: php-attributes
// src/Controller/ArticleController.php
namespace App\Controller;
// ...
class ArticleController extends AbstractController
{
#[Route(
path: '/articles/{_locale}/search.{_format}',
locale: 'en',
format: 'html',
requirements: [
'_locale' => 'en|fr',
'_format' => 'html|xml',
],
)]
public function search(): Response
{
}
}
.. code-block:: yaml
# config/routes.yaml
article_search:
path: /articles/{_locale}/search.{_format}
controller: App\Controller\ArticleController::search
locale: en
format: html
requirements:
_locale: en|fr
_format: html|xml
.. code-block:: xml
<!-- config/routes.xml -->
<?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
https://symfony.com/schema/routing/routing-1.0.xsd">
<route id="article_search"
path="/articles/{_locale}/search.{_format}"
controller="App\Controller\ArticleController::search"
locale="en"
format="html">
<requirement key="_locale">en|fr</requirement>
<requirement key="_format">html|xml</requirement>
</route>
</routes>
.. code-block:: php
// config/routes.php
namespace Symfony\Component\Routing\Loader\Configurator;
use App\Controller\ArticleController;
return static function (RoutingConfigurator $routes): void {
$routes->add('article_show', '/articles/{_locale}/search.{_format}')
->controller([ArticleController::class, 'search'])
->locale('en')
->format('html')
->requirements([
'_locale' => 'en|fr',
'_format' => 'html|xml',
])
;
};
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:
.. configuration-block::
.. code-block:: php-attributes
// src/Controller/BlogController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class BlogController extends AbstractController
{
#[Route('/blog/{page}', name: 'blog_index', defaults: ['page' => 1, 'title' => 'Hello world!'])]
public function index(int $page, string $title): Response
{
// ...
}
}
.. code-block:: yaml
# config/routes.yaml
blog_index:
path: /blog/{page}
controller: App\Controller\BlogController::index
defaults:
page: 1
title: "Hello world!"
.. code-block:: xml
<!-- config/routes.xml -->
<?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
https://symfony.com/schema/routing/routing-1.0.xsd">
<route id="blog_index" path="/blog/{page}" controller="App\Controller\BlogController::index">
<default key="page">1</default>
<default key="title">Hello world!</default>
</route>
</routes>
.. code-block:: php
// config/routes.php
use App\Controller\BlogController;
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
return static function (RoutingConfigurator $routes): void {
$routes->add('blog_index', '/blog/{page}')
->controller([BlogController::class, 'index'])
->defaults([
'page' => 1,
'title' => 'Hello world!',
])
;
};
.. _routing-slash-in-parameters:
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:
.. configuration-block::
.. code-block:: php-attributes
// src/Controller/DefaultController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class DefaultController extends AbstractController
{
#[Route('/share/{token}', name: 'share', requirements: ['token' => '.+'])]
public function share($token): Response
{
// ...
}
}
.. code-block:: yaml
# config/routes.yaml
share:
path: /share/{token}
controller: App\Controller\DefaultController::share
requirements:
token: .+
.. code-block:: xml
<!-- config/routes.xml -->
<?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
https://symfony.com/schema/routing/routing-1.0.xsd">
<route id="share" path="/share/{token}" controller="App\Controller\DefaultController::share">
<requirement key="token">.+</requirement>
</route>
</routes>
.. code-block:: php
// config/routes.php
use App\Controller\DefaultController;
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
return static function (RoutingConfigurator $routes): void {
$routes->add('share', '/share/{token}')
->controller([DefaultController::class, 'share'])
->requirements([
'token' => '.+',
])
;
};
.. 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.
.. _routing-alias:
Route Aliasing
--------------
Route alias allows you to have multiple names for the same route
and can be used to provide backward compatibility for routes that
have been renamed. Let's say you have a route called ``product_show``:
.. configuration-block::
.. code-block:: php-attributes
// src/Controller/ProductController.php
namespace App\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class ProductController
{
#[Route('/product/{id}', name: 'product_show')]
public function show(): Response
{
// ...
}
}
.. code-block:: yaml
# config/routes.yaml
product_show:
path: /product/{id}
controller: App\Controller\ProductController::show
.. code-block:: xml
<!-- config/routes.xml -->
<?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
https://symfony.com/schema/routing/routing-1.0.xsd">
<route id="product_show" path="/product/{id}" controller="App\Controller\ProductController::show"/>
</routes>
.. code-block:: php
// config/routes.php
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
return static function (RoutingConfigurator $routes): void {
$routes->add('product_show', '/product/{id}')
->controller('App\Controller\ProductController::show');
};
Now, let's say you want to create a new route called ``product_details``
that acts exactly the same as ``product_show``.
Instead of duplicating the original route, you can create an alias for it.
.. configuration-block::
.. code-block:: php-attributes
// src/Controller/ProductController.php
namespace App\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class ProductController
{
// the "alias" argument assigns an alternate name to this route;
// the alias will point to the actual route "product_show"
#[Route('/product/{id}', name: 'product_show', alias: ['product_details'])]
public function show(): Response
{
// ...
}
}
.. code-block:: yaml
# config/routes.yaml
product_show:
path: /product/{id}
controller: App\Controller\ProductController::show
product_details:
# "alias" option refers to the name of the route declared above
alias: product_show
.. code-block:: xml
<!-- config/routes.xml -->
<?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
https://symfony.com/schema/routing/routing-1.0.xsd">
<route id="product_show" path="/product/{id}" controller="App\Controller\ProductController::show"/>
<!-- "alias" attribute value refers to the name of the route declared above -->
<route id="product_details" alias="product_show"/>
</routes>
.. code-block:: php
// config/routes.php
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
return static function (RoutingConfigurator $routes): void {
$routes->add('product_show', '/product/{id}')
->controller('App\Controller\ProductController::show');
// second argument refers to the name of the route declared above
$routes->alias('product_details', 'product_show');
};
.. versionadded:: 7.3
Support for route aliases in PHP attributes was introduced in Symfony 7.3.
In this example, both ``product_show`` and ``product_details`` routes can
be used in the application and will produce the same result.
.. note::
YAML, XML, and PHP configuration formats are the only ways to define an alias
for a route that you do not own. You can't do this when using PHP attributes.
This allows you for example to use your own route name for URL generation,
while still targeting a route defined by a third-party bundle. The alias and
the original route do not need to be declared in the same file or format.
.. _routing-alias-deprecation:
Deprecating Route Aliases
~~~~~~~~~~~~~~~~~~~~~~~~~
Route aliases can be used to provide backward compatibility for routes that
have been renamed.
Now, let's say you want to replace the ``product_show`` route in favor of
``product_details`` and mark the old one as deprecated.
In the previous example, the alias ``product_details`` was pointing to
``product_show`` route.
To mark the ``product_show`` route as deprecated, you need to "switch" the alias.
The ``product_show`` become the alias, and will now point to the ``product_details`` route.
This way, the ``product_show`` alias could be deprecated.
.. configuration-block::
.. code-block:: php-attributes
// src/Controller/ProductController.php
namespace App\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\DeprecatedAlias;
use Symfony\Component\Routing\Attribute\Route;
class ProductController
{
// this outputs the following generic deprecation message:
// Since acme/package 1.2: The "product_show" route alias is deprecated. You should stop using it, as it will be removed in the future.
#[Route('/product/{id}',
name: 'product_details',
alias: new DeprecatedAlias(
aliasName: 'product_show',
package: 'acme/package',
version: '1.2',
),
)]
// Or, you can also define a custom deprecation message (%alias_id% placeholder is available)
#[Route('/product/{id}',
name: 'product_details',
alias: new DeprecatedAlias(
aliasName: 'product_show',
package: 'acme/package',
version: '1.2',
message: 'The "%alias_id%" route alias is deprecated. Please use "product_details" instead.',
),
)]
public function show(): Response
{
// ...
}
}
.. code-block:: yaml
# Move the concrete route definition under ``product_details``
product_details:
path: /product/{id}
controller: App\Controller\ProductController::show
# Define the alias and the deprecation under the ``product_show`` definition
product_show:
alias: product_details
# this outputs the following generic deprecation message:
# Since acme/package 1.2: The "product_show" route alias is deprecated. You should stop using it, as it will be removed in the future.
deprecated:
package: 'acme/package'
version: '1.2'
# or
# you can define a custom deprecation message (%alias_id% placeholder is available)
deprecated:
package: 'acme/package'
version: '1.2'
message: 'The "%alias_id%" route alias is deprecated. Please use "product_details" instead.'
.. code-block:: xml
<?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
https://symfony.com/schema/routing/routing-1.0.xsd">
<!-- Move the concrete route definition under ``product_details`` -->
<route id="product_details" path="/product/{id}" controller="App\Controller\ProductController::show"/>
<!-- Define the alias and the deprecation under the ``product_show`` definition -->
<route id="product_show" alias="product_details">
<!-- this outputs the following generic deprecation message:
Since acme/package 1.2: The "product_show" route alias is deprecated. You should stop using it, as it will be removed in the future. -->
<deprecated package="acme/package" version="1.2"/>
<!-- or -->
<!-- you can define a custom deprecation message (%alias_id% placeholder is available) -->
<deprecated package="acme/package" version="1.2">
The "%alias_id%" route alias is deprecated. Please use "product_details" instead.
</deprecated>
</route>
</routes>
.. code-block:: php
$routes->add('product_details', '/product/{id}')
->controller('App\Controller\ProductController::show');
$routes->alias('product_show', 'product_details')
// this outputs the following generic deprecation message:
// Since acme/package 1.2: The "product_show" route alias is deprecated. You should stop using it, as it will be removed in the future.
->deprecate('acme/package', '1.2', '')
// or
// you can define a custom deprecation message (%alias_id% placeholder is available)
->deprecate(
'acme/package',
'1.2',
'The "%alias_id%" route alias is deprecated. Please use "product_details" instead.'
)
;
.. versionadded:: 7.3
The ``DeprecatedAlias`` class for PHP attributes was introduced in Symfony 7.3.
In this example, every time the ``product_show`` alias is used, a deprecation
warning is triggered, advising you to stop using this route and prefer using ``product_details``.
The message is actually a message template, which replaces occurrences of the
``%alias_id%`` placeholder by the route alias name. You **must** have
at least one occurrence of the ``%alias_id%`` placeholder in your template.
.. _routing-route-groups:
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, put the common configuration
in the ``#[Route]`` attribute of the controller class.
In other routing formats, define the common configuration using options
when importing the routes.
.. configuration-block::
.. code-block:: php-attributes
// src/Controller/BlogController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
#[Route('/blog', requirements: ['_locale' => 'en|es|fr'], name: 'blog_')]
class BlogController extends AbstractController
{
#[Route('/{_locale}', name: 'index')]
public function index(): Response
{
// ...
}
#[Route('/{_locale}/posts/{slug}', name: 'show')]
public function show(string $slug): Response
{
// ...
}
}
.. code-block:: yaml
# config/routes/attributes.yaml
controllers:
resource: '../../src/Controller/'
type: attribute
# this is added to the beginning of all imported route URLs
prefix: '/blog'
# this is added to the beginning of all imported route names
name_prefix: 'blog_'
# these requirements are added to all imported routes
requirements:
_locale: 'en|es|fr'
# An imported route with an empty URL will become "/blog/"
# Uncomment this option to make that URL "/blog" instead
# trailing_slash_on_root: false
# you can optionally exclude some files/subdirectories when loading attributes
# (the value must be a string or an array of PHP glob patterns)
# exclude: '../../src/Controller/{Debug*Controller.php}'
.. code-block:: xml
<!-- config/routes/attributes.xml -->
<?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
https://symfony.com/schema/routing/routing-1.0.xsd">
<!--
the 'prefix' value is added to the beginning of all imported route URLs
the 'name-prefix' value is added to the beginning of all imported route names
the 'exclude' option defines the files or subdirectories ignored when loading attributes
(the value must be a PHP glob pattern and you can repeat this option any number of times)
-->
<import resource="../../src/Controller/"
type="attribute"
prefix="/blog"
name-prefix="blog_"
exclude="../../src/Controller/{Debug*Controller.php}">
<!-- these requirements are added to all imported routes -->
<requirement key="_locale">en|es|fr</requirement>
</import>
<!-- An imported route with an empty URL will become "/blog/"
Uncomment this option to make that URL "/blog" instead -->
<import resource="../../src/Controller/" type="attribute"
prefix="/blog"
trailing-slash-on-root="false">
<!-- ... -->
</import>
</routes>
.. code-block:: php
// config/routes/attributes.php
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
return static function (RoutingConfigurator $routes): void {
$routes->import(
'../../src/Controller/',
'attribute',
false,
// the optional fourth argument is used to exclude some files
// or subdirectories when loading attributes
// (the value must be a string or an array of PHP glob patterns)
'../../src/Controller/{Debug*Controller.php}'
)
// this is added to the beginning of all imported route URLs
->prefix('/blog')
// An imported route with an empty URL will become "/blog/"
// Pass FALSE as the second argument to make that URL "/blog" instead
// ->prefix('/blog', false)
// this is added to the beginning of all imported route names
->namePrefix('blog_')
// these requirements are added to all imported routes
->requirements(['_locale' => 'en|es|fr'])
;
};
.. warning::
The ``exclude`` option only works when the ``resource`` value is a glob string.
If you use a regular string (e.g. ``'../src/Controller'``) the ``exclude``
value will be ignored.
In this example, the route of the ``index()`` action will be called ``blog_index``
and its URL will be ``/blog/{_locale}``. The route of the ``show()`` 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 attribute.
.. note::
If any of the prefixed routes defines an empty path, Symfony adds a trailing
slash to it. In the previous example, an empty path prefixed with ``/blog``
will result in the ``/blog/`` URL. If you want to avoid this behavior, set
the ``trailing_slash_on_root`` option to ``false`` (this option is not
available when using PHP attributes):
.. configuration-block::
.. code-block:: yaml
# config/routes/attributes.yaml
controllers:
resource: '../../src/Controller/'
type: attribute
prefix: '/blog'
trailing_slash_on_root: false
# ...
.. code-block:: xml
<!-- config/routes/attributes.xml -->
<?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
https://symfony.com/schema/routing/routing-1.0.xsd">
<import resource="../../src/Controller/"
type="attribute"
prefix="/blog"
name-prefix="blog_"
trailing-slash-on-root="false"
exclude="../../src/Controller/{DebugEmailController}.php">
<!-- ... -->
</import>
</routes>
.. code-block:: php
// config/routes/attributes.php
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
return static function (RoutingConfigurator $routes): void {
$routes->import('../../src/Controller/', 'attribute')
// the second argument is the $trailingSlashOnRoot option
->prefix('/blog', false)
// ...
;
};
.. seealso::
Symfony can :doc:`import routes from different sources </routing/custom_route_loader>`
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::
// src/Controller/BlogController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class BlogController extends AbstractController
{
#[Route('/blog', name: 'blog_list')]
public function list(Request $request): Response
{
$routeName = $request->attributes->get('_route');
$routeParameters = $request->attributes->get('_route_params');
// use this to get all the available attributes (not only routing ones):
$allAttributes = $request->attributes->all();
// ...
}
}
In services, you can get this information by
:doc:`injecting the RequestStack service </service_container/request>`.
In templates, use the :ref:`Twig global app variable <twig-app-variable>`
to get the current route name (``app.current_route``) and its parameters
(``app.current_route_parameters``).
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 :ref:`rendering a template from a route <templates-render-from-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:
.. configuration-block::
.. code-block:: yaml
# config/routes.yaml
doc_shortcut:
path: /doc
controller: Symfony\Bundle\FrameworkBundle\Controller\RedirectController
defaults:
route: 'doc_page'
# optionally you can define some arguments passed to the route
page: 'index'
version: 'current'
# redirections are temporary by default (code 302) but you can make them permanent (code 301)
permanent: true
# add this to keep the original query string parameters when redirecting
keepQueryParams: true
# add this to keep the HTTP method when redirecting. The redirect status changes
# * for temporary redirects, it uses the 307 status code instead of 302
# * for permanent redirects, it uses the 308 status code instead of 301
keepRequestMethod: true
# add this to remove all original route attributes when redirecting
ignoreAttributes: true
# or specify which attributes to ignore:
# ignoreAttributes: ['offset', 'limit']
legacy_doc:
path: /legacy/doc
controller: Symfony\Bundle\FrameworkBundle\Controller\RedirectController
defaults:
# this value can be an absolute path or an absolute URL
path: 'https://legacy.example.com/doc'
permanent: true
.. code-block:: xml
<!-- config/routes.xml -->
<?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
https://symfony.com/schema/routing/routing-1.0.xsd">
<route id="doc_shortcut" path="/doc"
controller="Symfony\Bundle\FrameworkBundle\Controller\RedirectController">
<default key="route">doc_page</default>
<!-- optionally you can define some arguments passed to the route -->
<default key="page">index</default>
<default key="version">current</default>
<!-- redirections are temporary by default (code 302) but you can make them permanent (code 301)-->
<default key="permanent">true</default>
<!-- add this to keep the original query string parameters when redirecting -->
<default key="keepQueryParams">true</default>
<!-- add this to keep the HTTP method when redirecting. The redirect status changes:
* for temporary redirects, it uses the 307 status code instead of 302
* for permanent redirects, it uses the 308 status code instead of 301 -->
<default key="keepRequestMethod">true</default>
</route>
<route id="legacy_doc" path="/legacy/doc"
controller="Symfony\Bundle\FrameworkBundle\Controller\RedirectController">
<!-- this value can be an absolute path or an absolute URL -->
<default key="path">https://legacy.example.com/doc</default>
<!-- redirections are temporary by default (code 302) but you can make them permanent (code 301)-->
<default key="permanent">true</default>
</route>
</routes>
.. code-block:: php
// config/routes.php
use App\Controller\DefaultController;
use Symfony\Bundle\FrameworkBundle\Controller\RedirectController;
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
return static function (RoutingConfigurator $routes): void {
$routes->add('doc_shortcut', '/doc')
->controller(RedirectController::class)
->defaults([
'route' => 'doc_page',
// optionally you can define some arguments passed to the route
'page' => 'index',
'version' => 'current',
// redirections are temporary by default (code 302) but you can make them permanent (code 301)
'permanent' => true,
// add this to keep the original query string parameters when redirecting
'keepQueryParams' => true,
// add this to keep the HTTP method when redirecting. The redirect status changes:
// * for temporary redirects, it uses the 307 status code instead of 302
// * for permanent redirects, it uses the 308 status code instead of 301
'keepRequestMethod' => true,
])
;
$routes->add('legacy_doc', '/legacy/doc')
->controller(RedirectController::class)
->defaults([
// this value can be an absolute path or an absolute URL
'path' => 'https://legacy.example.com/doc',
// redirections are temporary by default (code 302) but you can make them permanent (code 301)
'permanent' => true,
])
;
};
.. tip::
Symfony also provides some utilities to
:ref:`redirect inside controllers <controller-redirect>`
.. _routing-trailing-slash-redirection:
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 URL 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)
========== ======================================== ==========================================
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:
.. configuration-block::
.. code-block:: php-attributes
// src/Controller/MainController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class MainController extends AbstractController
{
#[Route('/', name: 'mobile_homepage', host: 'm.example.com')]
public function mobileHomepage(): Response
{
// ...
}
#[Route('/', name: 'homepage')]
public function homepage(): Response
{
// ...
}
}
.. code-block:: yaml
# config/routes.yaml
mobile_homepage:
path: /
host: m.example.com
controller: App\Controller\MainController::mobileHomepage
homepage:
path: /
controller: App\Controller\MainController::homepage
.. code-block:: xml
<!-- config/routes.xml -->
<?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
https://symfony.com/schema/routing/routing-1.0.xsd">
<route id="mobile_homepage"
path="/"
host="m.example.com"
controller="App\Controller\MainController::mobileHomepage"/>
<route id="homepage" path="/" controller="App\Controller\MainController::homepage"/>
</routes>
.. code-block:: php
// config/routes.php
use App\Controller\MainController;
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
return static function (RoutingConfigurator $routes): void {
$routes->add('mobile_homepage', '/')
->controller([MainController::class, 'mobileHomepage'])
->host('m.example.com')
;
$routes->add('homepage', '/')
->controller([MainController::class, 'homepage'])
;
};
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``:
.. configuration-block::
.. code-block:: php-attributes
// src/Controller/MainController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class MainController extends AbstractController
{
#[Route(
'/',
name: 'mobile_homepage',
host: '{subdomain}.example.com',
defaults: ['subdomain' => 'm'],
requirements: ['subdomain' => 'm|mobile'],
)]
public function mobileHomepage(): Response
{
// ...
}
#[Route('/', name: 'homepage')]
public function homepage(): Response
{
// ...
}
}
.. code-block:: yaml
# config/routes.yaml
mobile_homepage:
path: /
host: "{subdomain}.example.com"
controller: App\Controller\MainController::mobileHomepage
defaults:
subdomain: m
requirements:
subdomain: m|mobile
homepage:
path: /
controller: App\Controller\MainController::homepage
.. code-block:: xml
<!-- config/routes.xml -->
<?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
https://symfony.com/schema/routing/routing-1.0.xsd">
<route id="mobile_homepage"
path="/"
host="{subdomain}.example.com"
controller="App\Controller\MainController::mobileHomepage">
<default key="subdomain">m</default>
<requirement key="subdomain">m|mobile</requirement>
</route>
<route id="homepage" path="/" controller="App\Controller\MainController::homepage"/>
</routes>
.. code-block:: php
// config/routes.php
use App\Controller\MainController;
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
return static function (RoutingConfigurator $routes): void {
$routes->add('mobile_homepage', '/')
->controller([MainController::class, 'mobileHomepage'])
->host('{subdomain}.example.com')
->defaults([
'subdomain' => 'm',
])
->requirements([
'subdomain' => 'm|mobile',
])
;
$routes->add('homepage', '/')
->controller([MainController::class, 'homepage'])
;
};
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 :ref:`importing routes <routing-route-groups>`
to make all of them require that host name.
.. note::
When using sub-domain routing, you must set the ``Host`` HTTP headers in
:doc:`functional tests </testing>` or routes won't match::
$crawler = $client->request(
'GET',
'/',
[],
[],
['HTTP_HOST' => 'm.example.com']
// or get the value from some configuration parameter:
// ['HTTP_HOST' => 'm.'.$client->getContainer()->getParameter('domain')]
);
.. tip::
You can also use the inline defaults and requirements format in the
``host`` option: ``{subdomain<m|mobile>?m}.example.com``
.. _i18n-routing:
Localized Routes (i18n)
-----------------------
If your application is translated into multiple languages, each route can define
a different URL per each :ref:`translation locale <translation-locale>`. This
avoids the need for duplicating routes, which also reduces the potential bugs:
.. configuration-block::
.. code-block:: php-attributes
// src/Controller/CompanyController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class CompanyController extends AbstractController
{
#[Route(path: [
'en' => '/about-us',
'nl' => '/over-ons'
// optionally, you can define a path without a locale. It will be used
// for any locale that does not match the locales above
'/about-us',
], name: 'about_us')]
public function about(): Response
{
// ...
}
}
.. code-block:: yaml
# config/routes.yaml
about_us:
path:
en: /about-us
nl: /over-ons
controller: App\Controller\CompanyController::about
.. code-block:: xml
<!-- config/routes.xml -->
<?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
https://symfony.com/schema/routing/routing-1.0.xsd">
<route id="about_us" controller="App\Controller\CompanyController::about">
<path locale="en">/about-us</path>
<path locale="nl">/over-ons</path>
</route>
</routes>
.. code-block:: php
// config/routes.php
use App\Controller\CompanyController;
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
return static function (RoutingConfigurator $routes): void {
$routes->add('about_us', [
'en' => '/about-us',
'nl' => '/over-ons',
// optionally, you can define a path without a locale. It will be used
// for any locale that does not match the locales above
'/about-us',
])
->controller([CompanyController::class, 'about'])
;
};
.. 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):
.. configuration-block::
.. code-block:: yaml
# config/routes/attributes.yaml
controllers:
resource: '../../src/Controller/'
type: attribute
prefix:
en: '' # don't prefix URLs for English, the default locale
nl: '/nl'
.. code-block:: xml
<!-- config/routes/attributes.xml -->
<?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
https://symfony.com/schema/routing/routing-1.0.xsd">
<import resource="../../src/Controller/" type="attribute">
<!-- don't prefix URLs for English, the default locale -->
<prefix locale="en"></prefix>
<prefix locale="nl">/nl</prefix>
</import>
</routes>
.. code-block:: php
// config/routes/attributes.php
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
return static function (RoutingConfigurator $routes): void {
$routes->import('../../src/Controller/', 'attribute')
->prefix([
// don't prefix URLs for English, the default locale
'en' => '',
'nl' => '/nl',
])
;
};
.. note::
If a route being imported includes the special :ref:`_locale <routing-locale-parameter>`
parameter in its own definition, Symfony will only import it for that locale
and not for the other configured locale prefixes.
E.g. if a route contains ``locale: 'en'`` in its definition and it's being
imported with ``en`` (prefix: empty) and ``nl`` (prefix: ``/nl``) locales,
that route will be available only in ``en`` locale and not in ``nl``.
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.
.. configuration-block::
.. code-block:: yaml
# config/routes/attributes.yaml
controllers:
resource: '../../src/Controller/'
type: attribute
host:
en: 'www.example.com'
nl: 'www.example.nl'
.. code-block:: xml
<!-- config/routes/attributes.xml -->
<?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
https://symfony.com/schema/routing/routing-1.0.xsd">
<import resource="../../src/Controller/" type="attribute">
<host locale="en">www.example.com</host>
<host locale="nl">www.example.nl</host>
</import>
</routes>
.. code-block:: php
// config/routes/attributes.php
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
return static function (RoutingConfigurator $routes): void {
$routes->import('../../src/Controller/', 'attribute')
->host([
'en' => 'www.example.com',
'nl' => 'www.example.nl',
])
;
};
.. _stateless-routing:
Stateless Routes
----------------
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 :doc:`/http_cache`.
Routes can configure a ``stateless`` boolean option in order to declare that the
session shouldn't be used when matching a request:
.. configuration-block::
.. code-block:: php-attributes
// src/Controller/MainController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Attribute\Route;
class MainController extends AbstractController
{
#[Route('/', name: 'homepage', stateless: true)]
public function homepage(): Response
{
// ...
}
}
.. code-block:: yaml
# config/routes.yaml
homepage:
controller: App\Controller\MainController::homepage
path: /
stateless: true
.. code-block:: xml
<!-- config/routes.xml -->
<?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
https://symfony.com/schema/routing/routing-1.0.xsd">
<route id="homepage" controller="App\Controller\MainController::homepage" path="/" stateless="true"/>
</routes>
.. code-block:: php
// config/routes.php
use App\Controller\MainController;
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
return static function (RoutingConfigurator $routes): void {
$routes->add('homepage', '/')
->controller([MainController::class, 'homepage'])
->stateless()
;
};
Now, if the session is used, the application will report it based on your
``kernel.debug`` parameter:
* ``enabled``: will throw an :class:`Symfony\\Component\\HttpKernel\\Exception\\UnexpectedSessionUsageException` exception
* ``disabled``: will log a warning
It will help you understand and hopefully fixing unexpected behavior in your application.
.. _routing-generating-urls:
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.
Symfony declares route aliases based on the FQCN if the target class has an
``__invoke()`` method that adds a route **and** if the target class added
one route exactly. Symfony also automatically adds an alias for every method
that defines only one route. Consider the following class::
// src/Controller/MainController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Attribute\Route;
final class MainController extends AbstractController
{
#[Route('/', name: 'homepage')]
public function homepage(): Response
{
// ...
}
}
Symfony will add a route alias named ``App\Controller\MainController::homepage``.
Generating URLs in Controllers
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If your controller extends from the :ref:`AbstractController <the-base-controller-class-services>`,
use the ``generateUrl()`` helper::
// src/Controller/BlogController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
class BlogController extends AbstractController
{
#[Route('/blog', name: 'blog_list')]
public function list(): Response
{
// generate a URL with no route arguments
$signUpPage = $this->generateUrl('sign_up');
// generate a URL with route arguments
$userProfilePage = $this->generateUrl('user_profile', [
'username' => $user->getUserIdentifier(),
]);
// generated URLs are "absolute paths" by default. Pass a third optional
// argument to generate different URLs (e.g. an "absolute URL")
$signUpPage = $this->generateUrl('sign_up', [], UrlGeneratorInterface::ABSOLUTE_URL);
// when a route is localized, Symfony uses by default the current request locale
// pass a different '_locale' value if you want to set the locale explicitly
$signUpPageInDutch = $this->generateUrl('sign_up', ['_locale' => 'nl']);
// ...
}
}
.. 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::
$this->generateUrl('blog', ['page' => 2, 'category' => 'Symfony']);
// the 'blog' route only defines the 'page' parameter; the generated URL is:
// /blog/2?category=Symfony
.. warning::
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::
$this->generateUrl('blog', ['uuid' => (string) $entity->getUuid()]);
If your controller does not extend from ``AbstractController``, you'll need to
:ref:`fetch services in your controller <controller-accessing-services>` and
follow the instructions of the next section.
.. _routing-generating-urls-in-services:
Generating URLs in Services
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Inject the ``router`` Symfony service into your own services and use its
``generate()`` method. When using :doc:`service autowiring </service_container/autowiring>`
you only need to add an argument in the service constructor and type-hint it with
the :class:`Symfony\\Component\\Routing\\Generator\\UrlGeneratorInterface` class::
// src/Service/SomeService.php
namespace App\Service;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
class SomeService
{
public function __construct(
private UrlGeneratorInterface $urlGenerator,
) {
}
public function someMethod(): void
{
// ...
// generate a URL with no route arguments
$signUpPage = $this->urlGenerator->generate('sign_up');
// generate a URL with route arguments
$userProfilePage = $this->urlGenerator->generate('user_profile', [
'username' => $user->getUserIdentifier(),
]);
// generated URLs are "absolute paths" by default. Pass a third optional
// argument to generate different URLs (e.g. an "absolute URL")
$signUpPage = $this->urlGenerator->generate('sign_up', [], UrlGeneratorInterface::ABSOLUTE_URL);
// when a route is localized, Symfony uses by default the current request locale
// pass a different '_locale' value if you want to set the locale explicitly
$signUpPageInDutch = $this->urlGenerator->generate('sign_up', ['_locale' => 'nl']);
}
}
Generating URLs in Templates
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Read the section about :ref:`creating links between pages <templates-link-to-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()`` and ``url()`` Twig functions to generate the URLs and store them in
JavaScript variables. The ``escape()`` filter is needed to escape any
non-JavaScript-safe values:
.. code-block:: html+twig
<script>
const route = "{{ path('blog_show', {slug: 'my-blog-post'})|escape('js') }}";
</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`_.
.. _router-generate-urls-commands:
Generating URLs in Commands
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Generating URLs in commands works the same as
:ref:`generating URLs in services <routing-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:
.. configuration-block::
.. code-block:: yaml
# config/packages/routing.yaml
framework:
router:
# ...
default_uri: 'https://example.org/my/path/'
.. code-block:: xml
<!-- config/packages/routing.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:framework="http://symfony.com/schema/dic/symfony"
xsi:schemaLocation="http://symfony.com/schema/dic/services
https://symfony.com/schema/dic/services/services-1.0.xsd
http://symfony.com/schema/dic/symfony
https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
<framework:config>
<framework:router default-uri="https://example.org/my/path/">
<!-- ... -->
</framework:router>
</framework:config>
</container>
.. code-block:: php
// config/packages/routing.php
use Symfony\Config\FrameworkConfig;
return static function (FrameworkConfig $framework): void {
$framework->router()->defaultUri('https://example.org/my/path/');
};
Now you'll get the expected results when generating URLs in your commands::
// src/Command/MyCommand.php
namespace App\Command;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
// ...
#[AsCommand(name: 'app:my-command')]
class MyCommand
{
public function __construct(
private UrlGeneratorInterface $urlGenerator,
) {
}
public function __invoke(SymfonyStyle $io): int
{
// generate a URL with no route arguments
$signUpPage = $this->urlGenerator->generate('sign_up');
// generate a URL with route arguments
$userProfilePage = $this->urlGenerator->generate('user_profile', [
'username' => $user->getUserIdentifier(),
]);
// by default, generated URLs are "absolute paths". Pass a third optional
// argument to generate different URIs (e.g. an "absolute URL")
$signUpPage = $this->urlGenerator->generate('sign_up', [], UrlGeneratorInterface::ABSOLUTE_URL);
// when a route is localized, Symfony uses by default the current request locale
// pass a different '_locale' value if you want to set the locale explicitly
$signUpPageInDutch = $this->urlGenerator->generate('sign_up', ['_locale' => 'nl']);
// ...
}
}
.. 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
:method:`Symfony\\Component\\Routing\\Router::getRouteCollection` method because
that regenerates the routing cache and slows down the application.
Instead, try to generate the URL and catch the
:class:`Symfony\\Component\\Routing\\Exception\\RouteNotFoundException` thrown
when the route doesn't exist::
use Symfony\Component\Routing\Exception\RouteNotFoundException;
// ...
try {
$url = $this->router->generate($routeName, $routeParameters);
} catch (RouteNotFoundException $e) {
// the route is not defined...
}
.. _routing-force-https:
Forcing HTTPS on Generated URLs
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. note::
If your server runs behind a proxy that terminates SSL, make sure to
:doc:`configure Symfony to work behind a proxy </deployment/proxies>`
The configuration for the scheme is only used for non-HTTP requests.
The ``schemes`` option together with incorrect proxy configuration will
lead to a redirect loop.
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:
.. configuration-block::
.. code-block:: yaml
# config/services.yaml
parameters:
router.request_context.scheme: 'https'
asset.request_context.secure: true
.. code-block:: xml
<!-- config/services.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services
https://symfony.com/schema/dic/services/services-1.0.xsd">
<parameters>
<parameter key="router.request_context.scheme">https</parameter>
<parameter key="asset.request_context.secure">true</parameter>
</parameters>
</container>
.. code-block:: php
// config/services.php
$container->parameters()
->set('router.request_context.scheme', 'https')
->set('asset.request_context.secure', true)
;
Outside of console commands, use the ``schemes`` option to define the scheme of
each route explicitly:
.. configuration-block::
.. code-block:: php-attributes
// src/Controller/SecurityController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class SecurityController extends AbstractController
{
#[Route('/login', name: 'login', schemes: ['https'])]
public function login(): Response
{
// ...
}
}
.. code-block:: yaml
# config/routes.yaml
login:
path: /login
controller: App\Controller\SecurityController::login
schemes: [https]
.. code-block:: xml
<!-- config/routes.xml -->
<?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 https://symfony.com/schema/routing/routing-1.0.xsd">
<route id="login" path="/login" schemes="https"
controller="App\Controller\SecurityController::login"/>
</routes>
.. code-block:: php
// config/routes.php
use App\Controller\SecurityController;
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
return static function (RoutingConfigurator $routes): void {
$routes->add('login', '/login')
->controller([SecurityController::class, 'login'])
->schemes(['https'])
;
};
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:
.. code-block:: twig
{# if the current scheme is HTTPS, generates a relative URL: /login #}
{{ path('login') }}
{# if the current scheme is HTTP, generates an absolute URL to change
the scheme: https://example.com/login #}
{{ 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 attributes:
.. configuration-block::
.. code-block:: yaml
# config/routes/attributes.yaml
controllers:
resource: '../../src/Controller/'
type: attribute
schemes: [https]
.. code-block:: xml
<!-- config/routes/attributes.xml -->
<?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
https://symfony.com/schema/routing/routing-1.0.xsd">
<import resource="../../src/Controller/" type="attribute" schemes="https"/>
</routes>
.. code-block:: php
// config/routes/attributes.php
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
return static function (RoutingConfigurator $routes): void {
$routes->import('../../src/Controller/', 'attribute')
->schemes(['https'])
;
};
.. note::
The Security component provides
:doc:`another way to enforce HTTP or HTTPS </security/force_https>`
via the ``requires_channel`` setting.
Signing URIs
~~~~~~~~~~~~
A signed URI is an URI that includes a hash value that depends on the contents of
the URI. This way, you can later check the integrity of the signed URI by
recomputing its hash value and comparing it with the hash included in the URI.
Symfony provides a utility to sign URIs via the :class:`Symfony\\Component\\HttpFoundation\\UriSigner`
service, which you can inject in your services or controllers::
// src/Service/SomeService.php
namespace App\Service;
use Symfony\Component\HttpFoundation\UriSigner;
class SomeService
{
public function __construct(
private UriSigner $uriSigner,
) {
}
public function someMethod(): void
{
// ...
// generate a URL yourself or get it somehow...
$url = 'https://example.com/foo/bar?sort=desc';
// sign the URL (it adds a query parameter called '_hash')
$signedUrl = $this->uriSigner->sign($url);
// $url = 'https://example.com/foo/bar?sort=desc&_hash=e4a21b9'
// check the URL signature
$uriSignatureIsValid = $this->uriSigner->check($signedUrl);
// $uriSignatureIsValid = true
// if you have access to the current Request object, you can use this
// other method to pass the entire Request object instead of the URI:
$uriSignatureIsValid = $this->uriSigner->checkRequest($request);
}
}
For security reasons, it's common to make signed URIs expire after some time
(e.g. when using them to reset user credentials). By default, signed URIs don't
expire, but you can define an expiration date/time using the ``$expiration``
argument of :method:`Symfony\\Component\\HttpFoundation\\UriSigner::sign`::
// src/Service/SomeService.php
namespace App\Service;
use Symfony\Component\HttpFoundation\UriSigner;
class SomeService
{
public function __construct(
private UriSigner $uriSigner,
) {
}
public function someMethod(): void
{
// ...
// generate a URL yourself or get it somehow...
$url = 'https://example.com/foo/bar?sort=desc';
// sign the URL with an explicit expiration date
$signedUrl = $this->uriSigner->sign($url, new \DateTimeImmutable('2050-01-01'));
// $signedUrl = 'https://example.com/foo/bar?sort=desc&_expiration=2524608000&_hash=e4a21b9'
// if you pass a \DateInterval, it will be added from now to get the expiration date
$signedUrl = $this->uriSigner->sign($url, new \DateInterval('PT10S')); // valid for 10 seconds from now
// $signedUrl = 'https://example.com/foo/bar?sort=desc&_expiration=1712414278&_hash=e4a21b9'
// you can also use a timestamp in seconds
$signedUrl = $this->uriSigner->sign($url, 4070908800); // timestamp for the date 2099-01-01
// $signedUrl = 'https://example.com/foo/bar?sort=desc&_expiration=4070908800&_hash=e4a21b9'
}
}
.. note::
The expiration date/time is included in the signed URIs as a timestamp via
the ``_expiration`` query parameter.
.. versionadded:: 7.1
The feature to add an expiration date for a signed URI was introduced in Symfony 7.1.
If you need to know the reason why a signed URI is invalid, you can use the
``verify()`` method which throws exceptions on failure::
use Symfony\Component\HttpFoundation\Exception\ExpiredSignedUriException;
use Symfony\Component\HttpFoundation\Exception\UnsignedUriException;
use Symfony\Component\HttpFoundation\Exception\UnverifiedSignedUriException;
// ...
try {
$uriSigner->verify($uri); // $uri can be a string or Request object
// the URI is valid
} catch (UnsignedUriException) {
// the URI isn't signed
} catch (UnverifiedSignedUriException) {
// the URI is signed but the signature is invalid
} catch (ExpiredSignedUriException) {
// the URI is signed but expired
}
.. versionadded:: 7.3
The ``verify()`` method was introduced in Symfony 7.3.
.. tip::
If ``symfony/clock`` is installed, it will be used to create and verify
expirations. This allows you to :ref:`mock the current time in your tests
<clock_writing-tests>`.
.. versionadded:: 7.3
Support for :doc:`Symfony Clock </components/clock>` in ``UriSigner`` was
introduced in Symfony 7.3.
Troubleshooting
---------------
Here are some common errors you might see while working with routing:
.. code-block:: text
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``)::
public function show(string $slug): Response
{
// ...
}
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``).
.. code-block:: text
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::
$this->generateUrl('blog_show', ['slug' => 'slug-value']);
or, in Twig:
.. code-block:: twig
{{ path('blog_show', {slug: 'slug-value'}) }}
Learn more about Routing
------------------------
.. toctree::
:maxdepth: 1
:glob:
routing/*
.. _`PHP regular expressions`: https://www.php.net/manual/en/book.pcre.php
.. _`PCRE Unicode properties`: https://www.php.net/manual/en/regexp.reference.unicode.php
.. _`FOSJsRoutingBundle`: https://github.com/FriendsOfSymfony/FOSJsRoutingBundle
.. _`backed enumerations`: https://www.php.net/manual/en/language.enumerations.backed.php