From 0f1f2ea715cc2d8ea98bd5906dcd364eca90d9bc Mon Sep 17 00:00:00 2001 From: Jules Pietri Date: Fri, 8 Mar 2019 15:43:14 +0100 Subject: [PATCH] Improved routing section --- components/routing.rst | 91 +++++++++++++++++------------ routing.rst | 49 +++++++++++----- routing/conditions.rst | 2 +- routing/custom_route_loader.rst | 17 ++++-- routing/debug.rst | 21 ++++++- routing/external_resources.rst | 30 +++++----- routing/extra_information.rst | 26 +++++++++ routing/generate_url_javascript.rst | 13 +++-- routing/hostname_pattern.rst | 8 +-- routing/optional_placeholders.rst | 7 +-- routing/requirements.rst | 1 + routing/scheme.rst | 2 +- routing/slash_in_parameter.rst | 2 + 13 files changed, 178 insertions(+), 91 deletions(-) diff --git a/components/routing.rst b/components/routing.rst index 9ba6ff7e466..4483158f258 100644 --- a/components/routing.rst +++ b/components/routing.rst @@ -39,8 +39,8 @@ your autoloader to load the Routing component:: use Symfony\Component\Routing\Matcher\UrlMatcher; use Symfony\Component\Routing\RequestContext; - use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\Route; + use Symfony\Component\Routing\RouteCollection; $route = new Route('/foo', ['_controller' => 'MyController']); $routes = new RouteCollection(); @@ -82,20 +82,24 @@ be thrown. Defining Routes ~~~~~~~~~~~~~~~ -A full route definition can contain up to seven parts: +A full route definition can contain up to eight parts: -#. The URL path route. This is matched against the URL passed to the `RequestContext`, - and can contain named wildcard placeholders (e.g. ``{placeholders}``) - to match dynamic parts in the URL. +#. 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 values. This contains an array of arbitrary values - that will be returned when the request matches the route. +#. 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 as regular expressions. + placeholders in the pattern as regular expressions. -#. An array of options. These contain internal settings for the route and - are the least commonly needed. +#. 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. @@ -105,6 +109,10 @@ A full route definition can contain up to seven parts: #. 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:: $route = new Route( @@ -114,7 +122,8 @@ Take the following route, which combines several of these ideas:: [], // options '{subdomain}.example.com', // host [], // schemes - [] // methods + [], // methods + 'context.getHost() matches "/(secure|admin).example.com/"' // condition ); // ... @@ -138,19 +147,22 @@ 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. -.. tip:: +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 to match all URLs which start with a certain path and end in an - arbitrary suffix you can use the following route definition:: +If you want a placeholder to match anything, it must be the last of the route:: - $route = new Route( - '/start/{suffix}', - ['suffix' => ''], - ['suffix' => '.*'] - ); + $route = new Route( + '/start/{required}/{anything}', + ['required' => 'default'], // should always be defined + ['anything' => '.*'] // explicit requirement to allow "/" + ); -Using Prefixes -~~~~~~~~~~~~~~ +Learn more about it by reading :ref:`routing/slash_in_parameter`. + +Using Prefixes and Collection Settings +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ You can add routes or other instances of :class:`Symfony\\Component\\Routing\\RouteCollection` to *another* collection. @@ -166,11 +178,12 @@ host to all routes of a subtree using methods provided by the $subCollection->add(...); $subCollection->addPrefix('/prefix'); $subCollection->addDefaults([...]); - $subCollection->addRequirements([]); - $subCollection->addOptions([]); - $subCollection->setHost('admin.example.com'); + $subCollection->addRequirements([...]); + $subCollection->addOptions([...]); + $subCollection->setHost('{subdomain}.example.com'); $subCollection->setMethods(['POST']); $subCollection->setSchemes(['https']); + $subCollection->setCondition('context.getHost() matches "/(secure|admin).example.com/"'); $rootCollection->addCollection($subCollection); @@ -210,7 +223,7 @@ 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:: +a certain route with the :class:`Symfony\\Component\\Routing\\Generator\\UrlGenerator`:: use Symfony\Component\Routing\Generator\UrlGenerator; use Symfony\Component\Routing\RequestContext; @@ -260,7 +273,7 @@ when the route doesn't exist:: Load Routes from a File ~~~~~~~~~~~~~~~~~~~~~~~ -You've already seen how you can easily add routes to a collection right inside +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. The Routing component comes with a number of loader classes, each giving @@ -347,7 +360,7 @@ The all-in-one Router ~~~~~~~~~~~~~~~~~~~~~ The :class:`Symfony\\Component\\Routing\\Router` class is an all-in-one package -to quickly use the Routing component. The constructor expects a loader instance, +to use the Routing component. The constructor expects a loader instance, a path to the main route definition and some other settings:: public function __construct( @@ -372,7 +385,8 @@ automatically in the background if you want to use it. A basic example of the ['cache_dir' => __DIR__.'/cache'], $requestContext ); - $router->match('/foo/bar'); + $parameters = $router->match('/foo/bar'); + $url = $router->generate('some_route', ['parameter' => 'value']); .. note:: @@ -380,6 +394,8 @@ 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 ~~~~~~~~~~~~~~~~~~~~~~~ @@ -481,7 +497,7 @@ You can also include UTF-8 strings as routing requirements: * @Route( * "/category/{name}", * name="route2", - * requirements={"default"="한국어"}, + * defaults={"name": "한국어"}, * options={"utf8": true} * ) */ @@ -494,10 +510,10 @@ You can also include UTF-8 strings as routing requirements: # app/config/routing.yml route2: - path: /default/{default} - defaults: { _controller: 'AppBundle:Default:default' } - requirements: - default: "한국어" + path: /category/{name} + defaults: + _controller: 'AppBundle:Default:default' + name: '한국어' options: utf8: true @@ -510,9 +526,9 @@ You can also include UTF-8 strings as routing requirements: xsi:schemaLocation="http://symfony.com/schema/routing https://symfony.com/schema/routing/routing-1.0.xsd"> - + AppBundle:Default:default - 한국어 + 한국어 @@ -527,10 +543,9 @@ You can also include UTF-8 strings as routing requirements: $routes->add('route2', new Route('/default/{default}', [ '_controller' => 'AppBundle:Default:default', + 'name' => '한국어', ], - [ - 'default' => '한국어', - ], + [], [ 'utf8' => true, ] diff --git a/routing.rst b/routing.rst index 90d0977ffbc..b79e30c9e78 100644 --- a/routing.rst +++ b/routing.rst @@ -4,14 +4,14 @@ Routing ======= -Beautiful URLs are an absolute 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``. +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 should you need to +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 is simple. +the change should be trivial. The Symfony router lets you define creative URLs that you map to different areas of your application. By the end of this article, you'll be able to: @@ -27,10 +27,13 @@ areas of your application. By the end of this article, you'll be able to: Routing Examples ---------------- -A *route* is a map from a URL path to a controller. For example, suppose -you want to match any URL like ``/blog/my-post`` or ``/blog/all-about-symfony`` -and send it to a controller that can look up and render that blog post. -The route is simple: +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 annotations. All formats provide +the same features and performance, so choose the one you prefer: .. configuration-block:: @@ -56,6 +59,7 @@ The route is simple: /** * Matches /blog/* + * but not /blog/slug/extra-part * * @Route("/blog/{slug}", name="blog_show") */ @@ -72,10 +76,13 @@ The route is simple: # app/config/routing.yml blog_list: + # Matches /blog exactly path: /blog defaults: { _controller: AppBundle:Blog:list } blog_show: + # Matches /blog/* + # but not /blog/slug/extra-part path: /blog/{slug} defaults: { _controller: AppBundle:Blog:show } @@ -88,10 +95,13 @@ The route is simple: xsi:schemaLocation="http://symfony.com/schema/routing https://symfony.com/schema/routing/routing-1.0.xsd"> + AppBundle:Blog:list + + AppBundle:Blog:show @@ -104,9 +114,12 @@ The route is simple: use Symfony\Component\Routing\Route; $routes = new RouteCollection(); + // Matches /blog exactly $routes->add('blog_list', new Route('/blog', [ '_controller' => 'AppBundle:Blog:list', ])); + // Matches /blog/* + // but not /blog/slug/extra-part $routes->add('blog_show', new Route('/blog/{slug}', [ '_controller' => 'AppBundle:Blog:show', ])); @@ -119,13 +132,21 @@ Thanks to these two routes: is executed; * If the user goes to ``/blog/*``, the second route is matched and ``showAction()`` - is executed. Because the route path is ``/blog/{slug}``, a ``$slug`` variable is - passed to ``showAction()`` matching that value. For example, if the user goes to + is executed. Because the route path is ``/blog/{slug}``, a ``$slug`` variable + is passed to ``showAction()`` matching that value. For example, if the user goes to ``/blog/yay-routing``, then ``$slug`` will equal ``yay-routing``. -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). +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). + +.. caution:: + + 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`. 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. diff --git a/routing/conditions.rst b/routing/conditions.rst index 0eb83d544b6..bc209b0b9aa 100644 --- a/routing/conditions.rst +++ b/routing/conditions.rst @@ -6,7 +6,7 @@ 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 ``conditions`` routing option: +define arbitrary matching logic, use the ``condition`` routing setting: .. configuration-block:: diff --git a/routing/custom_route_loader.rst b/routing/custom_route_loader.rst index 520801d2088..c83a556a1fd 100644 --- a/routing/custom_route_loader.rst +++ b/routing/custom_route_loader.rst @@ -102,11 +102,17 @@ and configure the service and method to call: return $routes; -In this example, the routes are loaded by calling the ``loadRoutes()`` method of -the service whose ID is ``admin_route_loader``. Your service doesn't have to +In this example, the routes are loaded by calling the ``loadRoutes()`` method +of the service whose ID is ``admin_route_loader``. Your service doesn't have to extend or implement any special class, but the called method must return a :class:`Symfony\\Component\\Routing\\RouteCollection` object. +.. note:: + + The routes defined using service route loaders will be automatically + cached by the framework. So whenever your service should load new routes, + don't forget to clear the cache. + Creating a custom Loader ------------------------ @@ -219,8 +225,7 @@ Now define a service for the ``ExtraLoader``: use AppBundle\Routing\ExtraLoader; - $container - ->autowire(ExtraLoader::class) + $container->autowire(ExtraLoader::class) ->addTag('routing.loader') ; @@ -229,7 +234,7 @@ as potential route loaders and added as specialized route loaders to the ``routing.loader`` *service*, which is an instance of :class:`Symfony\\Bundle\\FrameworkBundle\\Routing\\DelegatingLoader`. -Using the custom Loader +Using the Custom Loader ~~~~~~~~~~~~~~~~~~~~~~~ If you did nothing else, your custom routing loader would *not* be called. @@ -276,7 +281,7 @@ for the ``ExtraLoader``, so it is set to ".". cached by the framework. So whenever you change something in the loader class itself, don't forget to clear the cache. -More advanced Loaders +More Advanced Loaders --------------------- If your custom route loader extends from diff --git a/routing/debug.rst b/routing/debug.rst index e9f7f3829b0..d0c4e78faea 100644 --- a/routing/debug.rst +++ b/routing/debug.rst @@ -6,8 +6,8 @@ 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 -lists *all* the configured routes in your application: +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 @@ -40,3 +40,20 @@ which route is associated with the given URL: $ 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 | + +--------------+---------------------------------------------------------+ diff --git a/routing/external_resources.rst b/routing/external_resources.rst index bb1680becbe..4979a8744df 100644 --- a/routing/external_resources.rst +++ b/routing/external_resources.rst @@ -7,8 +7,8 @@ How to Include External Routing Resources Simple applications can define all their routes in a single configuration file - usually ``app/config/routing.yml`` (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 or XML files -stored in some directory, etc. +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: @@ -19,7 +19,7 @@ This can be done by importing routing resources from the main routing file: # app/config/routing.yml app_file: # loads routes from the given routing file stored in some bundle - resource: '@AcmeOtherBundle/Resources/config/routing.yml' + resource: '@AcmeBundle/Resources/config/routing.yml' app_annotations: # loads routes from the PHP annotations of the controllers found in that directory @@ -27,13 +27,13 @@ This can be done by importing routing resources from the main routing file: type: annotation app_directory: - # loads routes from the YAML or XML files found in that 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 or XML files found in some bundle directory - resource: '@AppBundle/Resources/config/routing/public/' + # loads routes from the YAML, XML or PHP files found in some bundle directory + resource: '@AcmeOtherBundle/Resources/config/routing/' type: directory .. code-block:: xml @@ -46,7 +46,7 @@ This can be done by importing routing resources from the main routing file: https://symfony.com/schema/routing/routing-1.0.xsd"> - + @@ -55,7 +55,7 @@ This can be done by importing routing resources from the main routing file: - + .. code-block:: php @@ -66,7 +66,7 @@ This can be done by importing routing resources from the main routing file: $routes = new RouteCollection(); $routes->addCollection( // loads routes from the given routing file stored in some bundle - $loader->import("@AcmeOtherBundle/Resources/config/routing.yml") + $loader->import("@AcmeBundle/Resources/config/routing.yml") // loads routes from the PHP annotations of the controllers found in that directory $loader->import("@AppBundle/Controller/", "annotation") @@ -75,15 +75,15 @@ This can be done by importing routing resources from the main routing file: $loader->import("../legacy/routing/", "directory") // loads routes from the YAML or XML files found in some bundle directory - $loader->import("@AppBundle/Resources/config/routing/public/", "directory") - ); + $routes->import('@AcmeOtherBundle/Resources/config/routing/public/', 'directory'); + }; return $routes; .. note:: - When importing resources from YAML, the key (e.g. ``app_file``) is meaningless. - Just be sure that it's unique so no other lines override it. + 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: @@ -91,8 +91,8 @@ Prefixing the URLs of Imported Routes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ You can also choose to provide a "prefix" for the imported routes. For example, -suppose you want to prefix all routes in the AppBundle with ``/site`` (e.g. -``/site/blog/{slug}`` instead of ``/blog/{slug}``): +to prefix all application routes with ``/site`` (e.g.``/site/blog/{slug}`` +instead of ``/blog/{slug}``): .. configuration-block:: diff --git a/routing/extra_information.rst b/routing/extra_information.rst index 5ee80da003d..13766709e18 100644 --- a/routing/extra_information.rst +++ b/routing/extra_information.rst @@ -11,6 +11,32 @@ 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 # app/config/routing.yml diff --git a/routing/generate_url_javascript.rst b/routing/generate_url_javascript.rst index b4e3721c519..0893b53de8e 100644 --- a/routing/generate_url_javascript.rst +++ b/routing/generate_url_javascript.rst @@ -1,13 +1,14 @@ 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: +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 @@ -16,9 +17,9 @@ 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 index 383ca274a36..76b013c5051 100644 --- a/routing/hostname_pattern.rst +++ b/routing/hostname_pattern.rst @@ -4,7 +4,7 @@ How to Match a Route Based on the Host ====================================== -You can also match on the HTTP *host* of the incoming request. +You can also match any route with the HTTP *host* of the incoming request. .. configuration-block:: @@ -161,9 +161,9 @@ you can use placeholders in your hostname: return $routes; -You can also set requirements and default options for these placeholders. For +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 use this: +``mobile.example.com``, you can use this: .. configuration-block:: @@ -412,7 +412,7 @@ past url matching in your functional tests:: $crawler = $client->request( 'GET', - '/homepage', + '/', [], [], ['HTTP_HOST' => 'm.' . $client->getContainer()->getParameter('domain')] diff --git a/routing/optional_placeholders.rst b/routing/optional_placeholders.rst index 3757340cda6..c89fb3f0fd9 100644 --- a/routing/optional_placeholders.rst +++ b/routing/optional_placeholders.rst @@ -12,12 +12,10 @@ the available blog posts for this imaginary blog application: .. code-block:: php-annotations // src/AppBundle/Controller/BlogController.php + use Symfony\Component\Routing\Annotation\Route; - // ... - class BlogController extends Controller + class BlogController { - // ... - /** * @Route("/blog") */ @@ -25,6 +23,7 @@ the available blog posts for this imaginary blog application: { // ... } + // ... } .. code-block:: yaml diff --git a/routing/requirements.rst b/routing/requirements.rst index ee2174f820c..519cb451c73 100644 --- a/routing/requirements.rst +++ b/routing/requirements.rst @@ -103,6 +103,7 @@ URL: */ public function homepageAction($_locale) { + // ... } } diff --git a/routing/scheme.rst b/routing/scheme.rst index 314601dd06f..a4dcf7c9c68 100644 --- a/routing/scheme.rst +++ b/routing/scheme.rst @@ -6,7 +6,7 @@ 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 via schemes: +the URI scheme with the ``schemes`` setting: .. configuration-block:: diff --git a/routing/slash_in_parameter.rst b/routing/slash_in_parameter.rst index 8b3b7df6058..202284897f6 100644 --- a/routing/slash_in_parameter.rst +++ b/routing/slash_in_parameter.rst @@ -1,6 +1,8 @@ .. index:: single: Routing; Allow / in route parameter +.. _routing/slash_in_parameter: + How to Allow a "/" Character in a Route Parameter =================================================