From 6e48ceee6ed96364fa9000fe2c6fa1f792e86971 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Wed, 13 Jul 2016 21:42:58 -0400 Subject: [PATCH] Re-reading the routing chapter 1) Removing a lot of duplication 2) Deleted method_parameters (this topic seemed to be covered in multiple places) --- form/action_method.rst | 4 +- redirection_map | 2 +- reference/configuration/framework.rst | 2 +- reference/forms/types/options/method.rst.inc | 4 +- routing.rst | 480 +++++++++---------- routing/method_parameters.rst | 89 ---- routing/requirements.rst | 164 ++----- 7 files changed, 277 insertions(+), 468 deletions(-) delete mode 100644 routing/method_parameters.rst diff --git a/form/action_method.rst b/form/action_method.rst index 8ce9e67e4be..27fdddfb1ac 100644 --- a/form/action_method.rst +++ b/form/action_method.rst @@ -59,5 +59,5 @@ to the ``form()`` or the ``form_start()`` helper functions: will insert a hidden field with the name ``_method`` that stores this method. The form will be submitted in a normal POST request, but Symfony's router is capable of detecting the ``_method`` parameter and will interpret it as - a PUT, PATCH or DELETE request. Read the cookbook chapter - ":doc:`/routing/method_parameters`" for more information. + a PUT, PATCH or DELETE request. See the :ref:`configuration-framework-http_method_override` + option. diff --git a/redirection_map b/redirection_map index 52f4452bb2b..8c1bb17df16 100644 --- a/redirection_map +++ b/redirection_map @@ -169,7 +169,7 @@ /cookbook/routing/debug /routing/debug /cookbook/routing/external_resources /routing/external_resources /cookbook/routing/extra_information /routing/extra_information -/cookbook/routing/method_parameters /routing/method_parameters +/cookbook/routing/method_parameters /routing/requirements /cookbook/routing/optional_placeholders /routing/optional_placeholders /cookbook/routing/redirect_in_config /routing/redirect_in_config /cookbook/routing/redirect_trailing_slash /routing/redirect_trailing_slash diff --git a/reference/configuration/framework.rst b/reference/configuration/framework.rst index 4e8606b9cea..e163a408cee 100644 --- a/reference/configuration/framework.rst +++ b/reference/configuration/framework.rst @@ -148,7 +148,7 @@ named ``kernel.http_method_override``. .. seealso:: - For more information, see :doc:`/routing/method_parameters`. + For more information, see :doc:`/form/action_method`. .. caution:: diff --git a/reference/forms/types/options/method.rst.inc b/reference/forms/types/options/method.rst.inc index 8cf5a1a3f4a..e8a5a13961a 100644 --- a/reference/forms/types/options/method.rst.inc +++ b/reference/forms/types/options/method.rst.inc @@ -21,8 +21,8 @@ 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. For - more information, see :doc:`/routing/method_parameters`. + these HTTP methods, as they're not supported on standard browsers. This can + be useful when using :ref:`method routing requirements `. .. note:: diff --git a/routing.rst b/routing.rst index 23324716bc9..38bd8fbf59e 100644 --- a/routing.rst +++ b/routing.rst @@ -24,8 +24,8 @@ areas of your application. By the end of this chapter, you'll be able to: .. index:: single: Routing; Basics -Routing in Action ------------------ +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`` @@ -45,10 +45,25 @@ The route is simple: class BlogController extends Controller { /** + * Matches /blog exactly + * + * @Route("/blog", name="blog_list") + */ + public function listAction() + { + // ... + } + + /** + * Matches /blog/* + * * @Route("/blog/{slug}", name="blog_show") */ public function showAction($slug) { + // $slug will equal the dynamic part of the URL + // e.g. at /blog/yay-routing, then $slug='yay-routing' + // ... } } @@ -56,6 +71,10 @@ The route is simple: .. code-block:: yaml # app/config/routing.yml + blog_list: + path: /blog + defaults: { _controller: AppBundle:Blog:list } + blog_show: path: /blog/{slug} defaults: { _controller: AppBundle:Blog:show } @@ -69,6 +88,10 @@ The route is simple: xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd"> + + AppBundle:Blog:list + + AppBundle:Blog:show @@ -81,148 +104,92 @@ The route is simple: use Symfony\Component\Routing\Route; $collection = new RouteCollection(); + $collection->add('blog_list', new Route('/blog', array( + '_controller' => 'AppBundle:Blog:list', + ))); $collection->add('blog_show', new Route('/blog/{slug}', array( '_controller' => 'AppBundle:Blog:show', ))); return $collection; -The path defined by the ``blog_show`` route acts like ``/blog/*`` where -the wildcard is given the name ``slug``. For the URL ``/blog/my-blog-post``, -the ``slug`` variable gets a value of ``my-blog-post``, which is available -for you to use in your controller (keep reading). The ``blog_show`` is the -internal name of the route, which doesn't have any meaning yet and just needs -to be unique. Later, you'll use it to generate URLs. - -If you don't want to use annotations, you can also use YAML, XML or PHP. In these -formats, the ``_controller`` parameter is a special -key that tells Symfony which controller should be executed when a URL matches -this route. The ``_controller`` string is called the -:ref:`logical name `. It follows a pattern that -points to a specific PHP class and method, in this case the -``AppBundle\Controller\BlogController::showAction`` method. - -Congratulations! You've just created your first route and connected it to -a controller. Now, when you visit ``/blog/my-post``, the ``showAction`` controller -will be executed and the ``$slug`` variable will be equal to ``my-post``. - -This is the goal of the Symfony router: to map the URL of a request to a -controller. Along the way, you'll learn all sorts of tricks that make mapping -even the most complex URLs easy. - -.. index:: - single: Routing; Under the hood - -Routing: Under the Hood ------------------------ +Thanks to these two routes: -When a request is made to your application, it contains an address to the -exact "resource" that the client is requesting. This address is called the -URL, (or URI), and could be ``/contact``, ``/blog/read-me``, or anything -else. Take the following HTTP request for example: +* If the user goes to ``/blog``, the first route is matched and ``listAction()`` + is executed; -.. code-block:: text +* 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 + ``/blog/yay-routing``, then ``$slug`` will equal ``yay-routing``. - GET /blog/my-blog-post +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 goal of the Symfony routing system is to parse this URL and determine -which controller should be executed. The whole process looks like this: +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. +Later, you'll use it to generate URLs. -#. The request is handled by the Symfony front controller (e.g. ``app.php``); +.. sidebar:: Routing in Other Formats -#. The Symfony core (i.e. Kernel) asks the router to inspect the request; + 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! -#. The router matches the incoming URL to a specific route and returns information - about the route, including the controller that should be executed; - -#. The Symfony Kernel executes the controller, which ultimately returns - a ``Response`` object. - -.. figure:: /_images/http/request-flow.png - :align: center - :alt: Symfony request flow - - The routing layer is a tool that translates the incoming URL into a specific - controller to execute. - -.. index:: - single: Routing; Creating routes - -.. _routing-creating-routes: - -Creating Routes ---------------- - -Symfony loads all the routes for your application from a single routing configuration -file. The file is usually ``app/config/routing.yml``, but can be configured -to be anything (including an XML or PHP file) via the application configuration -file: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - framework: - # ... - router: { resource: '%kernel.root_dir%/config/routing.yml' } + In these formats, the ``_controller`` "defaults" value is a special key that + tells Symfony which controller should be executed when a URL matches this route. + The ``_controller`` string is called the + :ref:`logical name `. It follows a pattern that + points to a specific PHP class and method, in this case the + ``AppBundle\Controller\BlogController::listAction`` and + ``AppBundle\Controller\BlogController::showAction`` methods. - .. code-block:: xml - - - - - - - - - - +This is the goal of the Symfony router: to map the URL of a request to a +controller. Along the way, you'll learn all sorts of tricks that make mapping +even the most complex URLs easy. - .. code-block:: php +.. _routing-requirements: - // app/config/config.php - $container->loadFromExtension('framework', array( - // ... - 'router' => array( - 'resource' => '%kernel.root_dir%/config/routing.php', - ), - )); +Adding {placeholder} Requirements +--------------------------------- -.. tip:: +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: - Even though all routes are loaded from a single file, it's common practice - to include additional routing resources. To do so, just point out in the - main routing configuration file which external files should be included. - See the :doc:`/routing/external_resources` section for more - information. +* blog_list: ``/blog/{page}`` will match ``/blog/*``; +* blog_show: ``/blog/{slug}`` will *also* match ``/blog/*``. -Basic Route Configuration -~~~~~~~~~~~~~~~~~~~~~~~~~ +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! -Defining a route is easy, and a typical application will have lots of routes. -A basic route consists of just two parts: the ``path`` to match and a -``defaults`` array: +To fix this, add a *requirement* that the ``{page}`` wildcard can *only* match numbers +(digits): .. configuration-block:: .. code-block:: php-annotations - // src/AppBundle/Controller/MainController.php + // src/AppBundle/Controller/BlogController.php + namespace AppBundle\Controller; - // ... - class MainController extends Controller + use Symfony\Bundle\FrameworkBundle\Controller\Controller; + use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; + + class BlogController extends Controller { /** - * @Route("/") + * @Route("/blog/{page}", name="blog_list", requirements={"page": "\d+"}) + */ + public function listAction($page) + { + // ... + } + + /** + * @Route("/blog/{slug}", name="blog_show") */ - public function homepageAction() + public function showAction($slug) { // ... } @@ -231,9 +198,14 @@ A basic route consists of just two parts: the ``path`` to match and a .. code-block:: yaml # app/config/routing.yml - _welcome: - path: / - defaults: { _controller: AppBundle:Main:homepage } + blog_list: + path: /blog/{page} + defaults: { _controller: AppBundle:Blog:list } + requirements: + page: '\d+' + + blog_show: + # ... .. code-block:: xml @@ -244,52 +216,70 @@ A basic route consists of just two parts: the ``path`` to match and a xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd"> - - AppBundle:Main:homepage + + AppBundle:Blog:list + \d+ + - .. code-block:: php + .. code-block:: php // app/config/routing.php use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\Route; $collection = new RouteCollection(); - $collection->add('_welcome', new Route('/', array( - '_controller' => 'AppBundle:Main:homepage', + $collection->add('blog_list', new Route('/blog/{page}', array( + '_controller' => 'AppBundle:Blog:list', + ), array( + 'page' => '\d+' ))); + // ... + return $collection; -This route matches the homepage (``/``) and maps it to the -``AppBundle:Main:homepage`` controller. The ``_controller`` string is -translated by Symfony into an actual PHP function and executed. That process -will be explained shortly in the :ref:`controller-string-syntax` section. +The ``\d+`` is a regular expression that matches a *digit* of any length. Now: -.. index:: - single: Routing; Placeholders +======================== ============= =============================== +URL Route Parameters +======================== ============= =============================== +``/blog/2`` ``blog_list`` ``$page`` = ``2`` +``/blog/yay-routing`` ``blog_show`` ``$slug`` = ``yay-routing`` +======================== ============= =============================== + +To learn about other route requirements - like HTTP method, hostname and dynamic +expressions - see :doc:`/routing/requirements`. -Dynamic Routing with Placeholders -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Giving {placeholders} a Default Value +------------------------------------- -Of course the routing system supports much more interesting routes. Many -routes will contain one or more named "wildcard" placeholders: +In the previous example, the ``blog_list`` has a path of ``/blog/{page}``. If the +user goes to ``/blog/1``, it will match. But if the user goes to ``/blog``, it will +**not** match. As soon as you add a ``{placeholder}`` to a route, it *must* have +a value. + +So how can we make ``/blog_list`` once again match when the user goes to ``/blog``? +By adding a *default* value: .. configuration-block:: .. code-block:: php-annotations // src/AppBundle/Controller/BlogController.php + namespace AppBundle\Controller; + + use Symfony\Bundle\FrameworkBundle\Controller\Controller; + use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; - // ... class BlogController extends Controller { /** - * @Route("/blog/{slug}") + * @Route("/blog/{page}", name="blog_list", requirements={"page": "\d+"}) */ - public function showAction($slug) + public function listAction($page = 1) { // ... } @@ -298,9 +288,14 @@ routes will contain one or more named "wildcard" placeholders: .. code-block:: yaml # app/config/routing.yml + blog_list: + path: /blog/{page} + defaults: { _controller: AppBundle:Blog:list, page: 1 } + requirements: + page: '\d+' + blog_show: - path: /blog/{slug} - defaults: { _controller: AppBundle:Blog:show } + # ... .. code-block:: xml @@ -311,9 +306,13 @@ routes will contain one or more named "wildcard" placeholders: xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd"> - - AppBundle:Blog:show + + AppBundle:Blog:list + 1 + \d+ + + .. code-block:: php @@ -323,21 +322,19 @@ routes will contain one or more named "wildcard" placeholders: use Symfony\Component\Routing\Route; $collection = new RouteCollection(); - $collection->add('blog_show', new Route('/blog/{slug}', array( - '_controller' => 'AppBundle:Blog:show', + $collection->add('blog_list', new Route('/blog/{page}', array( + '_controller' => 'AppBundle:Blog:list', + 'page' => 1, + ), array( + 'page' => '\d+' ))); - return $collection; + // ... -The path will match anything that looks like ``/blog/*``. Even better, -the value matching the ``{slug}`` placeholder will be available inside your -controller. In other words, if the URL is ``/blog/hello-world``, a ``$slug`` -variable, with a value of ``hello-world``, will be available in the controller. -This can be used, for example, to load the blog post matching that string. + return $collection; -The path will *not*, however, match simply ``/blog``. That's because, -by default, all placeholders are required. This can be changed by adding -a placeholder value to the ``defaults`` array. +Now, when the user goes to ``/page``, the ``blog_list`` route will match and ``$page`` +will default to a value of ``1``. .. index:: single: Routing; Advanced example @@ -348,9 +345,7 @@ a placeholder value to the ``defaults`` array. Advanced Routing Example ~~~~~~~~~~~~~~~~~~~~~~~~ -At this point, you have everything you need to create a powerful routing -structure in Symfony. The following is an example of just how flexible the -routing system can be: +With all of this in mind, check out this advanced example: .. configuration-block:: @@ -492,11 +487,11 @@ that are special: each adds a unique piece of functionality inside your applicat Controller Naming Pattern ------------------------- -Every route must have a ``_controller`` parameter, which dictates which -controller should be executed when that route is matched. This parameter -uses a simple string pattern called the *logical controller name*, which -Symfony maps to a specific PHP method and class. The pattern has three parts, -each separated by a colon: +If you use YAML, XML or PHP route configuration, then each route must have a +``_controller`` parameter, which dictates which controller should be executed when +that route is matched. This parameter uses a simple string pattern called the +*logical controller name*, which Symfony maps to a specific PHP method and class. +The pattern has three parts, each separated by a colon: **bundle**:**controller**:**action** @@ -538,45 +533,62 @@ more flexibility. uses just one colon separator (e.g. ``service_name:indexAction``) and refers to the controller as a service (see :doc:`/controller/service`). -Route Parameters and Controller Arguments ------------------------------------------ +.. index:: + single: Routing; Creating routes -The route parameters (e.g. ``{slug}``) are especially important because -each is made available as an argument to the controller method:: +.. _routing-creating-routes: - public function showAction($slug) - { - // ... - } +Loading Routes +-------------- + +Symfony loads all the routes for your application from a *single* routing configuration +file: ``app/config/routing.yml``. But from inside of this file, you can load any +*other* routing files you want. In fact, by default, Symfony loads annotation route +configuration from your AppBundle's ``Controller/`` directory, which is how Symfony +sees our annotation routes: + +.. configuration-block:: + + .. code-block:: yaml + + # app/config/routing.yml + app: + resource: "@AppBundle/Controller/" + type: annotation + + .. code-block:: xml -In reality, the entire ``defaults`` collection is merged with the parameter -values to form a single array. Each key of that array is available as an -argument on the controller. + + + -In other words, for each argument of your controller method, Symfony looks -for a route parameter of that name and assigns its value to that argument. -In the advanced example above, any combination (in any order) of the following -variables could be used as arguments to the ``showAction()`` method: + + + -* ``$_locale`` -* ``$year`` -* ``$title`` -* ``$_format`` -* ``$_controller`` -* ``$_route`` + .. code-block:: php -Since the placeholders and ``defaults`` collection are merged together, even -the ``$_controller`` variable is available. For a more detailed discussion, -see :ref:`route-parameters-controller-arguments`. + // app/config/routing.php + use Symfony\Component\Routing\RouteCollection; + + $collection = new RouteCollection(); + $collection->addCollection( + // second argument is the type, which is required to enable + // the annotation reader for this resource + $loader->import("@AppBundle/Controller/", "annotation") + ); -.. tip:: + return $collection; - The special ``$_route`` variable is set to the name of the route that was - matched. +For more details on loading routes, including how to prefix the paths of loaded routes, +see :doc:`/routing/external_resources`. -You can even add extra information to your route definition and access it -within your controller. For more information on this topic, -see :doc:`/routing/extra_information`. +The path will *not*, however, match simply ``/blog``. That's because, +by default, all placeholders are required. This can be changed by adding +a placeholder value to the ``defaults`` array. .. index:: single: Routing; Generating URLs @@ -585,22 +597,8 @@ Generating URLs --------------- The routing system should also be used to generate URLs. In reality, routing -is a bidirectional system: mapping the URL to a controller+parameters and -a route+parameters back to a URL. The -:method:`Symfony\\Component\\Routing\\Router::match` and -:method:`Symfony\\Component\\Routing\\Router::generate` methods form this bidirectional -system. Take the ``blog_show`` example route from earlier:: - - $params = $this->get('router')->match('/blog/my-blog-post'); - // array( - // 'slug' => 'my-blog-post', - // '_controller' => 'AppBundle:Blog:show', - // ) - - $uri = $this->get('router')->generate('blog_show', array( - 'slug' => 'my-blog-post' - )); - // /blog/my-blog-post +is a bidirectional system: mapping the URL to a controller and +a route back to a 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 @@ -612,6 +610,7 @@ route. With this information, any URL can easily be generated:: { // ... + // /blog/my-blog-post $url = $this->generateUrl( 'blog_show', array('slug' => 'my-blog-post') @@ -630,23 +629,6 @@ route. With this information, any URL can easily be generated:: array('slug' => 'my-blog-post') ); -In an upcoming section, you'll learn how to generate URLs from inside templates. - -.. tip:: - - If the front-end of your application uses Ajax requests, you might want - to be able to generate URLs in JavaScript based on your routing configuration. - By using the `FOSJsRoutingBundle`_, you can do exactly that: - - .. code-block:: javascript - - var url = Routing.generate( - 'blog_show', - {'slug': 'my-blog-post'} - ); - - For more information, see the documentation for that bundle. - .. index:: single: Routing; Generating URLs in a template @@ -685,29 +667,40 @@ the ``path()`` function to generate a relative URL: Read this blog post. -.. tip:: +Generating 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: + +.. configuration-block:: + + .. code-block:: html+twig + + - If you are generating the route inside a `` - .. 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:: javascript - .. code-block:: html+php - - + var url = Routing.generate( + 'blog_show', + {'slug': 'my-blog-post'} + ); .. index:: single: Routing; Absolute URLs @@ -716,7 +709,7 @@ Generating Absolute URLs ~~~~~~~~~~~~~~~~~~~~~~~~ By default, the router will generate relative URLs (e.g. ``/blog``). From -a controller, simply pass ``UrlGeneratorInterface::ABSOLUTE_URL`` to the third argument of the ``generateUrl()`` +a controller, pass ``UrlGeneratorInterface::ABSOLUTE_URL`` to the third argument of the ``generateUrl()`` method:: use Symfony\Component\Routing\Generator\UrlGeneratorInterface; @@ -724,9 +717,8 @@ method:: $this->generateUrl('blog_show', array('slug' => 'my-blog-post'), UrlGeneratorInterface::ABSOLUTE_URL); // http://www.example.com/blog/my-blog-post -From a template, in Twig, simply use the ``url()`` function (which generates an absolute URL) -rather than the ``path()`` function (which generates a relative URL). In PHP, pass ``true`` -to ``generate()``: +From a template, in Twig, use the ``url()`` function (which generates an absolute URL) +rather than the ``path()`` function (which generates a relative URL): .. configuration-block:: diff --git a/routing/method_parameters.rst b/routing/method_parameters.rst deleted file mode 100644 index 00da7d88343..00000000000 --- a/routing/method_parameters.rst +++ /dev/null @@ -1,89 +0,0 @@ -.. index:: - single: Routing; methods - -How to Use HTTP Methods beyond GET and POST in Routes -===================================================== - -The HTTP method of a request is one of the requirements that can be checked -when seeing if it matches a route. This is introduced in the routing chapter -of the book ":doc:`/routing`" with examples using GET and POST. You can -also use other HTTP verbs in this way. For example, if you have a blog post -entry then you could use the same URL path to show it, make changes to it and -delete it by matching on GET, PUT and DELETE. - -.. configuration-block:: - - .. code-block:: yaml - - blog_show: - path: /blog/{slug} - defaults: { _controller: AppBundle:Blog:show } - methods: [GET] - - blog_update: - path: /blog/{slug} - defaults: { _controller: AppBundle:Blog:update } - methods: [PUT] - - blog_delete: - path: /blog/{slug} - defaults: { _controller: AppBundle:Blog:delete } - methods: [DELETE] - - .. code-block:: xml - - - - - - - AppBundle:Blog:show - - - - AppBundle:Blog:update - - - - AppBundle:Blog:delete - - - - .. code-block:: php - - use Symfony\Component\Routing\RouteCollection; - use Symfony\Component\Routing\Route; - - $collection = new RouteCollection(); - $collection->add('blog_show', new Route('/blog/{slug}', array( - '_controller' => 'AppBundle:Blog:show', - ), array(), array(), '', array(), array('GET'))); - - $collection->add('blog_update', new Route('/blog/{slug}', array( - '_controller' => 'AppBundle:Blog:update', - ), array(), array(), '', array(), array('PUT'))); - - $collection->add('blog_delete', new Route('/blog/{slug}', array( - '_controller' => 'AppBundle:Blog:delete', - ), array(), array(), '', array('DELETE'))); - - return $collection; - -Faking the Method with ``_method`` ----------------------------------- - -Unfortunately, life isn't quite this simple, since most browsers do not support -sending PUT and DELETE requests via the `method` attribute in an HTML form. Fortunately, -Symfony provides you with a simple way of working around this limitation. By including -a ``_method`` parameter in the query string or parameters of an HTTP request, Symfony -will use this as the method when matching routes. Forms automatically include a -hidden field for this parameter if their submission method is not GET or POST. -See :doc:`the related chapter in the forms documentation ` -for more information. - -.. tip:: - - You can disable the ``_method`` functionality shown here using the - :ref:`configuration-framework-http_method_override` option. diff --git a/routing/requirements.rst b/routing/requirements.rst index 43c0d749af5..c356afdb055 100644 --- a/routing/requirements.rst +++ b/routing/requirements.rst @@ -4,29 +4,26 @@ How to Define Route Requirements ================================ -Take a quick look at the routes that have been created so far: +: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/AppBundle/Controller/BlogController.php + namespace AppBundle\Controller; + + use Symfony\Bundle\FrameworkBundle\Controller\Controller; + use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; - // ... class BlogController extends Controller { /** - * @Route("/blog/{page}", defaults={"page" = 1}) - */ - public function indexAction($page) - { - // ... - } - - /** - * @Route("/blog/{slug}") + * @Route("/blog/{page}", name="blog_list", requirements={"page": "\d+"}) */ - public function showAction($slug) + public function listAction($page) { // ... } @@ -35,97 +32,11 @@ Take a quick look at the routes that have been created so far: .. code-block:: yaml # app/config/routing.yml - blog: + blog_list: path: /blog/{page} - defaults: { _controller: AppBundle:Blog:index, page: 1 } - - blog_show: - path: /blog/{slug} - defaults: { _controller: AppBundle:Blog:show } - - .. code-block:: xml - - - - - - - AppBundle:Blog:index - 1 - - - - AppBundle:Blog:show - - - - .. code-block:: php - - // app/config/routing.php - use Symfony\Component\Routing\RouteCollection; - use Symfony\Component\Routing\Route; - - $collection = new RouteCollection(); - $collection->add('blog', new Route('/blog/{page}', array( - '_controller' => 'AppBundle:Blog:index', - 'page' => 1, - ))); - - $collection->add('blog_show', new Route('/blog/{show}', array( - '_controller' => 'AppBundle:Blog:show', - ))); - - return $collection; - -Can you spot the problem? Notice that both routes have patterns that match -URLs that look like ``/blog/*``. The Symfony router will always choose the -**first** matching route it finds. In other words, the ``blog_show`` route -will *never* be matched. Instead, a URL like ``/blog/my-blog-post`` will match -the first route (``blog``) and return a nonsense value of ``my-blog-post`` -to the ``{page}`` parameter. - -====================== ======== =============================== -URL Route Parameters -====================== ======== =============================== -``/blog/2`` ``blog`` ``{page}`` = ``2`` -``/blog/my-blog-post`` ``blog`` ``{page}`` = ``"my-blog-post"`` -====================== ======== =============================== - -The answer to the problem is to add route *requirements* or route *conditions* -(see :doc:`/routing/conditions`). The routes in this example would work -perfectly if the ``/blog/{page}`` path *only* matched URLs where the ``{page}`` -portion is an integer. Fortunately, regular expression requirements can easily -be added for each parameter. For example: - -.. configuration-block:: - - .. code-block:: php-annotations - - // src/AppBundle/Controller/BlogController.php - - // ... - - /** - * @Route("/blog/{page}", defaults={"page": 1}, requirements={ - * "page": "\d+" - * }) - */ - public function indexAction($page) - { - // ... - } - - .. code-block:: yaml - - # app/config/routing.yml - blog: - path: /blog/{page} - defaults: { _controller: AppBundle:Blog:index, page: 1 } + defaults: { _controller: AppBundle:Blog:list } requirements: - page: \d+ + page: '\d+' .. code-block:: xml @@ -136,11 +47,12 @@ be added for each parameter. For example: xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd"> - - AppBundle:Blog:index - 1 + + AppBundle:Blog:list \d+ + + .. code-block:: php @@ -150,39 +62,25 @@ be added for each parameter. For example: use Symfony\Component\Routing\Route; $collection = new RouteCollection(); - $collection->add('blog', new Route('/blog/{page}', array( - '_controller' => 'AppBundle:Blog:index', - 'page' => 1, + $collection->add('blog_list', new Route('/blog/{page}', array( + '_controller' => 'AppBundle:Blog:list', ), array( - 'page' => '\d+', + 'page' => '\d+' ))); - return $collection; - -The ``\d+`` requirement is a regular expression that says that the value of -the ``{page}`` parameter must be a digit (i.e. a number). The ``blog`` route -will still match on a URL like ``/blog/2`` (because 2 is a number), but it -will no longer match a URL like ``/blog/my-blog-post`` (because ``my-blog-post`` -is *not* a number). + // ... -As a result, a URL like ``/blog/my-blog-post`` will now properly match the -``blog_show`` route. + return $collection; -======================== ============= =============================== -URL Route Parameters -======================== ============= =============================== -``/blog/2`` ``blog`` ``{page}`` = ``2`` -``/blog/my-blog-post`` ``blog_show`` ``{slug}`` = ``my-blog-post`` -``/blog/2-my-blog-post`` ``blog_show`` ``{slug}`` = ``2-my-blog-post`` -======================== ============= =============================== +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 - What this all means is that the order of the routes is very important. - If the ``blog_show`` route were placed above the ``blog`` route, the - URL ``/blog/2`` would match ``blog_show`` instead of ``blog`` since the - ``{slug}`` parameter of ``blog_show`` has no requirements. By using proper - ordering and clever requirements, you can accomplish just about anything. + 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 @@ -271,6 +169,8 @@ Path Parameters .. index:: single: Routing; Method requirement +.. _routing-method-requirement: + Adding HTTP Method Requirements ------------------------------- @@ -369,6 +269,12 @@ two actions. 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 -------------------------