From 1268ba7b43be6d6793bb149f2d58b4f34570312e Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Wed, 7 Aug 2019 16:59:25 +0200 Subject: [PATCH] Revamped the routing documentation --- _build/redirection_map | 14 + components/routing.rst | 359 +-- console/request_context.rst | 99 - controller.rst | 4 +- reference/configuration/framework.rst | 2 +- reference/forms/types/options/method.rst.inc | 2 +- routing.rst | 2114 ++++++++++++++---- routing/conditions.rst | 112 - routing/custom_route_loader.rst | 84 + routing/debug.rst | 71 - routing/external_resources.rst | 260 --- routing/extra_information.rst | 102 - routing/generate_url_javascript.rst | 25 - routing/hostname_pattern.rst | 439 ---- routing/optional_placeholders.rst | 198 -- routing/redirect_in_config.rst | 260 --- routing/redirect_trailing_slash.rst | 12 - routing/requirements.rst | 310 --- routing/scheme.rst | 94 - routing/service_container_parameters.rst | 212 -- routing/slash_in_parameter.rst | 99 - security/force_https.rst | 6 +- templating/formats.rst | 6 +- translation/locale.rst | 8 +- 24 files changed, 1833 insertions(+), 3059 deletions(-) delete mode 100644 console/request_context.rst delete mode 100644 routing/conditions.rst delete mode 100644 routing/debug.rst delete mode 100644 routing/external_resources.rst delete mode 100644 routing/extra_information.rst delete mode 100644 routing/generate_url_javascript.rst delete mode 100644 routing/hostname_pattern.rst delete mode 100644 routing/optional_placeholders.rst delete mode 100644 routing/redirect_in_config.rst delete mode 100644 routing/redirect_trailing_slash.rst delete mode 100644 routing/requirements.rst delete mode 100644 routing/scheme.rst delete mode 100644 routing/service_container_parameters.rst delete mode 100644 routing/slash_in_parameter.rst diff --git a/_build/redirection_map b/_build/redirection_map index b932501df3e..10b3bc9432a 100644 --- a/_build/redirection_map +++ b/_build/redirection_map @@ -429,3 +429,17 @@ /setup/composer /setup /security/security_checker /setup /service_container/parameters /configuration +/routing/generate_url_javascript /routing +/routing/slash_in_parameter /routing +/routing/scheme /routing +/routing/optional_placeholders /routing +/routing/conditions /routing +/routing/requirements /routing +/routing/redirect_trailing_slash /routing +/routing/debug /routing +/routing/service_container_parameters /routing +/routing/redirect_in_config /routing +/routing/external_resources /routing +/routing/hostname_pattern /routing +/routing/extra_information /routing +/console/request_context /routing diff --git a/components/routing.rst b/components/routing.rst index 020abfa912e..7bccb0e0a47 100644 --- a/components/routing.rst +++ b/components/routing.rst @@ -6,7 +6,8 @@ The Routing Component ===================== The Routing component maps an HTTP request to a set of configuration - variables. + variables. It's used to build routing systems for web applications where + each URL is associated with some code to execute. Installation ------------ @@ -20,45 +21,51 @@ Installation Usage ----- -.. seealso:: +The main :doc:`Symfony routing ` article explains all the features of +this component when used inside a Symfony application. This article only +explains the things you need to do to use it in a non-Symfony PHP application. - This article explains how to use the Routing features as an independent - component in any PHP application. Read the :doc:`/routing` article to learn - about how to use it in Symfony applications. +Routing System Setup +-------------------- -In order to set up a basic routing system you need three parts: +A routing system has three parts: -* A :class:`Symfony\\Component\\Routing\\RouteCollection`, which contains the route definitions (instances of the class :class:`Symfony\\Component\\Routing\\Route`) -* A :class:`Symfony\\Component\\Routing\\RequestContext`, which has information about the request -* A :class:`Symfony\\Component\\Routing\\Matcher\\UrlMatcher`, which performs the mapping of the path to a single route +* A :class:`Symfony\\Component\\Routing\\RouteCollection`, which contains the + route definitions (instances of the class :class:`Symfony\\Component\\Routing\\Route`); +* A :class:`Symfony\\Component\\Routing\\RequestContext`, which has information + about the request; +* A :class:`Symfony\\Component\\Routing\\Matcher\\UrlMatcher`, which performs + the mapping of the path to a single route. -Here is a quick example. Notice that this assumes that you've already configured -your autoloader to load the Routing component:: +Here is a quick example:: + use App\Controller\BlogController; use Symfony\Component\Routing\Matcher\UrlMatcher; use Symfony\Component\Routing\RequestContext; use Symfony\Component\Routing\Route; use Symfony\Component\Routing\RouteCollection; - $route = new Route('/foo', ['_controller' => 'MyController']); + $route = new Route('/blog/{slug}', ['_controller' => BlogController::class]) $routes = new RouteCollection(); - $routes->add('route_name', $route); + $routes->add('blog_show', $route); $context = new RequestContext('/'); + // Routing can match routes with incoming requests $matcher = new UrlMatcher($routes, $context); + $parameters = $matcher->match('/blog/lorem-ipsum'); + // $parameters = [ + // '_controller' => 'App\Controller\BlogController', + // 'slug' => 'lorem-ipsum', + // '_route' => 'blog_show' + // ] - $parameters = $matcher->match('/foo'); - // ['_controller' => 'MyController', '_route' => 'route_name'] - -.. note:: - - The :class:`Symfony\\Component\\Routing\\RequestContext` parameters can be populated - with the values stored in ``$_SERVER``, but it's easier to use the HttpFoundation - component as explained :ref:`below `. - -You can add as many routes as you like to a -:class:`Symfony\\Component\\Routing\\RouteCollection`. + // Routing can also generate URLs for a given route + $generator = new UrlGenerator($routes, $context); + $url = $generator->generate('blog_show', [ + 'slug' => 'my-blog-post', + ]); + // $url = '/blog/my-blog-post' The :method:`RouteCollection::add() ` method takes two arguments. The first is the name of the route. The second @@ -68,50 +75,19 @@ of custom variables can be *anything* that's significant to your application, and is returned when that route is matched. The :method:`UrlMatcher::match() ` -returns the variables you set on the route as well as the wildcard placeholders -(see below). Your application can now use this information to continue -processing the request. In addition to the configured variables, a ``_route`` -key is added, which holds the name of the matched route. +returns the variables you set on the route as well as the route parameters. +Your application can now use this information to continue processing the request. +In addition to the configured variables, a ``_route`` key is added, which holds +the name of the matched route. If no matching route can be found, a :class:`Symfony\\Component\\Routing\\Exception\\ResourceNotFoundException` will be thrown. Defining Routes -~~~~~~~~~~~~~~~ - -A full route definition can contain up to eight parts: - -#. The URL pattern. This is matched against the URL passed to the - ``RequestContext``. It is not a regular expression, but can contain named - wildcard placeholders (e.g. ``{slug}``) to match dynamic parts in the URL. - The component will create the regular expression from it. - -#. An array of default parameters. This contains an array of arbitrary values - that will be returned when the request matches the route. It is used by - convention to map a controller to the route. - -#. An array of requirements. These define constraints for the values of the - placeholders in the pattern as regular expressions. - -#. An array of options. These contain advanced settings for the route and - can be used to control encoding or customize compilation. - See :ref:`routing-unicode-support` below. You can learn more about them by - reading :method:`Symfony\\Component\\Routing\\Route::setOptions` implementation. - -#. A host. This is matched against the host of the request. See - :doc:`/routing/hostname_pattern` for more details. - -#. An array of schemes. These enforce a certain HTTP scheme (``http``, ``https``). - -#. An array of methods. These enforce a certain HTTP request method (``HEAD``, - ``GET``, ``POST``, ...). +--------------- -#. A condition, using the :doc:`/components/expression_language/syntax`. - A string that must evaluate to ``true`` so the route matches. See - :doc:`/routing/conditions` for more details. - -Take the following route, which combines several of these ideas:: +A full route definition can contain up to eight parts:: $route = new Route( '/archive/{month}', // path @@ -137,36 +113,13 @@ Take the following route, which combines several of these ideas:: $parameters = $matcher->match('/archive/foo'); // throws ResourceNotFoundException -In this case, the route is matched by ``/archive/2012-01``, because the ``{month}`` -wildcard matches the regular expression wildcard given. However, ``/archive/foo`` -does *not* match, because "foo" fails the month wildcard. - -When using wildcards, these are returned in the array result when calling -``match``. The part of the path that the wildcard matched (e.g. ``2012-01``) is used -as value. - -A placeholder matches any character except slashes ``/`` by default, unless you define -a specific requirement for it. -The reason is that they are used by convention to separate different placeholders. - -If you want a placeholder to match anything, it must be the last of the route:: - - $route = new Route( - '/start/{required}/{anything}', - ['required' => 'default'], // should always be defined - ['anything' => '.*'] // explicit requirement to allow "/" - ); - -Learn more about it by reading :ref:`routing/slash_in_parameter`. - -Using Prefixes and Collection Settings -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Route Collections +----------------- You can add routes or other instances of :class:`Symfony\\Component\\Routing\\RouteCollection` to *another* collection. -This way you can build a tree of routes. Additionally you can define a prefix -and default values for the parameters, requirements, options, schemes and the -host to all routes of a subtree using methods provided by the +This way you can build a tree of routes. Additionally you can define common +options for all routes of a subtree using methods provided by the ``RouteCollection`` class:: $rootCollection = new RouteCollection(); @@ -185,8 +138,8 @@ host to all routes of a subtree using methods provided by the $rootCollection->addCollection($subCollection); -Set the Request Parameters -~~~~~~~~~~~~~~~~~~~~~~~~~~ +Setting the Request Parameters +------------------------------ The :class:`Symfony\\Component\\Routing\\RequestContext` provides information about the current request. You can define all parameters of an HTTP request @@ -216,67 +169,15 @@ Normally you can pass the values from the ``$_SERVER`` variable to populate the $context = new RequestContext(); $context->fromRequest(Request::createFromGlobals()); -Generate a URL -~~~~~~~~~~~~~~ - -While the :class:`Symfony\\Component\\Routing\\Matcher\\UrlMatcher` tries -to find a route that fits the given request you can also build a URL from -a certain route with the :class:`Symfony\\Component\\Routing\\Generator\\UrlGenerator`:: - - use Symfony\Component\Routing\Generator\UrlGenerator; - use Symfony\Component\Routing\RequestContext; - use Symfony\Component\Routing\Route; - use Symfony\Component\Routing\RouteCollection; - - $routes = new RouteCollection(); - $routes->add('show_post', new Route('/show/{slug}')); - - $context = new RequestContext('/'); - - $generator = new UrlGenerator($routes, $context); - - $url = $generator->generate('show_post', [ - 'slug' => 'my-blog-post', - ]); - // /show/my-blog-post - -.. note:: - - If you have defined a scheme, an absolute URL is generated if the scheme - of the current :class:`Symfony\\Component\\Routing\\RequestContext` does - not match the requirement. - -Check 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; +Loading Routes +-------------- - // ... - - try { - $url = $generator->generate($dynamicRouteName, $parameters); - } catch (RouteNotFoundException $e) { - // the route is not defined... - } - -Load Routes from a File -~~~~~~~~~~~~~~~~~~~~~~~ +The Routing component comes with a number of loader classes, each giving you the +ability to load a collection of route definitions from external resources. -You've already seen how you can add routes to a collection right inside -PHP. But you can also load routes from a number of different files. +File Routing Loaders +~~~~~~~~~~~~~~~~~~~~ -The Routing component comes with a number of loader classes, each giving -you the ability to load a collection of route definitions from an external -file of some format. Each loader expects a :class:`Symfony\\Component\\Config\\FileLocator` instance as the constructor argument. You can use the :class:`Symfony\\Component\\Config\\FileLocator` to define an array of paths in which the loader will look for the requested files. @@ -291,7 +192,6 @@ If you're using the ``YamlFileLoader``, then route definitions look like this: path: /foo controller: MyController::fooAction methods: GET|HEAD - route2: path: /foo/bar controller: FooBarInvokableController @@ -315,7 +215,8 @@ other loaders that work the same way: * :class:`Symfony\\Component\\Routing\\Loader\\PhpFileLoader` If you use the :class:`Symfony\\Component\\Routing\\Loader\\PhpFileLoader` you -have to provide the name of a PHP file which returns a callable handling a :class:`Symfony\\Component\\Routing\\Loader\\Configurator\\RoutingConfigurator`. +have to provide the name of a PHP file which returns a callable handling a +:class:`Symfony\\Component\\Routing\\Loader\\Configurator\\RoutingConfigurator`. This class allows to chain imports, collections or simple route definition calls:: // RouteProvider.php @@ -328,8 +229,8 @@ This class allows to chain imports, collections or simple route definition calls ; }; -Routes as Closures -.................. +Closure Routing Loaders +~~~~~~~~~~~~~~~~~~~~~~~ There is also the :class:`Symfony\\Component\\Routing\\Loader\\ClosureLoader`, which calls a closure and uses the result as a :class:`Symfony\\Component\\Routing\\RouteCollection`:: @@ -343,8 +244,8 @@ calls a closure and uses the result as a :class:`Symfony\\Component\\Routing\\Ro $loader = new ClosureLoader(); $routes = $loader->load($closure); -Routes as Annotations -..................... +Annotation Routing Loaders +~~~~~~~~~~~~~~~~~~~~~~~~~~ Last but not least there are :class:`Symfony\\Component\\Routing\\Loader\\AnnotationDirectoryLoader` and @@ -392,155 +293,6 @@ automatically in the background if you want to use it. A basic example of the are saved in the ``cache_dir``. This means your script must have write permissions for that location. -.. _routing-unicode-support: - -Unicode Routing Support -~~~~~~~~~~~~~~~~~~~~~~~ - -The Routing component supports UTF-8 characters in route paths and requirements. -Thanks to the ``utf8`` route option, you can make Symfony match and generate -routes with UTF-8 characters: - -.. configuration-block:: - - .. code-block:: php-annotations - - namespace App\Controller; - - use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; - use Symfony\Component\Routing\Annotation\Route; - - class DefaultController extends AbstractController - { - /** - * @Route("/category/{name}", name="route1", utf8=true) - */ - public function category() - { - // ... - } - - .. code-block:: yaml - - route1: - path: /category/{name} - controller: App\Controller\DefaultController::category - utf8: true - - .. code-block:: xml - - - - - - - - .. code-block:: php - - // config/routes.php - use App\Controller\DefaultController; - use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; - - return function (RoutingConfigurator $routes) { - $routes->add('route1', '/category/{name}') - ->controller([DefaultController::class, 'category']) - ->utf8() - ; - }; - -.. versionadded:: 4.3 - - The ``utf8`` option/method has been introduced in Symfony 4.3. - Before you had to use the ``options`` setting to define this value (for - example, when using annotations: ``options={"utf8": true}``). - -In this route, the ``utf8`` option set to ``true`` makes Symfony consider the -``.`` requirement to match any UTF-8 characters instead of just a single -byte character. This means that so the following URLs would match: -``/category/日本語``, ``/category/فارسی``, ``/category/한국어``, etc. In case you -are wondering, this option also allows to include and match emojis in URLs. - -You can also include UTF-8 strings as routing requirements: - -.. configuration-block:: - - .. code-block:: php-annotations - - namespace App\Controller; - - use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; - use Symfony\Component\Routing\Annotation\Route; - - class DefaultController extends AbstractController - { - /** - * @Route( - * "/category/{name}", - * name="route2", - * defaults={"name": "한국어"}, - * utf8=true - * ) - */ - public function category() - { - // ... - } - - .. code-block:: yaml - - route2: - path: /category/{name} - controller: App\Controller\DefaultController::category - defaults: - name: "한국어" - utf8: true - - .. code-block:: xml - - - - - - 한국어 - - - - .. code-block:: php - - // config/routes.php - use App\Controller\DefaultController; - use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; - - return function (RoutingConfigurator $routes) { - $routes->add('route2', '/category/{name}') - ->controller([DefaultController::class, 'category']) - ->defaults([ - 'name' => '한국어', - ]) - ->utf8() - ; - }; - -.. tip:: - - In addition to UTF-8 characters, the Routing component also supports all - the `PCRE Unicode properties`_, which are escape sequences that match - generic character types. For example, ``\p{Lu}`` matches any uppercase - character in any language, ``\p{Greek}`` matches any Greek character, - ``\P{Han}`` matches any character not included in the Chinese Han script. - Learn more ---------- @@ -554,4 +306,3 @@ Learn more /controller/* .. _Packagist: https://packagist.org/packages/symfony/routing -.. _PCRE Unicode properties: http://php.net/manual/en/regexp.reference.unicode.php diff --git a/console/request_context.rst b/console/request_context.rst deleted file mode 100644 index f2fbb505cf2..00000000000 --- a/console/request_context.rst +++ /dev/null @@ -1,99 +0,0 @@ -.. index:: - single: Console; Generating URLs - -How to Generate URLs from the Console -===================================== - -Unfortunately, the command line context does not know about your VirtualHost -or domain name. This means that if you generate absolute URLs within a -console command you'll probably end up with something like ``http://localhost/foo/bar`` -which is not very useful. - -To fix this, you need to configure the "request context", which is a fancy -way of saying that you need to configure your environment so that it knows -what URL it should use when generating URLs. - -There are two ways of configuring the request context: at the application level -and per Command. - -Configuring the Request Context Globally ----------------------------------------- - -To configure the Request Context - which is used by the URL Generator - you can -redefine the parameters it uses as default values to change the default host -(``localhost``) and scheme (``http``). You can also configure the base path (both for -the URL generator and the assets) if Symfony is not running in the root directory. - -Note that this does not impact URLs generated via normal web requests, since those -will override the defaults. - -.. configuration-block:: - - .. code-block:: yaml - - # config/services.yaml - parameters: - router.request_context.host: 'example.org' - router.request_context.scheme: 'https' - router.request_context.base_url: 'my/path' - asset.request_context.base_path: '%router.request_context.base_url%' - asset.request_context.secure: true - - .. code-block:: xml - - - - - - - example.org - https - my/path - %router.request_context.base_url% - true - - - - - .. code-block:: php - - // config/services.php - $container->setParameter('router.request_context.host', 'example.org'); - $container->setParameter('router.request_context.scheme', 'https'); - $container->setParameter('router.request_context.base_url', 'my/path'); - $container->setParameter('asset.request_context.base_path', $container->getParameter('router.request_context.base_url')); - $container->setParameter('asset.request_context.secure', true); - -Configuring the Request Context per Command -------------------------------------------- - -To change it only in one command you need to fetch the Request Context -from the ``router`` service and override its settings:: - - // src/Command/DemoCommand.php - use Symfony\Component\Routing\RouterInterface; - // ... - - class DemoCommand extends Command - { - private $router; - - public function __construct(RouterInterface $router) - { - parent::__construct(); - - $this->router = $router; - } - - protected function execute(InputInterface $input, OutputInterface $output) - { - $context = $this->router->getContext(); - $context->setHost('example.com'); - $context->setScheme('https'); - $context->setBaseUrl('my/path'); - - $url = $this->router->generate('route-name', ['param-name' => 'param-value']); - // ... - } - } diff --git a/controller.rst b/controller.rst index 0d7c225007a..2513b5d3faf 100644 --- a/controller.rst +++ b/controller.rst @@ -125,6 +125,8 @@ method is just a helper method that generates the URL for a given route:: $url = $this->generateUrl('app_lucky_number', ['max' => 10]); +.. _controller-redirect: + Redirecting ~~~~~~~~~~~ @@ -297,7 +299,7 @@ use: .. code-block:: terminal $ php bin/console make:crud Product - + created: src/Controller/ProductController.php created: src/Form/ProductType.php created: templates/product/_delete_form.html.twig diff --git a/reference/configuration/framework.rst b/reference/configuration/framework.rst index 2889c275895..b93992ac464 100644 --- a/reference/configuration/framework.rst +++ b/reference/configuration/framework.rst @@ -1110,7 +1110,7 @@ strict_requirements **type**: ``mixed`` **default**: ``true`` Determines the routing generator behavior. When generating a route that -has specific :doc:`requirements `, the generator +has specific :ref:`parameter requirements `, the generator can behave differently in case the used parameters do not meet these requirements. The value can be one of: diff --git a/reference/forms/types/options/method.rst.inc b/reference/forms/types/options/method.rst.inc index 0d86d1f47a0..b20fbc3f026 100644 --- a/reference/forms/types/options/method.rst.inc +++ b/reference/forms/types/options/method.rst.inc @@ -19,7 +19,7 @@ is used to decide whether to process the form submission in the When the method is PUT, PATCH, or DELETE, Symfony will automatically render a ``_method`` hidden field in your form. This is used to "fake" these HTTP methods, as they're not supported on standard browsers. This can - be useful when using :ref:`method routing requirements `. + be useful when :ref:`matching routes by HTTP method `. .. note:: diff --git a/routing.rst b/routing.rst index 36400f8609c..b5180bb4598 100644 --- a/routing.rst +++ b/routing.rst @@ -4,38 +4,31 @@ Routing ======= -Beautiful URLs are a must for any serious web application. This means leaving -behind ugly URLs like ``index.php?article_id=57`` in favor of something like -``/read/intro-to-symfony``. - -Having flexibility is even more important. What if you need to change the -URL of a page from ``/blog`` to ``/news``? How many links would you need to -hunt down and update to make the change? If you're using Symfony's router, -the change should be trivial. - -.. index:: - single: Routing; Basics +When your application receives a request, it executes a +:doc:`controller action ` to generate the response. The routing +configuration defines which action to run for each incoming URL. It also +provides other useful features, like generating SEO-friendly URLs (e.g. +``/read/intro-to-symfony`` instead of ``index.php?article_id=57``). .. _routing-creating-routes: Creating Routes --------------- -A *route* is a map from a URL path to attributes (i.e a controller). Suppose -you want one route that matches ``/blog`` exactly and another more dynamic -route that can match *any* URL like ``/blog/my-post`` or -``/blog/all-about-symfony``. - Routes can be configured in YAML, XML, PHP or using annotations. All formats -provide the same features and performance, so choose the one you prefer. If you -choose PHP annotations, run this command once in your application to add support -for them: +provide the same features and performance, so choose your favorite. +:doc:`Symfony recommends annotations ` +because it's convenient to put the route and controller in the +same place instead of dealing with multiple files. + +If you choose annotations, run this command once in your application to add +support for them: .. code-block:: terminal $ composer require annotations -Now you can configure the routes: +Suppose you want to define a route for the ``/blog`` URL in your application: .. configuration-block:: @@ -50,43 +43,25 @@ Now you can configure the routes: class BlogController extends AbstractController { /** - * Matches /blog exactly - * * @Route("/blog", name="blog_list") */ public function list() { // ... } - - /** - * Matches /blog/* - * but not /blog/slug/extra-part - * - * @Route("/blog/{slug}", name="blog_show") - */ - public function show($slug) - { - // $slug will equal the dynamic part of the URL - // e.g. at /blog/yay-routing, then $slug='yay-routing' - - // ... - } } .. code-block:: yaml # config/routes.yaml blog_list: - # Matches /blog exactly - path: /blog + path: /blog + # the controller value has the format 'controller_class::method_name' controller: App\Controller\BlogController::list - blog_show: - # Matches /blog/* - # but not /blog/slug/extra-part - path: /blog/{slug} - controller: App\Controller\BlogController::show + # 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 @@ -97,16 +72,13 @@ Now you can configure the routes: xsi:schemaLocation="http://symfony.com/schema/routing https://symfony.com/schema/routing/routing-1.0.xsd"> - - - - + + - - - - - + .. code-block:: php @@ -116,77 +88,151 @@ Now you can configure the routes: use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; return function (RoutingConfigurator $routes) { - // Matches /blog exactly $routes->add('blog_list', '/blog') + // the controller value has the format [controller_class, method_name] ->controller([BlogController::class, 'list']) - ; - // Matches /blog/* - // but not /blog/slug/extra-part - $routes->add('blog_show', '/blog/{slug}') - ->controller([BlogController::class, 'show']) + + // if the action is implemented as the __invoke() method of the + // controller class, you can skip the ', method_name]' part: + // ->controller([BlogController::class]) ; }; -Thanks to these two routes: +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. -* If the user goes to ``/blog``, the first route is matched and ``list()`` - is executed; +.. note:: -* If the user goes to ``/blog/*``, the second route is matched and ``show()`` - is executed. Because the route path is ``/blog/{slug}``, a ``$slug`` variable - is passed to ``show()`` matching that value. For example, if the user goes to - ``/blog/yay-routing``, then ``$slug`` will equal ``yay-routing``. + 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. -Whenever you have a ``{placeholder}`` in your route path, that portion becomes -a wildcard: it matches *any* value. Your controller can now *also* have an -argument called ``$placeholder`` (the wildcard and argument names *must* -match). +The route name (``blog_list``) is not important for now, but it will be essential later when +:ref:`generating URLs `. You only have to keep in mind +that each route name must be unique in the application. -.. caution:: +.. _routing-matching-http-methods: - However, the slash ``/`` is ignored by default in placeholder values because - the router uses it as separator between different placeholders. - To learn more about this, you can read - :ref:`routing/slash_in_parameter`. +Matching HTTP Methods +~~~~~~~~~~~~~~~~~~~~~ -Each route also has an internal name: ``blog_list`` and ``blog_show``. These can -be anything (as long as each is unique) and don't have any meaning yet. You'll -use them later to :ref:`generate URLs `. +By default, routes match any HTTP verb (``GET``, ``POST``, ``PUT``, etc.) +Use the ``methods`` option to restrict the verbs each route should respond to: -.. sidebar:: Routing in Other Formats +.. configuration-block:: - The ``@Route`` above each method is called an *annotation*. If you'd rather - configure your routes in YAML, XML or PHP, that's no problem! Create a new - routing file (e.g. ``routes.xml``) in the ``config/`` directory and Symfony - will automatically use it. + .. code-block:: php-annotations -.. _i18n-routing: + // src/Controller/BlogApiController.php + namespace App\Controller; -Localized Routing (i18n) ------------------------- + // ... + + class BlogApiController extends AbstractController + { + /** + * @Route("/api/posts/{id}", methods={"GET","HEAD"}) + */ + public function show(int $id) + { + // ... return a JSON response with the post + } + + /** + * @Route("/api/posts/{id}", methods={"PUT"}) + */ + public function edit(int $id) + { + // ... 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 + + + + + + + + + + + .. code-block:: php + + // config/routes.php + use App\Controller\BlogApiController; + use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; + + return function (RoutingConfigurator $routes) { + $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. ````). + If you create your forms with :doc:`Symfony Forms ` this is done + automatically for you. -Routes can be localized to provide unique paths per :doc:`locale `. -Symfony provides a handy way to declare localized routes without duplication. +Matching Expressions +~~~~~~~~~~~~~~~~~~~~ + +Use the ``condition`` option if you need some route to match based on some +arbitrary matching logic: .. configuration-block:: .. code-block:: php-annotations - // src/Controller/CompanyController.php + // src/Controller/DefaultController.php namespace App\Controller; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\Routing\Annotation\Route; - class CompanyController extends AbstractController + class DefaultController extends AbstractController { /** - * @Route({ - * "nl": "/over-ons", - * "en": "/about-us" - * }, name="about_us") + * @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 about() + public function contact() { // ... } @@ -195,11 +241,12 @@ Symfony provides a handy way to declare localized routes without duplication. .. code-block:: yaml # config/routes.yaml - about_us: - path: - nl: /over-ons - en: /about-us - controller: App\Controller\CompanyController::about + 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 config parameters: + # condition: "request.headers.get('User-Agent') matches '%app.allowed_browsers%'" .. code-block:: xml @@ -210,103 +257,191 @@ Symfony provides a handy way to declare localized routes without duplication. xsi:schemaLocation="http://symfony.com/schema/routing https://symfony.com/schema/routing/routing-1.0.xsd"> - - /over-ons - /about-us + + context.getMethod() in ['GET', 'HEAD'] and request.headers.get('User-Agent') matches '/firefox/i' + + .. code-block:: php // config/routes.php - use App\Controller\CompanyController; + use App\Controller\DefaultController; use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; return function (RoutingConfigurator $routes) { - $routes->add('about_us', [ - 'nl' => '/over-ons', - 'en' => '/about-us', - ]) - ->controller([CompanyController::class, 'about']) + $routes->add('contact', '') + ->controller([DefaultController::class, 'contact']) + ->condition('context.getMethod() in ["GET", "HEAD"] and request.headers.get("User-Agent") matches "/firefox/i"') + // expressions can also include config parameters: + // 'request.headers.get("User-Agent") matches "%app.allowed_browsers%"' ; }; -When a localized route is matched Symfony automatically knows which locale -should be used during the request. Defining routes this way also eliminated the -need for duplicate registration of routes which minimizes the risk for any bugs -caused by definition inconsistency. +The value of the ``condition`` option is any valid +:doc:`ExpressionLanguage expression ` +and can use and of these variables created by Symfony: -.. tip:: +``context`` + An instance of :class:`Symfony\\Component\\Routing\\RequestContext`, + which holds the most fundamental information about the route being matched. - If the application uses full language + territory locales (e.g. ``fr_FR``, - ``fr_BE``), you can use the language part only in your routes (e.g. ``fr``). - This prevents having to define multiple paths when you want to use the same - route path for locales that share the same language. +``request`` + The :ref:`Symfony Request ` object that + represents the current request. -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): +Behind the scenes, expressions are compiled down to raw PHP. Because of this, +using the ``condition`` key causes no extra overhead beyond the time it takes +for the underlying PHP to execute. + +.. caution:: + + Conditions are *not* taken into account when generating URLs (which is + explained later in this article). + +Debugging Routes +~~~~~~~~~~~~~~~~ + +As your application grows, you'll eventually have a *lot* of routes. Symfony +includes some commands to help you debug routing issues. First, the ``debug:router`` +command lists all your application routes in the same order in which Symfony +evaluates them: + +.. 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 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 + +Route Parameters +---------------- + +The previous examples defined routes where the URL never changes (e.g. ``/blog``). +However, it's common to define routes where some parts are variable. For example, +the URL to display some blog post will probably include the title or slug +(e.g. ``/blog/my-first-post`` or ``/blog/all-about-symfony``). + +In Symfony routes, variable parts are wrapped in ``{ ... }`` and they must have +a unique name. For example, the route to display the blog post contents is +defined as ``/blog/{slug}``: .. configuration-block:: + .. code-block:: php-annotations + + // src/Controller/BlogController.php + namespace App\Controller; + + use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; + use Symfony\Component\Routing\Annotation\Route; + + class BlogController extends AbstractController + { + // ... + + /** + * @Route("/blog/{slug}", name="blog_show") + */ + public function show(string $slug) + { + // $slug will equal the dynamic part of the URL + // e.g. at /blog/yay-routing, then $slug='yay-routing' + + // ... + } + } + .. code-block:: yaml - # config/routes/annotations.yaml - controllers: - resource: '../../src/Controller/' - type: annotation - prefix: - en: '' # don't prefix URLs for English, the default locale - nl: '/nl' + # config/routes.yaml + blog_show: + path: /blog/{slug} + controller: App\Controller\BlogController::show .. code-block:: xml - + - - - - /nl - + + .. code-block:: php - // config/routes/annotations.php + // config/routes.php + use App\Controller\BlogController; use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; return function (RoutingConfigurator $routes) { - $routes->import('../src/Controller/', 'annotation') - ->prefix([ - // don't prefix URLs for English, the default locale - 'en' => '', - 'nl' => '/nl' - ]) + $routes->add('blog_show', '/blog/{slug}') + ->controller([BlogController::class, 'show']) ; }; -.. _routing-requirements: +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. -Adding {wildcard} Requirements ------------------------------- +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}``). -Imagine the ``blog_list`` route will contain a paginated list of blog posts, with -URLs like ``/blog/2`` and ``/blog/3`` for pages 2 and 3. If you change the route's -path to ``/blog/{page}``, you'll have a problem: +.. _routing-requirements: -* blog_list: ``/blog/{page}`` will match ``/blog/*``; -* blog_show: ``/blog/{slug}`` will *also* match ``/blog/*``. +Parameters Validation +~~~~~~~~~~~~~~~~~~~~~ -When two routes match the same URL, the *first* route that's loaded wins. Unfortunately, -that means that ``/blog/yay-routing`` will match the ``blog_list``. No good! +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. -To fix this, add a *requirement* that the ``{page}`` wildcard can *only* match numbers -(digits): +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:: @@ -323,7 +458,7 @@ To fix this, add a *requirement* that the ``{page}`` wildcard can *only* match n /** * @Route("/blog/{page}", name="blog_list", requirements={"page"="\d+"}) */ - public function list($page) + public function list(int $page) { // ... } @@ -341,13 +476,14 @@ To fix this, add a *requirement* that the ``{page}`` wildcard can *only* match n # config/routes.yaml blog_list: - path: /blog/{page} + path: /blog/{page} controller: App\Controller\BlogController::list requirements: page: '\d+' blog_show: - # ... + path: /blog/{slug} + controller: App\Controller\BlogController::show .. code-block:: xml @@ -362,7 +498,9 @@ To fix this, add a *requirement* that the ``{page}`` wildcard can *only* match n \d+ - + + .. code-block:: php @@ -376,20 +514,45 @@ To fix this, add a *requirement* that the ``{page}`` wildcard can *only* match n ->controller([BlogController::class, 'list']) ->requirements(['page' => '\d+']) ; + + $routes->add('blog_show', '/blog/{slug}') + ->controller([BlogController::class, 'show']) + ; // ... }; -The ``\d+`` is a regular expression that matches a *digit* of any length. Now: +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/yay-routing`` ``blog_show`` ``$slug`` = ``yay-routing`` +``/blog/my-first-post`` ``blog_show`` ``$slug`` = ``my-first-post`` ======================== ============= =============================== -If you prefer, requirements can be inlined in each placeholder using the syntax -``{placeholder_name}``. This feature makes configuration more +.. tip:: + + Route requirements (and route paths too) can include + :ref:`container parameters `, which is useful to + define complex regular expressions once and reuse them in multiple routes. + +.. tip:: + + Parameters also support `PCRE Unicode properties`_, which are escape + sequences that match generic character types. For example, ``\p{Lu}`` + matches any uppercase character in any language, ``\p{Greek}`` matches any + Greek character, etc. + +.. note:: + + When using regular expressions in route parameters, you can set the ``utf8`` + route option to ``true`` to make any ``.`` character match any UTF-8 + characters instead of just a single byte. + +If you prefer, requirements can be inlined in each parameter using the syntax +``{parameter_name}``. This feature makes configuration more concise, but it can decrease route readability when requirements are complex: .. configuration-block:: @@ -407,7 +570,7 @@ concise, but it can decrease route readability when requirements are complex: /** * @Route("/blog/{page<\d+>}", name="blog_list") */ - public function list($page) + public function list(int $page) { // ... } @@ -417,7 +580,7 @@ concise, but it can decrease route readability when requirements are complex: # config/routes.yaml blog_list: - path: /blog/{page<\d+>} + path: /blog/{page<\d+>} controller: App\Controller\BlogController::list .. code-block:: xml @@ -429,7 +592,8 @@ concise, but it can decrease route readability when requirements are complex: xsi:schemaLocation="http://symfony.com/schema/routing https://symfony.com/schema/routing/routing-1.0.xsd"> - + @@ -447,19 +611,17 @@ concise, but it can decrease route readability when requirements are complex: // ... }; -To learn about other route requirements - like HTTP method, hostname and dynamic -expressions - see :doc:`/routing/requirements`. +Optional Parameters +~~~~~~~~~~~~~~~~~~~ -Giving {placeholders} a Default Value -------------------------------------- - -In the previous example, the ``blog_list`` has a path of ``/blog/{page}``. If -the user visits ``/blog/1``, it will match. But if they visit ``/blog``, it -will **not** match. As soon as you add a ``{placeholder}`` to a route, it -*must* have a value. +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. -So how can you make ``blog_list`` once again match when the user visits -``/blog``? By adding a *default* value: +You can make ``blog_list`` once again match when the user visits ``/blog`` by +adding a default value for the ``{page}`` parameter. When using annotations, +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:: @@ -476,7 +638,7 @@ So how can you make ``blog_list`` once again match when the user visits /** * @Route("/blog/{page}", name="blog_list", requirements={"page"="\d+"}) */ - public function list($page = 1) + public function list(int $page = 1) { // ... } @@ -486,7 +648,7 @@ So how can you make ``blog_list`` once again match when the user visits # config/routes.yaml blog_list: - path: /blog/{page} + path: /blog/{page} controller: App\Controller\BlogController::list defaults: page: 1 @@ -531,9 +693,21 @@ So how can you make ``blog_list`` once again match when the user visits Now, when the user visits ``/blog``, the ``blog_list`` route will match and ``$page`` will default to a value of ``1``. +.. caution:: + + You can have more than one optional parameter (e.g. ``/blog/{slug}/{page}``), + but everything after an optional parameter must be optional. For example, + ``/{page}/blog`` is a valid path, but ``page`` will always be required + (i.e. ``/blog`` will not match this route). + +.. note:: + + Routes with optional parameters at the end will not match on requests + with a trailing slash (i.e. ``/blog/`` will not match, ``/blog`` will match). + 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 placeholder name: ``/blog/{!page}`` +previous example) add the ``!`` character before the parameter name: ``/blog/{!page}`` .. versionadded:: 4.3 @@ -541,9 +715,9 @@ previous example) add the ``!`` character before the placeholder name: ``/blog/{ introduced in Symfony 4.3. As it happens with requirements, default values can also be inlined in each -placeholder using the syntax ``{placeholder_name?default_value}``. This feature +parameter using the syntax ``{parameter_name?default_value}``. This feature is compatible with inlined requirements, so you can inline both in a single -placeholder: +parameter: .. configuration-block:: @@ -560,7 +734,7 @@ placeholder: /** * @Route("/blog/{page<\d+>?1}", name="blog_list") */ - public function list($page) + public function list(int $page) { // ... } @@ -570,7 +744,7 @@ placeholder: # config/routes.yaml blog_list: - path: /blog/{page<\d+>?1} + path: /blog/{page<\d+>?1} controller: App\Controller\BlogController::list .. code-block:: xml @@ -582,7 +756,8 @@ placeholder: xsi:schemaLocation="http://symfony.com/schema/routing https://symfony.com/schema/routing/routing-1.0.xsd"> - + @@ -601,35 +776,86 @@ placeholder: .. tip:: - To give a ``null`` default value to any placeholder, add nothing after the + To give a ``null`` default value to any parameter, add nothing after the ``?`` character (e.g. ``/blog/{page?}``). -Listing all of your Routes --------------------------- +Parameter Conversion +~~~~~~~~~~~~~~~~~~~~ -As your app grows, you'll eventually have a *lot* of routes! To see them all, run: +A common routing need is to convert the value stored in some parameter (e.g. an +integer acting as the user ID) into another value (e.g. the object that +represents the user). This feature is called "param converter" and is only +available when using annotations to define routes. + +In case you didn't run this command before, run it now to add support for +annotations and "param converters": .. code-block:: terminal - $ php bin/console debug:router + $ composer require annotations - ------------------------------ -------- ------------------------------------- - Name Method Path - ------------------------------ -------- ------------------------------------- - app_lucky_number ANY /lucky/number/{max} - ... - ------------------------------ -------- ------------------------------------- +Now, keep the previous route configuration, but change the arguments of the +controller action. Instead of ``string $slug``, add ``BlogPost $post``:: -.. index:: - single: Routing; Advanced example - single: Routing; _format parameter + // src/Controller/BlogController.php + namespace App\Controller; -.. _advanced-routing-example: + use App\Entity\BlogPost; + use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; + use Symfony\Component\Routing\Annotation\Route; -Advanced Routing Example ------------------------- + class BlogController extends AbstractController + { + // ... + + /** + * @Route("/blog/{slug}", name="blog_show") + */ + public function show(BlogPost $post) + { + // $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. + +Read the `full param converter documentation`_ to learn about the converters +provided by Symfony and how to configure them. + +Special Parameters +~~~~~~~~~~~~~~~~~~ + +In addition to your own parameters, routes can include any of the following +special parameters created by Symfony: + +``_controller`` + This parameter is used to determine which controller and action is executed + when the route is matched. -With all of this in mind, check out this advanced example: +.. _routing-format-parameter: + +``_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. + +.. _routing-locale-parameter: + +``_locale`` + Used to set the :ref:`locale ` on the request. + +You can include these attributes (except ``_fragment``) both in individual routes +and in route imports. Symfony defines some special attributes with the same name +(except for the leading underscore) so you can define them easier: .. configuration-block:: @@ -642,19 +868,16 @@ With all of this in mind, check out this advanced example: { /** * @Route( - * "/articles/{_locale}/{year}/{slug}.{_format}", - * defaults={ - * "_locale": "en", - * "_format": "html" - * }, + * "/articles/{_locale}/search.{_format}", + * locale="en", + * format="html", * requirements={ * "_locale": "en|fr", - * "_format": "html|rss", - * "year": "\d+" + * "_format": "html|xml", * } * ) */ - public function show($_locale, $year, $slug) + public function search() { } } @@ -662,16 +885,14 @@ With all of this in mind, check out this advanced example: .. code-block:: yaml # config/routes.yaml - article_show: - path: /articles/{_locale}/{year}/{slug}.{_format} - controller: App\Controller\ArticleController::show - defaults: - _locale: en - _format: html - requirements: - _locale: en|fr - _format: html|rss - year: \d+ + 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 @@ -682,15 +903,14 @@ With all of this in mind, check out this advanced example: xsi:schemaLocation="http://symfony.com/schema/routing https://symfony.com/schema/routing/routing-1.0.xsd"> - + - en - html en|fr html|rss - \d+ @@ -698,310 +918,1308 @@ With all of this in mind, check out this advanced example: .. code-block:: php // config/routes.php + namespace Symfony\Component\Routing\Loader\Configurator; + use App\Controller\ArticleController; - use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; return function (RoutingConfigurator $routes) { - $routes->add('article_show', '/articles/{_locale}/{year}/{slug}.{_format}') - ->controller([ArticleController::class, 'show']) - ->defaults([ - '_locale' => 'en', - '_format' => 'html', - ]) + $routes->add('article_show', '/articles/{_locale}/search.{_format}') + ->controller([ArticleController::class, 'search']) + ->locale('en') + ->format('html') ->requirements([ '_locale' => 'en|fr', '_format' => 'html|rss', - 'year' => '\d+', ]) ; }; -As you've seen, this route will only match if the ``{_locale}`` portion of -the URL is either ``en`` or ``fr`` and if the ``{year}`` is a number. This -route also shows how you can use a dot between placeholders instead of -a slash. URLs matching this route might look like: - -* ``/articles/en/2010/my-post`` -* ``/articles/fr/2010/my-post.rss`` -* ``/articles/en/2013/my-latest-post.html`` +.. versionadded:: 4.3 + + The special attributes were introduced in Symfony 4.3. + +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-annotations + + use Symfony\Component\Routing\Annotation\Route; + + class BlogController + { + /** + * @Route("/blog/{page}", name="blog_index", defaults={"page": 1, "title": "Hello world!"}) + */ + public function index(int $page, string $title) + { + // ... + } + } + + .. 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 + + + + + + + 1 + Hello world! + + + + .. code-block:: php + + // config/routes.php + use App\Controller\BlogController; + use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; + + return function (RoutingConfigurator $routes) { + $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-annotations + + use Symfony\Component\Routing\Annotation\Route; + + class DefaultController + { + /** + * @Route("/share/{token}", name="share", requirements={"token"=".+"}) + */ + public function share($token) + { + // ... + } + } + + .. code-block:: yaml + + # config/routes.yaml + share: + path: /share/{token} + controller: App\Controller\DefaultController::share + requirements: + token: .+ + + .. code-block:: xml + + + + + + + .+ + + + + .. code-block:: php + + // config/routes.php + use App\Controller\DefaultController; + use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; + + return function (RoutingConfigurator $routes) { + $routes->add('share', '/share/{token}') + ->controller([DefaultController::class, 'share']) + ->requirements([ + 'token' => '.+', + ]) + ; + }; + +.. note:: + + If the route defines several parameter and you apply this permissive + regular expression to all of them, the results won't be the expected. For + example, if the route definition is ``/share/{path}/{token}`` and both + ``path`` and ``token`` accept ``/``, then ``path`` will contain its contents + and the token, and ``token`` will be empty. + +.. 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-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 annotations, put the common configuration in the +``@Route`` annotation of the controller class. In other routing formats, define +the common configuration using options when importing the routes. + +.. configuration-block:: + + .. code-block:: php-annotations + + use Symfony\Component\Routing\Annotation\Route; + + /** + * @Route("/blog", requirements={"locale": "en|es|fr"}, name="blog_") + */ + class BlogController + { + /** + * @Route("/{_locale}", name="index") + */ + public function index() + { + // ... + } + + /** + * @Route("/{_locale}/posts/{slug}", name="post") + */ + public function show(Post $post) + { + // ... + } + } + + .. code-block:: yaml + + # config/routes/annotations.yaml + controllers: + resource: '../src/Controller/' + type: annotation + # 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 + + .. code-block:: xml + + + + + + + + + en|es|fr + + + + + + + + + .. code-block:: php + + // config/routes/annotations.php + use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; + + return function (RoutingConfigurator $routes) { + $routes->import('../src/Controller/', 'annotation') + // 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']) + ; + }; + +In this example, the route of the ``index()`` action will be called ``blog_index`` +and its URL will be ``/blog/``. The route of the ``show()`` action will be called +``blog_post`` and its URL will be ``/blog/{_locale}/posts/{slug}``. Both routes +will also validate that the ``_locale`` parameter matches the regular expression +defined in the class annotation. + +.. seealso:: + + Symfony can :doc:`import routes from different sources ` + and you can even create your own route loader. + +Getting the Route Name and Parameters +------------------------------------- + +The ``Request`` object created by Symfony stores all the route configuration +(such as the name and parameters) in the "request attributes". You can get this +information in a controller via the ``Request`` object:: + + // src/Controller/BlogController.php + namespace App\Controller; + + use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; + use Symfony\Component\HttpFoundation\Request; + use Symfony\Component\Routing\Annotation\Route; + + class BlogController extends AbstractController + { + /** + * @Route("/blog", name="blog_list") + */ + public function list(Request $request) + { + // ... + + $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(); + } + } + +You can get this information in services too injecting the ``request_stack`` +service to :doc:`get the Request object in a service `. +In Twig templates, use the :ref:`global app object ` +to get the request and its attributes: + +.. code-block:: twig + + {% set route_name = app.request.attributes('_route') %} + {% set route_parameters = app.request.attributes('_route_params') %} + + {# use this to get all the available attributes (not only routing ones) #} + {% set all_attributes = app.request.attributes.all %} + +Special Routes +-------------- + +Symfony defines some special controllers to render templates and redirect to +other routes from the route configuration so you don't have to create a +controller action. + +Rendering Templates +~~~~~~~~~~~~~~~~~~~ + +Use the ``TemplateController`` to render the template whose path is defined in +the ``template`` option: + +.. configuration-block:: + + .. code-block:: yaml + + # config/routes.yaml + about_us: + path: /site/about-us + controller: Symfony\Bundle\FrameworkBundle\Controller\TemplateController::templateAction + defaults: + template: 'static_pages/about_us.html.twig' + + # optionally you can define some arguments passed to the template + site_name: 'ACME' + theme: 'dark' + + .. code-block:: xml + + + + + + + static_pages/about_us.html.twig + + + ACME + dark + + + + .. code-block:: php + + // config/routes.php + use App\Controller\DefaultController; + use Symfony\Bundle\FrameworkBundle\Controller\TemplateController; + use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; + + return function (RoutingConfigurator $routes) { + $routes->add('about_us', '/site/about-us') + ->controller([TemplateController::class, 'templateAction']) + ->defaults([ + 'template' => 'static_pages/about_us.html.twig', + // optionally you can define some arguments passed to the template + 'site_name' => 'ACME', + 'theme' => 'dark', + ]) + ; + }; + +Redirecting to URLs and Routes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Use the ``RedirectController`` to redirect to other routes (``redirectAction``) +and URLs (``urlRedirectAction``): + +.. configuration-block:: + + .. code-block:: yaml + + # config/routes.yaml + doc_shortcut: + path: /doc + controller: Symfony\Bundle\FrameworkBundle\Controller\RedirectController::redirectAction + 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 + + legacy_doc: + path: /legacy/doc + controller: Symfony\Bundle\FrameworkBundle\Controller\RedirectController::urlRedirectAction + defaults: + # this value can be an absolute path or an absolute URL + path: 'https://legacy.example.com/doc' + permanent: true + + .. code-block:: xml + + + + + + + doc_page + + index + current + + true + + true + + true + + + + + https://legacy.example.com/doc + + true + + + + .. code-block:: php + + // config/routes.php + use App\Controller\DefaultController; + use Symfony\Bundle\FrameworkBundle\Controller\RedirectController; + use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; + + return function (RoutingConfigurator $routes) { + $routes->add('doc_shortcut', '/doc') + ->controller([RedirectController::class, 'redirectAction']) + ->defaults([ + 'route' => 'doc_page', + // optionally you can define some arguments passed to the template + '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, 'urlRedirectAction']) + ->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 ` + +.. _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) +========== ======================================== ========================================== + +.. note:: + + If your application defines different routes for each path (``/foo`` and + ``/foo/``) this automatic redirection doesn't take place and the right + route is always matched. + +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-annotations + + // src/Controller/MainController.php + namespace App\Controller; + + use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; + use Symfony\Component\Routing\Annotation\Route; + + class MainController extends AbstractController + { + /** + * @Route("/", name="mobile_homepage", host="m.example.com") + */ + public function mobileHomepage() + { + // ... + } + + /** + * @Route("/", name="homepage") + */ + public function homepage() + { + // ... + } + } + + .. 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 + + + + + + + + + + + .. code-block:: php + + // config/routes.php + use App\Controller\MainController; + use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; + + return function (RoutingConfigurator $routes) { + $routes->add('mobile_homepage', '/') + ->controller([MainController::class, 'mobileHomepage']) + ->host('m.example.com') + ; + $routes->add('homepage', '/') + ->controller([MainController::class, 'homepage']) + ; + }; + + return $routes; + +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-annotations + + // src/Controller/MainController.php + namespace App\Controller; + + use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; + use Symfony\Component\Routing\Annotation\Route; + + class MainController extends AbstractController + { + /** + * @Route( + * "/", + * name="mobile_homepage", + * host="{subdomain}.example.com", + * defaults={"subdomain"="m"}, + * requirements={"subdomain"="m|mobile"} + * ) + */ + public function mobileHomepage() + { + // ... + } + + /** + * @Route("/", name="homepage") + */ + public function homepage() + { + // ... + } + } + + .. 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 + + + + + + + m + m|mobile + + + + + + .. code-block:: php + + // config/routes.php + use App\Controller\MainController; + use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; + + return function (RoutingConfigurator $routes) { + $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 ``domain`` parameter defines a default value because +otherwise you need to include a domain value each time you generate a URL using +these routes. + +.. tip:: + + You can also set the ``host`` option when :ref:`importing routes ` + to make all of them require that host name. + +.. note:: + + When using sub-domain routing, you must set the ``Host`` HTTP headers in + :doc:`functional tests ` or routes won't match:: + + $crawler = $client->request( + 'GET', + '/', + [], + [], + ['HTTP_HOST' => 'm.example.com'] + // or get the value from some container parameter: + // ['HTTP_HOST' => 'm.' . $client->getContainer()->getParameter('domain')] + ); + +.. _i18n-routing: + +Localized Routes (i18n) +----------------------- + +If your application is translated into multiple languages, each route can define +a different URL per each :doc:`translation locale `. This +avoids the need for duplicating routes, which also reduces the potential bugs: + +.. configuration-block:: + + .. code-block:: php-annotations + + // src/Controller/CompanyController.php + namespace App\Controller; + + use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; + use Symfony\Component\Routing\Annotation\Route; + + class CompanyController extends AbstractController + { + /** + * @Route({ + * "en": "/about-us", + * "nl": "/over-ons" + * }, name="about_us") + */ + public function about() + { + // ... + } + } + + .. code-block:: yaml + + # config/routes.yaml + about_us: + path: + en: /about-us + nl: /over-ons + controller: App\Controller\CompanyController::about + + .. code-block:: xml + + + + + + + /about-us + /over-ons + + + + .. code-block:: php + + // config/routes.php + use App\Controller\CompanyController; + use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; + + return function (RoutingConfigurator $routes) { + $routes->add('about_us', [ + 'en' => '/about-us', + 'nl' => '/over-ons', + ]) + ->controller([CompanyController::class, 'about']) + ; + }; + +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/annotations.yaml + controllers: + resource: '../src/Controller/' + type: annotation + prefix: + en: '' # don't prefix URLs for English, the default locale + nl: '/nl' + + .. code-block:: xml + + + + + + + + + /nl + + + + .. code-block:: php + + // config/routes/annotations.php + use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; + + return function (RoutingConfigurator $routes) { + $routes->import('../src/Controller/', 'annotation') + ->prefix([ + // don't prefix URLs for English, the default locale + 'en' => '', + 'nl' => '/nl' + ]) + ; + }; + +.. _routing-generating-urls: -.. _routing-format-param: - -.. sidebar:: The Special ``_format`` Routing Parameter +Generating URLs +--------------- - This example also highlights the special ``_format`` routing parameter. - When using this parameter, the matched value becomes the "request format" - of the ``Request`` object. +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 ```` +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. - Ultimately, the request format is used for such things as setting the - ``Content-Type`` of the response (e.g. a ``json`` request format translates - into a ``Content-Type`` of ``application/json``). +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``). -.. note:: +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. - Sometimes you want to make certain parts of your routes globally configurable. - Symfony provides you with a way to do this by leveraging service container - parameters. Read more about this in ":doc:`/routing/service_container_parameters`". +Generating URLs in Controllers +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Special Routing Parameters -~~~~~~~~~~~~~~~~~~~~~~~~~~ +If your controller extends from the :ref:`AbstractController `, +use the ``generateUrl()`` helper:: -As you've seen, each routing parameter or default value is eventually available -as an argument in the controller method. Additionally, there are four parameters -that are special: each adds a unique piece of functionality inside your application: + // src/Controller/BlogController.php + namespace App\Controller; -``_controller`` - As you've seen, this parameter is used to determine which controller is - executed when the route is matched. + use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; + use Symfony\Component\Routing\Annotation\Route; + use Symfony\Component\Routing\Generator\UrlGeneratorInterface; -``_format`` - Used to set the request format (:ref:`read more `). + class BlogController extends AbstractController + { + /** + * @Route("/blog", name="blog_list") + */ + public function list() + { + // ... -``_fragment`` - Used to set the fragment identifier, the optional last part of a URL that - starts with a ``#`` character and is used to identify a portion of a document. + // generate a URL with no route arguments + $signUpPage = $this->generateUrl('sign_up'); -``_locale`` - Used to set the locale on the request (:ref:`read more `). + // generate a URL with route arguments + $userProfilePage = $this->generateUrl('user_profile', [ + 'username' => $user->getUsername(), + ]); -You can also use special attributes to configure them (except ``_fragment``): + // 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); -.. configuration-block:: + // 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']); + } + } - .. code-block:: php-annotations +.. note:: - // src/Controller/ArticleController.php + 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::: - // ... - class ArticleController extends AbstractController - { - /** - * @Route( - * "/articles/{_locale}/search.{_format}", - * locale="en", - * format="html", - * requirements={ - * "_locale": "en|fr", - * "_format": "html|xml", - * } - * ) - */ - public function search() - { - } - } + $this->generateUrl('blog', ['page' => 2, 'category' => 'Symfony']); + // the 'blog' route only defines the 'page' parameter; the generated URL is: + // /blog/2?category=Symfony - .. code-block:: yaml +If your controller does not extend from ``AbstractController``, you'll need to +:ref:`fetch services in your controller ` and +follow the instructions of the next section. - # 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 +.. _routing-generating-urls-in-services: - .. code-block:: xml +Generating URLs in Services +~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - - +Inject the ``router`` Symfony service into your own services and use its +``generate()`` method. When using :doc:`service 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 + use Symfony\Component\Routing\Generator\UrlGeneratorInterface; - en|fr - html|rss + class SomeService + { + private $router; - - + public function __construct(UrlGeneratorInterface $router) + { + $this->router = $router; + } - .. code-block:: php + public function someMethod() + { + // ... - // config/routes.php - namespace Symfony\Component\Routing\Loader\Configurator; + // generate a URL with no route arguments + $signUpPage = $this->router->generate('sign_up'); - use App\Controller\ArticleController; + // generate a URL with route arguments + $userProfilePage = $this->router->generate('user_profile', [ + 'username' => $user->getUsername(), + ]); - return function (RoutingConfigurator $routes) { - $routes->add('article_show', '/articles/{_locale}/search.{_format}') - ->controller([ArticleController::class, 'search']) - ->locale('en') - ->format('html') - ->requirements([ - '_locale' => 'en|fr', - '_format' => 'html|rss', - ]) - ; - }; + // generated URLs are "absolute paths" by default. Pass a third optional + // argument to generate different URLs (e.g. an "absolute URL") + $signUpPage = $this->router->generate('sign_up', [], UrlGeneratorInterface::ABSOLUTE_URL); -These attributes can also be used for route imports. + // 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->router->generate('sign_up', ['_locale' => 'nl']); + } + } -.. versionadded:: 4.3 +Generating URLs in Templates +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - The special attributes were introduced in Symfony 4.3. +The Twig template language used in :doc:`Symfony templates ` +provides some functions to generate both relative and absolute URLs: -.. _routing-trailing-slash-redirection: +.. code-block:: twig -Redirecting URLs with Trailing Slashes -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + {# generates relative URLs #} + Sign up + + View your profile + + + Ver mi perfil + -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. + {# generates absolute URLs #} + Sign up + + View your profile + + + Ver mi perfil + -Symfony follows this logic to redirect between URLs with and without trailing -slashes (but only for ``GET`` and ``HEAD`` requests): +Generating URLs in JavaScript +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -========== ======================================== ========================================== -Route path If the requested URL is ``/foo`` If the requested URL is ``/foo/`` ----------- ---------------------------------------- ------------------------------------------ -``/foo`` It matches (``200`` status response) It makes a ``301`` redirect to ``/foo`` -``/foo/`` It makes a ``301`` redirect to ``/foo/`` It matches (``200`` status response) -========== ======================================== ========================================== +If your JavaScript code is included in a Twig template, you can use the same +``path()`` and ``url()`` functions to generate the URLs and store them in +JavaScript variables. The ``escape()`` function is needed to escape any +non-JavaScript-safe values: -.. note:: +.. code-block:: html+twig - If your application defines different routes for each path (``/foo`` and - ``/foo/``) this automatic redirection doesn't take place and the right - route is always matched. + -.. index:: - single: Routing; Controllers - single: Controller; String naming format +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`_. -.. _controller-string-syntax: +Generating URLs in Commands +~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Controller Naming Pattern -------------------------- +Generating URLs in commands works the same as +:ref:`generating URLs in services `. The +only difference is that commands are not executed in the HTTP context, so they +don't have access to HTTP requests. In practice, this means that if you generate +absolute URLs, you'll get ``http://localhost/`` as the host name instead of your +real host name. -The ``controller`` value in your routes has the format ``CONTROLLER_CLASS::METHOD``. +The solution is to configure "request context" used by commands when they +generate URLs. This context can be configured globally for all commands: -.. tip:: +.. configuration-block:: - To refer to an action that is implemented as the ``__invoke()`` method of a controller class, - you do not have to pass the method name, you can also use the fully qualified class name (e.g. - ``App\Controller\BlogController``). + .. code-block:: yaml -.. index:: - single: Routing; Generating URLs + # config/services.yaml + parameters: + router.request_context.host: 'example.org' + router.request_context.base_url: 'my/path' + asset.request_context.base_path: '%router.request_context.base_url%' -.. _routing-generate: + .. code-block:: xml -Generating URLs ---------------- + + + -The routing system can also generate URLs. In reality, routing is a bidirectional -system: mapping the URL to a controller and also a route back to a URL. + + example.org + my/path + %router.request_context.base_url% + -To generate a URL, you need to specify the name of the route (e.g. ``blog_show``) -and any wildcards (e.g. ``slug = my-blog-post``) used in the path for that -route. With this information, an URL can be generated in a controller:: + - class BlogController extends AbstractController - { - public function show($slug) - { - // ... + .. code-block:: php - // /blog/my-blog-post - $url = $this->generateUrl( - 'blog_show', - ['slug' => 'my-blog-post'] - ); - } - } + // config/services.php + $container->setParameter('router.request_context.host', 'example.org'); + $container->setParameter('router.request_context.base_url', 'my/path'); + $container->setParameter('asset.request_context.base_path', $container->getParameter('router.request_context.base_url')); -If you need to generate a URL from a service, type-hint the :class:`Symfony\\Component\\Routing\\Generator\\UrlGeneratorInterface` -service:: +This information can be configured per command too:: - // src/Service/SomeService.php + // src/Command/SomeCommand.php use Symfony\Component\Routing\Generator\UrlGeneratorInterface; + use Symfony\Component\Routing\RouterInterface; + // ... - class SomeService + class SomeCommand extends Command { private $router; - public function __construct(UrlGeneratorInterface $router) + public function __construct(RouterInterface $router) { + parent::__construct(); + $this->router = $router; } - public function someMethod() + protected function execute(InputInterface $input, OutputInterface $output) { - $url = $this->router->generate( - 'blog_show', - ['slug' => 'my-blog-post'] - ); + // these values override any global configuration + $context = $this->router->getContext(); + $context->setHost('example.com'); + $context->setBaseUrl('my/path'); + + // generate a URL with no route arguments + $signUpPage = $this->router->generate('sign_up'); + + // generate a URL with route arguments + $userProfilePage = $this->router->generate('user_profile', [ + 'username' => $user->getUsername(), + ]); + + // generated URLs are "absolute paths" by default. Pass a third optional + // argument to generate different URLs (e.g. an "absolute URL") + $signUpPage = $this->router->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->router->generate('sign_up', ['_locale' => 'nl']); + // ... } } -.. index:: - single: Routing; Generating URLs in a template +Checking if a Route Exists +~~~~~~~~~~~~~~~~~~~~~~~~~~ -Generating URLs with Query Strings -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +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. -The ``generate()`` method takes an array of wildcard values to generate the URI. -But if you pass extra ones, they will be added to the URI as a query string:: +Instead, try to generate the URL and catch the +:class:`Symfony\\Component\\Routing\\Exception\\RouteNotFoundException` thrown +when the route doesn't exist:: - $this->router->generate('blog', [ - 'page' => 2, - 'category' => 'Symfony', - ]); - // /blog/2?category=Symfony + use Symfony\Component\Routing\Exception\RouteNotFoundException; -Generating Localized URLs -~~~~~~~~~~~~~~~~~~~~~~~~~ + // ... -When a route is localized, Symfony uses by default the current request locale to -generate the URL. In order to generate the URL for a different locale you must -pass the ``_locale`` in the parameters array:: + try { + $url = $this->router->generate($routeName, $routeParameters); + } catch (RouteNotFoundException $e) { + // the route is not defined... + } - $this->router->generate('about_us', [ - '_locale' => 'nl', - ]); - // generates: /over-ons +.. _routing-force-https: -Generating URLs from a Template +Forcing HTTPS on Generated URLs ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -To generate URLs inside Twig, see the templating article: :ref:`templating-pages`. -If you also need to generate URLs in JavaScript, see :doc:`/routing/generate_url_javascript`. +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: -.. index:: - single: Routing; Absolute URLs +.. configuration-block:: -Generating Absolute URLs -~~~~~~~~~~~~~~~~~~~~~~~~ + .. code-block:: yaml -By default, the router will generate relative URLs (e.g. ``/blog``). From -a controller, pass ``UrlGeneratorInterface::ABSOLUTE_URL`` to the third argument of the ``generateUrl()`` -method:: + # config/services.yaml + parameters: + router.request_context.scheme: 'https' + asset.request_context.secure: true - use Symfony\Component\Routing\Generator\UrlGeneratorInterface; + .. code-block:: xml + + + + + + + https + true + + + + + .. code-block:: php + + // config/services.php + $container->setParameter('router.request_context.scheme', 'https'); + $container->setParameter('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-annotations + + // src/Controller/MainController.php + namespace App\Controller; + + use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; + use Symfony\Component\Routing\Annotation\Route; + + class SecurityController extends AbstractController + { + /** + * @Route("/login", name="login", schemes={"https"}) + */ + public function login() + { + // ... + } + } + + .. code-block:: yaml + + # config/routes.yaml + login: + path: /login + controller: App\Controller\SeurityController::login + schemes: [https] + + .. code-block:: xml + + + + + + + + + + .. code-block:: php + + // config/routes.php + use App\Controller\SecurityController; + use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; + + return function (RoutingConfigurator $routes) { + $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 annotations: + +.. configuration-block:: - $this->generateUrl('blog_show', ['slug' => 'my-blog-post'], UrlGeneratorInterface::ABSOLUTE_URL); - // http://www.example.com/blog/my-blog-post + .. code-block:: yaml + + # config/routes/annotations.yaml + controllers: + resource: '../src/Controller/' + type: annotation + defaults: + schemes: [https] + + .. code-block:: xml + + + + + + + HTTPS + + + + .. code-block:: php + + // config/routes/annotations.php + use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; + + return function (RoutingConfigurator $routes) { + $routes->import('../src/Controller/', 'annotation') + ->schemes(['https']) + ; + }; .. note:: - The host that's used when generating an absolute URL is automatically - detected using the current ``Request`` object. When generating absolute - URLs from outside the web context (for instance in a console command) this - doesn't work. See :doc:`/console/request_context` to learn how to - solve this problem. + The Security component provides + :doc:`another way to enforce HTTP or HTTPS ` + via the ``requires_channel`` setting. Troubleshooting --------------- @@ -1018,27 +2236,22 @@ This happens when your controller method has an argument (e.g. ``$slug``):: // ... } -But your route path does *not* have a ``{slug}`` wildcard (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``). +But your route path does *not* have a ``{slug}`` parameter (e.g. it is +``/blog/show``). Add a ``{slug}`` to your route path: ``/blog/show/{slug}`` or +give the argument a default value (i.e. ``$slug = null``). Some mandatory parameters are missing ("slug") to generate a URL for route "blog_show". -This means that you're trying to generate a URL to the ``blog_show`` route but you -are *not* passing a ``slug`` value (which is required, because it has a ``{slug}``) -wildcard in the route path. To fix this, pass a ``slug`` value when generating the -route:: +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 - // {{ path('blog_show', {'slug': 'slug-value'}) }} - -Keep Going! ------------ - -Routing, check! Now, uncover the power of :doc:`controllers `. + // {{ path('blog_show', {slug: 'slug-value'}) }} Learn more about Routing ------------------------ @@ -1053,3 +2266,8 @@ Learn more about Routing :glob: routing/* + +.. _`PHP regular expressions`: https://www.php.net/manual/en/book.pcre.php +.. _`PCRE Unicode properties`: http://php.net/manual/en/regexp.reference.unicode.php +.. _`full param converter documentation`: https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/converters.html +.. _`FOSJsRoutingBundle`: https://github.com/FriendsOfSymfony/FOSJsRoutingBundle diff --git a/routing/conditions.rst b/routing/conditions.rst deleted file mode 100644 index 48b00f8b55f..00000000000 --- a/routing/conditions.rst +++ /dev/null @@ -1,112 +0,0 @@ -.. index:: - single: Routing; Conditions - -How to Restrict Route Matching through Conditions -================================================= - -A route can be made to match only certain routing placeholders (via regular -expressions), HTTP methods, or host names. If you need more flexibility to -define arbitrary matching logic, use the ``condition`` routing setting: - -.. configuration-block:: - - .. code-block:: php-annotations - - // src/Controller/DefaultController.php - namespace App\Controller; - - use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; - use Symfony\Component\Routing\Annotation\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() - { - // ... - } - } - - .. 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 config parameters - # condition: "request.headers.get('User-Agent') matches '%app.allowed_browsers%'" - - .. code-block:: xml - - - - - - - context.getMethod() in ['GET', 'HEAD'] and request.headers.get('User-Agent') matches '/firefox/i' - - - - - - .. code-block:: php - - // config/routes.php - use App\Controller\DefaultController; - use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; - - return function (RoutingConfigurator $routes) { - $routes->add('contact', '') - ->controller([DefaultController::class, 'contact']) - ->condition('context.getMethod() in ["GET", "HEAD"] and request.headers.get("User-Agent") matches "/firefox/i"') - // expressions can also include config parameters - // 'request.headers.get("User-Agent") matches "%app.allowed_browsers%"' - ; - }; - -The ``condition`` is an expression, and you can learn more about its syntax -here: :doc:`/components/expression_language/syntax`. With this, the route -won't match unless the HTTP method is either GET or HEAD *and* if the ``User-Agent`` -header matches ``firefox``. - -You can do any complex logic you need in the expression by leveraging two -variables that are passed into the expression: - -``context`` - An instance of :class:`Symfony\\Component\\Routing\\RequestContext`, - which holds the most fundamental information about the route being matched. -``request`` - The Symfony :class:`Symfony\\Component\\HttpFoundation\\Request` object - (see :ref:`component-http-foundation-request`). - -.. caution:: - - Conditions are *not* taken into account when generating a URL. - -.. sidebar:: Expressions are Compiled to PHP - - Behind the scenes, expressions are compiled down to raw PHP. Our example - would generate the following PHP in the cache directory:: - - if (rtrim($pathInfo, '/contact') === '' && ( - in_array($context->getMethod(), [0 => "GET", 1 => "HEAD"]) - && preg_match("/firefox/i", $request->headers->get("User-Agent")) - )) { - // ... - } - - Because of this, using the ``condition`` key causes no extra overhead - beyond the time it takes for the underlying PHP to execute. diff --git a/routing/custom_route_loader.rst b/routing/custom_route_loader.rst index ebd24d80b14..60202690f17 100644 --- a/routing/custom_route_loader.rst +++ b/routing/custom_route_loader.rst @@ -4,6 +4,90 @@ How to Create a custom Route Loader =================================== +Simple applications can define all their routes in a single configuration file - +usually ``config/routes.yaml`` (see :ref:`routing-creating-routes`). +However, in most applications it's common to import routes definitions from +different resources: PHP annotations in controller files, YAML, XML or PHP +files stored in some directory, etc. + +Built-in Route Loaders +---------------------- + +Symfony provides several route loaders for the most common needs: + +.. configuration-block:: + + .. code-block:: yaml + + # config/routes.yaml + app_file: + # loads routes from the given routing file stored in some bundle + resource: '@AcmeBundle/Resources/config/routing.yaml' + + app_annotations: + # loads routes from the PHP annotations of the controllers found in that directory + resource: '../src/Controller/' + type: annotation + + app_directory: + # loads routes from the YAML, XML or PHP files found in that directory + resource: '../legacy/routing/' + type: directory + + app_bundle: + # loads routes from the YAML, XML or PHP files found in some bundle directory + resource: '@AcmeOtherBundle/Resources/config/routing/' + type: directory + + .. code-block:: xml + + + + + + + + + + + + + + + + + + + .. code-block:: php + + // config/routes.php + use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; + + return function (RoutingConfigurator $routes) { + // loads routes from the given routing file stored in some bundle + $routes->import('@AcmeBundle/Resources/config/routing.yaml'); + + // loads routes from the PHP annotations of the controllers found in that directory + $routes->import('../src/Controller/', 'annotation'); + + // loads routes from the YAML or XML files found in that directory + $routes->import('../legacy/routing/', 'directory'); + + // loads routes from the YAML or XML files found in some bundle directory + $routes->import('@AcmeOtherBundle/Resources/config/routing/', 'directory'); + }; + +.. note:: + + When importing resources, the key (e.g. ``app_file``) is the name of collection. + Just be sure that it's unique per file so no other lines override it. + +If your application needs are different, you can create your own custom route +loader as explained in the next section. + What is a Custom Route Loader ----------------------------- diff --git a/routing/debug.rst b/routing/debug.rst deleted file mode 100644 index f83331488cc..00000000000 --- a/routing/debug.rst +++ /dev/null @@ -1,71 +0,0 @@ -.. index:: - single: Routing; Debugging - -How to Visualize And Debug Routes -================================= - -While adding and customizing routes, it's helpful to be able to visualize -and get detailed information about your routes. A great way to see every -route in your application is via the ``debug:router`` console command, which, -by default, lists *all* the configured routes in your application: - -.. 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} - ------------------ -------- -------- ------ ---------------------------------------------- - -You can also get very specific information on a single route by including -the route name as the command argument: - -.. code-block:: terminal - - $ php bin/console debug:router article_show - - # or use part of the name to search for routes - $ php bin/console debug:router blo - - Select one of the matching routes: - [0] blog - [1] blog_show - -Likewise, if you want to test whether a URL matches a given route, use the -``router:match`` command. This is useful to debug routing issues and find out -which route is associated with the given URL: - -.. code-block:: terminal - - $ php bin/console router:match /blog/my-latest-post - - Route "blog_show" matches - - +--------------+---------------------------------------------------------+ - | Property | Value | - +--------------+---------------------------------------------------------+ - | Route Name | blog_show | - | Path | /blog/{slug} | - | Path Regex | #^/blog/(?P[^/]++)$#sDu | - | Host | ANY | - | Host Regex | | - | Scheme | ANY | - | Method | ANY | - | Requirements | NO CUSTOM | - | Class | Symfony\Component\Routing\Route | - | Defaults | _controller: App\Controller\BlogController:show | - | Options | compiler_class: Symfony\Component\Routing\RouteCompiler | - | | utf8: true | - | Condition | context.getMethod() in ['GET', 'HEAD', 'POST'] | - +--------------+---------------------------------------------------------+ - -.. versionadded:: 4.3 - - The ``Condition`` was added to the router debug output in Symfony 4.3. diff --git a/routing/external_resources.rst b/routing/external_resources.rst deleted file mode 100644 index c3283e6561e..00000000000 --- a/routing/external_resources.rst +++ /dev/null @@ -1,260 +0,0 @@ -.. index:: - single: Routing; Importing routing resources - -How to Include External Routing Resources -========================================= - -Simple applications can define all their routes in a single configuration file - -usually ``config/routes.yaml`` (see :ref:`routing-creating-routes`). -However, in most applications it's common to import routes definitions from -different resources: PHP annotations in controller files, YAML, XML or PHP -files stored in some directory, etc. - -This can be done by importing routing resources from the main routing file: - -.. configuration-block:: - - .. code-block:: yaml - - # config/routes.yaml - app_file: - # loads routes from the given routing file stored in some bundle - resource: '@AcmeBundle/Resources/config/routing.yaml' - - app_annotations: - # loads routes from the PHP annotations of the controllers found in that directory - resource: '../src/Controller/' - type: annotation - - app_directory: - # loads routes from the YAML, XML or PHP files found in that directory - resource: '../legacy/routing/' - type: directory - - app_bundle: - # loads routes from the YAML, XML or PHP files found in some bundle directory - resource: '@AcmeOtherBundle/Resources/config/routing/' - type: directory - - .. code-block:: xml - - - - - - - - - - - - - - - - - - - .. code-block:: php - - // config/routes.php - use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; - - return function (RoutingConfigurator $routes) { - // loads routes from the given routing file stored in some bundle - $routes->import('@AcmeBundle/Resources/config/routing.yaml'); - - // loads routes from the PHP annotations of the controllers found in that directory - $routes->import('../src/Controller/', 'annotation'); - - // loads routes from the YAML or XML files found in that directory - $routes->import('../legacy/routing/', 'directory'); - - // loads routes from the YAML or XML files found in some bundle directory - $routes->import('@AcmeOtherBundle/Resources/config/routing/', 'directory'); - }; - -.. note:: - - When importing resources, the key (e.g. ``app_file``) is the name of collection. - Just be sure that it's unique per file so no other lines override it. - -.. _prefixing-imported-routes: - -Prefixing the URLs of Imported Routes -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -You can also choose to provide a "prefix" for the imported routes. For example, -to prefix all application routes with ``/site`` (e.g. ``/site/blog/{slug}`` -instead of ``/blog/{slug}``): - -.. configuration-block:: - - .. code-block:: php-annotations - - use Symfony\Component\Routing\Annotation\Route; - - /** - * @Route("/site") - */ - class DefaultController - { - // ... - } - - .. code-block:: yaml - - # config/routes.yaml - controllers: - resource: '../src/Controller/' - type: annotation - prefix: /site - - .. code-block:: xml - - - - - - - - - .. code-block:: php - - // config/routes.php - use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; - - return function (RoutingConfigurator $routes) { - $routes->import('../src/Controller/', 'annotation') - ->prefix('/site') - ; - }; - -The path of each route being loaded from the new routing resource will now -be prefixed with the string ``/site``. - -.. 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 ``/site`` - will result in the ``/site/`` URL. If you want to avoid this behavior, set - the ``trailing_slash_on_root`` option to ``false``: - - .. configuration-block:: - - .. code-block:: yaml - - # config/routes.yaml - controllers: - resource: '../src/Controller/' - type: annotation - prefix: /site - trailing_slash_on_root: false - - .. code-block:: xml - - - - - - - - - .. code-block:: php - - // config/routes.php - use App\Controller\ArticleController; - use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; - - return function (RoutingConfigurator $routes) { - $routes->import('../src/Controller/', 'annotation') - ->prefix('/site', false) - ; - }; - -Prefixing the Names of Imported Routes -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -You also have the possibility to prefix the names of all the routes defined in -a controller class or imported from a configuration file: - -.. configuration-block:: - - .. code-block:: php-annotations - - use Symfony\Component\Routing\Annotation\Route; - - /** - * @Route(name="blog_") - */ - class BlogController - { - /** - * @Route("/blog", name="index") - */ - public function index() - { - // ... - } - - /** - * @Route("/blog/posts/{slug}", name="post") - */ - public function show(Post $post) - { - // ... - } - } - - .. code-block:: yaml - - # config/routes.yaml - controllers: - resource: '../src/Controller/' - type: annotation - name_prefix: 'blog_' - - .. code-block:: xml - - - - - - - - - .. code-block:: php - - // config/routes.php - use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; - - return function (RoutingConfigurator $routes) { - $routes->import('../src/Controller/', 'annotation') - ->namePrefix('blog_') - ; - }; - -In this example, the names of the routes will be ``blog_index`` and ``blog_post``. - -Adding a Host Requirement to Imported Routes -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -You can set the host regex on imported routes. For more information, see -:ref:`component-routing-host-imported`. diff --git a/routing/extra_information.rst b/routing/extra_information.rst deleted file mode 100644 index ce833426102..00000000000 --- a/routing/extra_information.rst +++ /dev/null @@ -1,102 +0,0 @@ -.. index:: - single: Routing; Extra Information - -How to Pass Extra Information from a Route to a Controller -========================================================== - -Parameters inside the ``defaults`` collection don't necessarily have to match -a placeholder in the route ``path``. In fact, you can use the ``defaults`` -array to specify extra parameters that will then be accessible as arguments -to your controller, and as attributes of the ``Request`` object: - -.. configuration-block:: - - .. code-block:: php-annotations - - use Symfony\Component\Routing\Annotation\Route; - - /** - * @Route(name="blog_") - */ - class BlogController - { - /** - * @Route("/blog/{page}", name="index", defaults={"page": 1, "title": "Hello world!"}) - */ - public function index($page) - { - // ... - } - } - - # config/routes.yaml - blog: - path: /blog/{page} - controller: App\Controller\BlogController::index - defaults: - page: 1 - title: "Hello world!" - - .. code-block:: yaml - - # config/routes.yaml - blog: - path: /blog/{page} - controller: App\Controller\BlogController::index - defaults: - page: 1 - title: "Hello world!" - - .. code-block:: xml - - - - - - - 1 - Hello world! - - - - .. code-block:: php - - // config/routes.php - use App\Controller\BlogController; - use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; - - return function (RoutingConfigurator $routes) { - $routes->add('blog', '/blog/{page}') - ->controller([BlogController::class, 'index']) - ->defaults([ - 'page' => 1, - 'title' => 'Hello world!', - ]) - ; - }; - -Now, you can access this extra parameter in your controller, as an argument -to the controller method:: - - public function index($page, $title) - { - // ... - } - -Alternatively, the title could be accessed through the ``Request`` object:: - - use Symfony\Component\HttpFoundation\Request; - - public function index(Request $request, $page) - { - $title = $request->attributes->get('title'); - - // ... - } - -As you can see, the ``$title`` variable was never defined inside the route -path, but you can still access its value from inside your controller, through -the method's argument, or from the ``Request`` object's ``attributes`` bag. diff --git a/routing/generate_url_javascript.rst b/routing/generate_url_javascript.rst deleted file mode 100644 index 0893b53de8e..00000000000 --- a/routing/generate_url_javascript.rst +++ /dev/null @@ -1,25 +0,0 @@ -How to Generate Routing URLs in JavaScript -========================================== - -If you're in a Twig template, you can use the same ``path()`` function to set -JavaScript variables. The ``escape()`` function helps escape any -non-JavaScript-safe values: - -.. code-block:: html+twig - - - -But if you *actually* need to generate routes in pure JavaScript, consider using -the `FOSJsRoutingBundle`_. It makes the following possible: - -.. code-block:: html+twig - - - -.. _`FOSJsRoutingBundle`: https://github.com/FriendsOfSymfony/FOSJsRoutingBundle diff --git a/routing/hostname_pattern.rst b/routing/hostname_pattern.rst deleted file mode 100644 index f92e13f8285..00000000000 --- a/routing/hostname_pattern.rst +++ /dev/null @@ -1,439 +0,0 @@ -.. index:: - single: Routing; Matching on Hostname - -How to Match a Route Based on the Host -====================================== - -You can also match any route with the HTTP *host* of the incoming request. - -.. configuration-block:: - - .. code-block:: php-annotations - - // src/Controller/MainController.php - namespace App\Controller; - - use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; - use Symfony\Component\Routing\Annotation\Route; - - class MainController extends AbstractController - { - /** - * @Route("/", name="mobile_homepage", host="m.example.com") - */ - public function mobileHomepage() - { - // ... - } - - /** - * @Route("/", name="homepage") - */ - public function homepage() - { - // ... - } - } - - .. 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 - - - - - - - - - - - .. code-block:: php - - // config/routes.php - use App\Controller\MainController; - use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; - - return function (RoutingConfigurator $routes) { - $routes->add('mobile_homepage', '/') - ->controller([MainController::class, 'mobileHomepage']) - ->host('m.example.com') - ; - $routes->add('homepage', '/') - ->controller([MainController::class, 'homepage']) - ; - }; - - return $routes; - -Both routes match the same path, ``/``. However, the first one will only -match if the host is ``m.example.com``. - -Using Placeholders ------------------- - -The host option uses the same syntax as the path matching system. This means -you can use placeholders in your hostname: - -.. configuration-block:: - - .. code-block:: php-annotations - - // src/Controller/MainController.php - namespace App\Controller; - - use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; - use Symfony\Component\Routing\Annotation\Route; - - class MainController extends AbstractController - { - /** - * @Route("/", name="projects_homepage", host="{project}.example.com") - */ - public function projectsHomepage(string $project) - { - // ... - } - - /** - * @Route("/", name="homepage") - */ - public function homepage() - { - // ... - } - } - - .. code-block:: yaml - - # config/routes.yaml - projects_homepage: - path: / - host: "{project}.example.com" - controller: App\Controller\MainController::projectsHomepage - - homepage: - path: / - controller: App\Controller\MainController::homepage - - .. code-block:: xml - - - - - - - - - - - .. code-block:: php - - // config/routes.php - use App\Controller\MainController; - use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; - - return function (RoutingConfigurator $routes) { - $routes->add('project_homepage', '/') - ->controller([MainController::class, 'projectHomepage']) - ->host('{project}.example.com') - ; - $routes->add('homepage', '/') - ->controller([MainController::class, 'homepage']) - ; - }; - -Also, any requirement or default can be set for these placeholders. For -instance, if you want to match both ``m.example.com`` and -``mobile.example.com``, you can use this: - -.. configuration-block:: - - .. code-block:: php-annotations - - // src/Controller/MainController.php - namespace App\Controller; - - use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; - use Symfony\Component\Routing\Annotation\Route; - - class MainController extends AbstractController - { - /** - * @Route( - * "/", - * name="mobile_homepage", - * host="{subdomain}.example.com", - * defaults={"subdomain"="m"}, - * requirements={"subdomain"="m|mobile"} - * ) - */ - public function mobileHomepage() - { - // ... - } - - /** - * @Route("/", name="homepage") - */ - public function homepage() - { - // ... - } - } - - .. 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 - - - - - - - m - m|mobile - - - - - - .. code-block:: php - - // config/routes.php - use App\Controller\MainController; - use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; - - return function (RoutingConfigurator $routes) { - $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']) - ; - }; - -.. tip:: - - You can also use service parameters if you do not want to hardcode the - hostname: - - .. configuration-block:: - - .. code-block:: php-annotations - - // src/Controller/MainController.php - namespace App\Controller; - - use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; - use Symfony\Component\Routing\Annotation\Route; - - class MainController extends AbstractController - { - /** - * @Route( - * "/", - * name="mobile_homepage", - * host="m.{domain}", - * defaults={"domain"="%domain%"}, - * requirements={"domain"="%domain%"} - * ) - */ - public function mobileHomepage() - { - // ... - } - - /** - * @Route("/", name="homepage") - */ - public function homepage() - { - // ... - } - } - - .. code-block:: yaml - - # config/routes.yaml - mobile_homepage: - path: / - host: "m.{domain}" - controller: App\Controller\MainController::mobileHomepage - defaults: - domain: '%domain%' - requirements: - domain: '%domain%' - - homepage: - path: / - controller: App\Controller\MainController::homepage - - .. code-block:: xml - - - - - - - %domain% - %domain% - - - - - - .. code-block:: php - - // config/routes.php - use App\Controller\MainController; - use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; - - return function (RoutingConfigurator $routes) { - $routes->add('mobile_homepage', '/') - ->controller([MainController::class, 'mobileHomepage']) - ->host('m.{domain}') - ->defaults([ - 'domain' => '%domain%', - ]) - ->requirements([ - 'domain' => '%domain%', - ]) - ; - $routes->add('homepage', '/') - ->controller([MainController::class, 'homepage']) - ; - }; - -.. tip:: - - Make sure you also include a default option for the ``domain`` placeholder, - otherwise you need to include a domain value each time you generate - a URL using the route. - -.. _component-routing-host-imported: - -Using Host Matching of Imported Routes --------------------------------------- - -You can also set the host option on imported routes: - -.. configuration-block:: - - .. code-block:: php-annotations - - // vendor/acme/acme-hello-bundle/src/Controller/MainController.php - namespace Acme\AcmeHelloBundle\Controller; - - use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; - use Symfony\Component\Routing\Annotation\Route; - - /** - * @Route(host="hello.example.com") - */ - class MainController extends AbstractController - { - // ... - } - - .. code-block:: yaml - - # config/routes.yaml - app_hello: - resource: '@AcmeHelloBundle/Resources/config/routing.yaml' - host: "hello.example.com" - - .. code-block:: xml - - - - - - - - - .. code-block:: php - - // config/routes.php - use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; - - return function (RoutingConfigurator $routes) { - $routes->import("@AcmeHelloBundle/Resources/config/routing.php") - ->host('hello.example.com') - ; - }; - -The host ``hello.example.com`` will be set on each route loaded from the new -routing resource. - -Testing your Controllers ------------------------- - -You need to set the Host HTTP header on your request objects if you want to get -past url matching in your functional tests:: - - $crawler = $client->request( - 'GET', - '/', - [], - [], - ['HTTP_HOST' => 'm.' . $client->getContainer()->getParameter('domain')] - ); diff --git a/routing/optional_placeholders.rst b/routing/optional_placeholders.rst deleted file mode 100644 index a988e4f7840..00000000000 --- a/routing/optional_placeholders.rst +++ /dev/null @@ -1,198 +0,0 @@ -.. index:: - single: Routing; Optional Placeholders - -How to Define Optional Placeholders -=================================== - -To make things more exciting, add a new route that displays a list of all -the available blog posts for this imaginary blog application: - -.. configuration-block:: - - .. code-block:: php-annotations - - // src/Controller/BlogController.php - use Symfony\Component\Routing\Annotation\Route; - - class BlogController - { - /** - * @Route("/blog") - */ - public function index() - { - // ... - } - // ... - } - - .. code-block:: yaml - - # config/routes.yaml - blog: - path: /blog - controller: App\Controller\BlogController::index - - .. code-block:: xml - - - - - - - - - .. code-block:: php - - // config/routes.php - use App\Controller\BlogController; - use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; - - return function (RoutingConfigurator $routes) { - $routes->add('blog', '/blog') - ->controller([BlogController::class, 'index']) - ; - }; - -So far, this route is as simple as possible - it contains no placeholders -and will only match the exact URL ``/blog``. But what if you need this route -to support pagination, where ``/blog/2`` displays the second page of blog -entries? Update the route to have a new ``{page}`` placeholder: - -.. configuration-block:: - - .. code-block:: php-annotations - - // src/Controller/BlogController.php - - // ... - - /** - * @Route("/blog/{page}") - */ - public function index($page) - { - // ... - } - - .. code-block:: yaml - - # config/routes.yaml - blog: - path: /blog/{page} - controller: App\Controller\BlogController::index - - .. code-block:: xml - - - - - - - - - .. code-block:: php - - // config/routes.php - use App\Controller\BlogController; - use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; - - return function (RoutingConfigurator $routes) { - $routes->add('blog', '/blog/{page}') - ->controller([BlogController::class, 'index']) - ; - }; - -Like the ``{slug}`` placeholder before, the value matching ``{page}`` will -be available inside your controller. Its value can be used to determine which -set of blog posts to display for the given page. - -But hold on! Since placeholders are required by default, this route will -no longer match on ``/blog`` alone. Instead, to see page 1 of the blog, -you'd need to use the URL ``/blog/1``! Since that's no way for a rich web -app to behave, modify the route to make the ``{page}`` parameter optional. -This is done by including it in the ``defaults`` collection: - -.. configuration-block:: - - .. code-block:: php-annotations - - // src/Controller/BlogController.php - - // ... - - /** - * @Route("/blog/{page}", defaults={"page"=1}) - */ - public function index($page) - { - // ... - } - - .. code-block:: yaml - - # config/routes.yaml - blog: - path: /blog/{page} - controller: App\Controller\BlogController::index - defaults: { page: 1 } - - .. code-block:: xml - - - - - - - 1 - - - - .. code-block:: php - - // config/routes.php - use App\Controller\BlogController; - use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; - - return function (RoutingConfigurator $routes) { - $routes->add('blog', '/blog/{page}') - ->controller([BlogController::class, 'index']) - ->defaults([ - 'page' => 1, - ]) - ; - }; - -By adding ``page`` to the ``defaults`` key, the ``{page}`` placeholder is -no longer required. The URL ``/blog`` will match this route and the value -of the ``page`` parameter will be set to ``1``. The URL ``/blog/2`` will -also match, giving the ``page`` parameter a value of ``2``. Perfect. - -=========== ======== ================== -URL Route Parameters -=========== ======== ================== -``/blog`` ``blog`` ``{page}`` = ``1`` -``/blog/1`` ``blog`` ``{page}`` = ``1`` -``/blog/2`` ``blog`` ``{page}`` = ``2`` -=========== ======== ================== - -.. caution:: - - You can have more than one optional placeholder (e.g. ``/blog/{slug}/{page}``), - but everything after an optional placeholder must be optional. For example, - ``/{page}/blog`` is a valid path, but ``page`` will always be required - (i.e. ``/blog`` will not match this route). - -.. tip:: - - Routes with optional parameters at the end will not match on requests - with a trailing slash (i.e. ``/blog/`` will not match, ``/blog`` will match). diff --git a/routing/redirect_in_config.rst b/routing/redirect_in_config.rst deleted file mode 100644 index 87abde17df3..00000000000 --- a/routing/redirect_in_config.rst +++ /dev/null @@ -1,260 +0,0 @@ -.. index:: - single: Routing; Redirect using Framework:RedirectController - -How to Configure a Redirect without a custom Controller -======================================================= - -Sometimes, a URL needs to redirect to another URL. You can do that by creating -a new controller action whose only task is to redirect, but using the -:class:`Symfony\\Bundle\\FrameworkBundle\\Controller\\RedirectController` of -the FrameworkBundle is even easier. - -You can redirect to a specific path (e.g. ``/about``) or to a specific route -using its name (e.g. ``homepage``). - -Redirecting Using a Path ------------------------- - -Assume there is no default controller for the ``/`` path of your application -and you want to redirect these requests to ``/app``. You will need to use the -:method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\RedirectController::urlRedirectAction` -action to redirect to this new url: - -.. configuration-block:: - - .. code-block:: yaml - - # config/routes.yaml - - # load some routes - one should ultimately have the path "/app" - controllers: - resource: '../src/Controller/' - type: annotation - prefix: /app - - # redirecting the homepage - homepage: - path: / - controller: Symfony\Bundle\FrameworkBundle\Controller\RedirectController::urlRedirectAction - defaults: - path: /app - permanent: true - - .. code-block:: xml - - - - - - - - - - - /app - true - - - - .. code-block:: php - - // config/routes.php - use Symfony\Bundle\FrameworkBundle\Controller\RedirectController; - use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; - - return function (RoutingConfigurator $routes) { - // load some routes - one should ultimately have the path "/app" - $routes->import('../src/Controller/', 'annotation') - ->prefix('/app') - ; - // redirecting the homepage - $routes->add('homepage', '/') - ->controller([RedirectController::class, 'urlRedirectAction']) - ->defaults([ - 'path' => '/app', - 'permanent' => true, - ]) - ; - }; - -In this example, you configured a route for the ``/`` path and let the -``RedirectController`` redirect it to ``/app``. The ``permanent`` switch -tells the action to issue a ``301`` HTTP status code instead of the default -``302`` HTTP status code. - -Redirecting Using a Route -------------------------- - -Assume you are migrating your website from WordPress to Symfony, you want to -redirect ``/wp-admin`` to the route ``sonata_admin_dashboard``. You don't know -the path, only the route name. This can be achieved using the -:method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\RedirectController::redirectAction` -action: - -.. configuration-block:: - - .. code-block:: yaml - - # config/routes.yaml - - # ... - - admin: - path: /wp-admin - controller: Symfony\Bundle\FrameworkBundle\Controller\RedirectController::redirectAction - defaults: - route: sonata_admin_dashboard - # make a permanent redirection... - permanent: true - # ...and keep the original query string parameters - keepQueryParams: true - - .. code-block:: xml - - - - - - - - - sonata_admin_dashboard - - true - - true - - - - .. code-block:: php - - // config/routes.php - use Symfony\Bundle\FrameworkBundle\Controller\RedirectController; - use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; - - return function (RoutingConfigurator $routes) { - // redirecting the homepage - $routes->add('admin', '/wp-admin') - ->controller([RedirectController::class, 'redirectAction']) - ->defaults([ - 'route' => 'sonata_admin_dashboard', - // make a permanent redirection... - 'permanent' => true, - // ...and keep the original query string parameters - 'keepQueryParams' => true, - ]) - ; - }; - -.. caution:: - - Because you are redirecting to a route instead of a path, the required - option is called ``route`` in the ``redirect()`` action, instead of ``path`` - in the ``urlRedirect()`` action. - -Keeping the Request Method when Redirecting -------------------------------------------- - -The redirections performed in the previous examples use the ``301`` and ``302`` -HTTP status codes. For legacy reasons, these HTTP redirections change the method -of ``POST`` requests to ``GET`` (because redirecting a ``POST`` request didn't -work well in old browsers). - -However, in some scenarios it's either expected or required that the redirection -request uses the same HTTP method. That's why the HTTP standard defines two -additional status codes (``307`` and ``308``) to perform temporary/permanent -redirects that maintain the original request method. - -The :method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\RedirectController::urlRedirectAction` -and :method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\RedirectController::redirectAction` -methods accept an additional argument called ``keepRequestMethod``. When it's -set to ``true``, temporary redirects use ``307`` code instead of ``302`` and -permanent redirects use ``308`` code instead of ``301``: - -.. configuration-block:: - - .. code-block:: yaml - - # config/routes.yaml - - # redirects with the 308 status code - route_foo: - # ... - controller: Symfony\Bundle\FrameworkBundle\Controller\RedirectController::redirectAction - defaults: - # ... - permanent: true - keepRequestMethod: true - - # redirects with the 307 status code - route_bar: - # ... - controller: Symfony\Bundle\FrameworkBundle\Controller\RedirectController::redirectAction - defaults: - # ... - permanent: false - keepRequestMethod: true - - .. code-block:: xml - - - - - - - - - true - true - - - - - - false - true - - - - .. code-block:: php - - // config/routes.php - use Symfony\Component\Routing\Route; - use Symfony\Component\Routing\RouteCollection; - - $collection = new RouteCollection(); - - // redirects with the 308 status code - $collection->add('route_foo', new Route('...', [ - // ... - '_controller' => 'Symfony\Bundle\FrameworkBundle\Controller\RedirectController::urlRedirectAction', - 'permanent' => true, - 'keepRequestMethod' => true, - ])); - - // redirects with the 307 status code - $collection->add('route_bar', new Route('...', [ - // ... - '_controller' => 'Symfony\Bundle\FrameworkBundle\Controller\RedirectController::urlRedirectAction', - 'permanent' => false, - 'keepRequestMethod' => true, - ])); - - return $collection; diff --git a/routing/redirect_trailing_slash.rst b/routing/redirect_trailing_slash.rst deleted file mode 100644 index 9f17a8c0b07..00000000000 --- a/routing/redirect_trailing_slash.rst +++ /dev/null @@ -1,12 +0,0 @@ -.. index:: - single: Routing; Redirect URLs with a trailing slash - -Redirect URLs with a Trailing Slash -=================================== - -.. caution:: - - In Symfony 4.1 the automatic URL redirection was improved as explained in - :ref:`routing-trailing-slash-redirection`. That's why you no longer need to - do that redirection yourself and this article has been removed because it's - no longer needed. diff --git a/routing/requirements.rst b/routing/requirements.rst deleted file mode 100644 index e01655fb7ff..00000000000 --- a/routing/requirements.rst +++ /dev/null @@ -1,310 +0,0 @@ -.. index:: - single: Routing; Requirements - -How to Define Route Requirements -================================ - -:ref:`Route requirements ` can be used to make a specific route -*only* match under specific conditions. The simplest example involves restricting -a routing ``{wildcard}`` to only match some regular expression: - -.. configuration-block:: - - .. code-block:: php-annotations - - // src/Controller/BlogController.php - namespace App\Controller; - - use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; - use Symfony\Component\Routing\Annotation\Route; - - class BlogController extends AbstractController - { - /** - * @Route("/blog/{page}", name="blog_list", requirements={"page"="\d+"}) - */ - public function list($page) - { - // ... - } - } - - .. code-block:: yaml - - # config/routes.yaml - blog_list: - path: /blog/{page} - controller: App\Controller\BlogController::list - requirements: - page: '\d+' - - .. code-block:: xml - - - - - - - \d+ - - - - - - .. code-block:: php - - // config/routes.php - use App\Controller\BlogController; - use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; - - return function (RoutingConfigurator $routes) { - $routes->add('blog_list', '/blog/{page}') - ->controller([BlogController::class, 'list']) - ->requirements([ - 'page' => '\d+', - ]) - ; - // ... - }; - -Thanks to the ``\d+`` requirement (i.e. a "digit" of any length), ``/blog/2`` will -match this route but ``/blog/some-string`` will *not* match. - -.. sidebar:: Earlier Routes Always Win - - Why would you ever care about requirements? If a request matches *two* routes, - then the first route always wins. By adding requirements to the first route, - you can make each route match in just the right situations. See :ref:`routing-requirements` - for an example. - -Since the parameter requirements are regular expressions, the complexity -and flexibility of each requirement is entirely up to you. Suppose the homepage -of your application is available in two different languages, based on the -URL: - -.. configuration-block:: - - .. code-block:: php-annotations - - // src/Controller/MainController.php - - // ... - class MainController extends AbstractController - { - /** - * @Route("/{_locale}", defaults={"_locale"="en"}, requirements={ - * "_locale"="en|fr" - * }) - */ - public function homepage($_locale) - { - // ... - } - } - - .. code-block:: yaml - - # config/routes.yaml - homepage: - path: /{_locale} - controller: App\Controller\MainController::homepage - defaults: { _locale: en } - requirements: - _locale: en|fr - - .. code-block:: xml - - - - - - - en - en|fr - - - - .. code-block:: php - - // config/routes.php - use App\Controller\MainController; - use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; - - return function (RoutingConfigurator $routes) { - $routes->add('homepage', '/{_locale}') - ->controller([MainController::class, 'homepage']) - ->defaults([ - '_locale' => 'en', - ]) - ->requirements([ - '_locale' => 'en|fr', - ]) - ; - }; - -For incoming requests, the ``{_locale}`` portion of the URL is matched against -the regular expression ``(en|fr)``. - -======= ======================== -Path Parameters -======= ======================== -``/`` ``{_locale}`` = ``"en"`` -``/en`` ``{_locale}`` = ``"en"`` -``/fr`` ``{_locale}`` = ``"fr"`` -``/es`` *won't match this route* -======= ======================== - -.. note:: - - You can enable UTF-8 route matching by setting the ``utf8`` option when - declaring or importing routes. This will make e.g. a ``.`` in requirements - match any UTF-8 characters instead of just a single byte. - -.. tip:: - - The route requirements can also include container parameters, as explained - in :doc:`this article `. - This comes in handy when the regular expression is very complex and used - repeatedly in your application. - -.. index:: - single: Routing; Method requirement - -.. _routing-method-requirement: - -Adding HTTP Method Requirements -------------------------------- - -In addition to the URL, you can also match on the *method* of the incoming -request (i.e. GET, HEAD, POST, PUT, DELETE). Suppose you create an API for -your blog and you have 2 routes: One for displaying a post (on a GET or HEAD -request) and one for updating a post (on a PUT request). This can be -accomplished with the following route configuration: - -.. configuration-block:: - - .. code-block:: php-annotations - - // src/Controller/BlogApiController.php - namespace App\Controller; - - // ... - - class BlogApiController extends AbstractController - { - /** - * @Route("/api/posts/{id}", methods={"GET","HEAD"}) - */ - public function show($id) - { - // ... return a JSON response with the post - } - - /** - * @Route("/api/posts/{id}", methods={"PUT"}) - */ - public function edit($id) - { - // ... 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 - - - - - - - - - - - .. code-block:: php - - // config/routes.php - use App\Controller\BlogApiController; - use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; - - return function (RoutingConfigurator $routes) { - $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']) - ; - - // or use collection - $api = $routes->collection('api_post_') - ->prefix('/api/posts/{id}') - ; - $api->add('show') - ->controller([BlogApiController::class, 'show']) - ->methods(['GET', 'HEAD']) - ; - $api->add('edit') - ->controller([BlogApiController::class, 'edit']) - ->methods(['PUT']) - ; - }; - -Despite the fact that these two routes have identical paths -(``/api/posts/{id}``), the first route will match only GET or HEAD requests and -the second route will match only PUT requests. This means that you can display -and edit the post with the same URL, while using distinct controllers for the -two actions. - -.. note:: - - If no ``methods`` are specified, the route will match on *all* methods. - -.. tip:: - - If you're using HTML forms and HTTP methods *other* than ``GET`` and ``POST``, - you'll need to include a ``_method`` parameter to *fake* the HTTP method. See - :doc:`/form/action_method` for more information. - -Adding a Host Requirement -------------------------- - -You can also match on the HTTP *host* of the incoming request. For more -information, see :doc:`/routing/hostname_pattern` in the Routing -component documentation. - -Adding Dynamic Requirements with Expressions --------------------------------------------- - -For really complex requirements, you can use dynamic expressions to match *any* -information on the request. See :doc:`/routing/conditions`. - -.. _`PCRE Unicode property`: http://php.net/manual/en/regexp.reference.unicode.php diff --git a/routing/scheme.rst b/routing/scheme.rst deleted file mode 100644 index 0ca8b8587fb..00000000000 --- a/routing/scheme.rst +++ /dev/null @@ -1,94 +0,0 @@ -.. index:: - single: Routing; Scheme requirement - -How to Force Routes to Always Use HTTPS or HTTP -=============================================== - -Sometimes, you want to secure some routes and be sure that they are always -accessed via the HTTPS protocol. The Routing component allows you to enforce -the URI scheme with the ``schemes`` setting: - -.. configuration-block:: - - .. code-block:: php-annotations - - // src/Controller/MainController.php - namespace App\Controller; - - use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; - use Symfony\Component\Routing\Annotation\Route; - - class MainController extends AbstractController - { - /** - * @Route("/secure", name="secure", schemes={"https"}) - */ - public function secure() - { - // ... - } - } - - .. code-block:: yaml - - # config/routes.yaml - secure: - path: /secure - controller: App\Controller\MainController::secure - schemes: [https] - - .. code-block:: xml - - - - - - - - - - .. code-block:: php - - // config/routes.php - use App\Controller\MainController; - use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; - - return function (RoutingConfigurator $routes) { - $routes->add('secure', '/secure') - ->controller([MainController::class, 'secure']) - ->schemes(['https']) - ; - }; - -The above configuration forces the ``secure`` route to always use HTTPS. - -When generating the ``secure`` URL, and if the current scheme is HTTP, Symfony -will automatically generate an absolute URL with HTTPS as the scheme, even when -using the ``path()`` function: - -.. code-block:: twig - - {# If the current scheme is HTTPS #} - {{ path('secure') }} - {# generates a relative URL: /secure #} - - {# If the current scheme is HTTP #} - {{ path('secure') }} - {# generates an absolute URL: https://example.com/secure #} - -The requirement is also enforced for incoming requests. If you try to access -the ``/secure`` path with HTTP, you will automatically be redirected to the -same URL, but with the HTTPS scheme. - -The above example uses ``https`` for the scheme, but you can also force a URL -to always use ``http``. - -.. note:: - - The Security component provides another way to enforce HTTP or HTTPS via - the ``requires_channel`` setting. This alternative method is better suited - to secure an "area" of your website (all URLs under ``/admin``) or when - you want to secure URLs defined in a third party bundle (see - :doc:`/security/force_https` for more details). diff --git a/routing/service_container_parameters.rst b/routing/service_container_parameters.rst deleted file mode 100644 index 58c26ad7da2..00000000000 --- a/routing/service_container_parameters.rst +++ /dev/null @@ -1,212 +0,0 @@ -.. index:: - single: Routing; Service Container Parameters - -How to Use Service Container Parameters in your Routes -====================================================== - -Sometimes you may find it useful to make some parts of your routes -globally configurable. For instance, if you build an internationalized -site, you'll probably start with one or two locales. Surely you'll -add a requirement to your routes to prevent a user from matching a locale -other than the locales you support. - -You *could* hardcode your ``_locale`` requirement in all your routes, but -a better solution is to use a configurable service container parameter right -inside your routing configuration: - -.. configuration-block:: - - .. code-block:: php-annotations - - // src/Controller/MainController.php - namespace App\Controller; - - use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; - use Symfony\Component\Routing\Annotation\Route; - - class MainController extends AbstractController - { - /** - * @Route("/{_locale}/contact", name="contact", requirements={ - * "_locale"="%app.locales%" - * }) - */ - public function contact() - { - // ... - } - } - - .. code-block:: yaml - - # config/routes.yaml - contact: - path: /{_locale}/contact - controller: App\Controller\MainController::contact - requirements: - _locale: '%app.locales%' - - .. code-block:: xml - - - - - - - %app.locales% - - - - .. code-block:: php - - // config/routes.php - use App\Controller\MainController; - use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; - - return function (RoutingConfigurator $routes) { - $routes->add('contact', '/{_locale}/contact') - ->controller([MainController::class, 'contact']) - ->requirements([ - '_locale' => '%app.locales%', - ]) - ; - }; - -.. versionadded:: 4.3 - - Support for boolean container parameters in routes was introduced in Symfony 4.3. - -You can now control and set the ``app.locales`` parameter somewhere -in your container: - -.. configuration-block:: - - .. code-block:: yaml - - # config/services.yaml - parameters: - app.locales: en|es - - .. code-block:: xml - - - - - - - en|es - - - - .. code-block:: php - - // config/services.php - $container->setParameter('app.locales', 'en|es'); - -You can also use a parameter to define your route path (or part of your -path): - -.. configuration-block:: - - .. code-block:: php-annotations - - // src/Controller/MainController.php - namespace App\Controller; - - use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; - use Symfony\Component\Routing\Annotation\Route; - - class MainController extends AbstractController - { - /** - * @Route("/%app.route_prefix%/contact", name="contact") - */ - public function contact() - { - // ... - } - } - - .. code-block:: yaml - - # config/routes.yaml - some_route: - path: /%app.route_prefix%/contact - controller: App\Controller\MainController::contact - - .. code-block:: xml - - - - - - - - - .. code-block:: php - - // config/routes.php - use App\Controller\MainController; - use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; - - return function (RoutingConfigurator $routes) { - $routes->add('contact', '/%app.route_prefix%/contact') - ->controller([MainController::class, 'contact']) - ; - }; - -Now make sure that the ``app.route_prefix`` parameter is set somewhere in your -container: - -.. configuration-block:: - - .. code-block:: yaml - - # config/services.yaml - parameters: - app.route_prefix: 'foo' - - .. code-block:: xml - - - - - - - foo - - - - .. code-block:: php - - // config/services.php - $container->setParameter('app.route_prefix', 'foo'); - -.. note:: - - Just like in normal service container configuration files, if you actually - need a ``%`` in your route, you can escape the percent sign by doubling - it, e.g. ``/score-50%%``, which would resolve to ``/score-50%``. - - However, as the ``%`` characters included in any URL are automatically encoded, - the resulting URL of this example would be ``/score-50%25`` (``%25`` is the - result of encoding the ``%`` character). - -.. seealso:: - - For parameter handling within a Dependency Injection Class see - :doc:`/configuration/using_parameters_in_dic`. diff --git a/routing/slash_in_parameter.rst b/routing/slash_in_parameter.rst deleted file mode 100644 index 9b56def082b..00000000000 --- a/routing/slash_in_parameter.rst +++ /dev/null @@ -1,99 +0,0 @@ -.. index:: - single: Routing; Allow / in route parameter - -.. _routing/slash_in_parameter: - -How to Allow a "/" Character in a Route Parameter -================================================= - -Sometimes, you need to compose URLs with parameters that can contain a slash -``/``. For example, consider the ``/share/{token}`` route. If the ``token`` -value contains a ``/`` character this route won't match. This is because Symfony -uses this character as separator between route parts. - -This article explains how you can modify a route definition so that placeholders -can contain the ``/`` character too. - -Configure the Route -------------------- - -By default, the Symfony Routing component requires that the parameters match -the following regular expression: ``[^/]+``. This means that all characters are -allowed except ``/``. - -You must explicitly allow ``/`` to be part of your placeholder by specifying -a more permissive regular expression for it: - -.. configuration-block:: - - .. code-block:: php-annotations - - use Symfony\Component\Routing\Annotation\Route; - - class DefaultController - { - /** - * @Route("/share/{token}", name="share", requirements={"token"=".+"}) - */ - public function share($token) - { - // ... - } - } - - .. code-block:: yaml - - # config/routes.yaml - share: - path: /share/{token} - controller: App\Controller\DefaultController::share - requirements: - token: .+ - - .. code-block:: xml - - - - - - - .+ - - - - .. code-block:: php - - // config/routes.php - use App\Controller\DefaultController; - use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; - - return function (RoutingConfigurator $routes) { - $routes->add('share', '/share/{token}') - ->controller([DefaultController::class, 'share']) - ->requirements([ - 'token' => '.+', - ]) - ; - }; - -That's it! Now, the ``{token}`` parameter can contain the ``/`` character. - -.. note:: - - If the route includes the special ``{_format}`` placeholder, 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. - -.. note:: - - If the route defines several placeholders and you apply this permissive - regular expression to all of them, the results won't be the expected. For - example, if the route definition is ``/share/{path}/{token}`` and both - ``path`` and ``token`` accept ``/``, then ``path`` will contain its contents - and the token, and ``token`` will be empty. diff --git a/security/force_https.rst b/security/force_https.rst index bf69d7ad995..6358f663942 100644 --- a/security/force_https.rst +++ b/security/force_https.rst @@ -85,8 +85,10 @@ like ``requires_channel: '%env(SECURE_SCHEME)%'``. In your ``.env`` file, set See :doc:`/security/access_control` for more details about ``access_control`` in general. -It is also possible to specify using HTTPS in the routing configuration, -see :doc:`/routing/scheme` for more details. +.. note:: + + An alternative way to enforce HTTP or HTTPS is to use + :ref:`the scheme option ` of a route or group of routes. .. note:: diff --git a/templating/formats.rst b/templating/formats.rst index 17fe4c4e0d9..b12f471a6ff 100644 --- a/templating/formats.rst +++ b/templating/formats.rst @@ -48,7 +48,7 @@ but can return any other format based on the format requested by the user. The request format is most often managed by the routing, where a route can be configured so that ``/about-us`` sets the request format to ``html`` while ``/about-us.xml`` sets the format to ``xml``. This can be achieved by using the -special ``_format`` placeholder in your route definition:: +:ref:`special _format parameter ` in your route definition:: /** * @Route("/{slug}.{_format}", defaults={"_format"="html"}, requirements={"_format"="html|xml"})) @@ -67,10 +67,6 @@ format: View as XML -.. seealso:: - - For more information, see this :ref:`Advanced Routing Example `. - .. tip:: When building APIs, using file name extensions often isn't the best diff --git a/translation/locale.rst b/translation/locale.rst index da2fc84cfd9..2dfd7c80b7c 100644 --- a/translation/locale.rst +++ b/translation/locale.rst @@ -59,8 +59,8 @@ this violates a fundamental rule of the Web: that a particular URL returns the same resource regardless of the user. To further muddy the problem, which version of the content would be indexed by search engines? -A better policy is to include the locale in the URL. This is fully-supported -by the routing system using the special ``_locale`` parameter: +A better policy is to include the locale in the URL using the +:ref:`special _locale parameter `: .. configuration-block:: @@ -114,8 +114,8 @@ application. .. tip:: - Read :doc:`/routing/service_container_parameters` to learn how to avoid - hardcoding the ``_locale`` requirement in all your routes. + Define the locale requirement as a :ref:`container parameter ` + to avoid hardcoding its value in all your routes. .. index:: single: Translations; Fallback and default locale