mirror of
https://github.com/symfony/symfony-docs.git
synced 2026-03-23 16:22:10 +01:00
3164 lines
113 KiB
ReStructuredText
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'] < 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
|