Repository: symfony/symfony-docs
Pull request #20956: [Routing] Tell about `{foo:bar}` mapping syntax
Author: login: nicolas-grekas
Labels: name: Routing
name: Status: Reviewed
Created: 2025-05-12T13:22:53Z | Merged: 2025-05-20T07:17:35Z

Fix #20806
Fix #19846
Fix #19848
Replaces #19869
Replaces #20812

Linked issue #20806: [Routing] Add alias in `{foo:bar}` syntax in route parameter
| Q            | A
| ------------ | ---
| Feature PR   | symfony/symfony#59904
| PR author(s) | @eltharin
| Merged in    | 7.3

We created this issue to not forget to document this new feature. We would really appreciate if you can help us with this task. If you are not sure how to do it, please ask us and we will help you.

To fix this issue, please create a PR against the 7.3 branch in the [symfony-docs repository](https://github.com/symfony/symfony-docs).

Thank you! :smiley:

Diff:
diff --git a/doctrine.rst b/doctrine.rst
index 171f8a3348a..270c59a08c4 100644
--- a/doctrine.rst
+++ b/doctrine.rst
@@ -680,7 +680,7 @@ will automatically fetch them::
     /**
      * Perform a findOneBy() where the slug property matches {slug}.
      */
-    #[Route('/product/{slug}')]
+    #[Route('/product/{slug:product}')]
     public function showBySlug(Product $product): Response
     {
     }
@@ -694,14 +694,17 @@ Automatic fetching works in these situations:
   *all* of the wildcards in your route that are actually properties
   on your entity (non-properties are ignored).
 
-This behavior is enabled by default on all controllers. If you prefer, you can
-restrict this feature to only work on route wildcards called ``id`` to look for
-entities by primary key. To do so, set the option
-``doctrine.orm.controller_resolver.auto_mapping`` to ``false``.
+The ``{slug:product}`` syntax maps the route parameter named ``slug`` to the
+controller argument named ``$product``. It also hints the resolver to lookup
+by slug when loading the corresponding ``Product`` object from the database.
 
-When ``auto_mapping`` is disabled, you can configure the mapping explicitly for
-any controller argument with the ``MapEntity`` attribute. You can even control
-the ``EntityValueResolver`` behavior by using the `MapEntity options`_ ::
+.. versionadded:: 7.1
+
+    Route parameter mapping was introduced in Symfony 7.1.
+
+You can also configure the mapping explicitly for any controller argument
+with the ``MapEntity`` attribute. You can even control the
+``EntityValueResolver`` behavior by using the `MapEntity options`_ ::
 
     // src/Controller/ProductController.php
     namespace App\Controller;
@@ -812,18 +815,6 @@ control behavior:
         ): Response {
         }
 
-``exclude``
-    Configures the properties that should be used in the ``findOneBy()``
-    method by *excluding* one or more properties so that not *all* are used::
-
-        #[Route('/product/{slug}/{date}')]
-        public function show(
-            #[MapEntity(exclude: ['date'])]
-            Product $product,
-            \DateTime $date
-        ): Response {
-        }
-
 ``stripNull``
     If true, then when ``findOneBy()`` is used, any values that are
     ``null`` will not be used for the query.
diff --git a/routing.rst b/routing.rst
index 24218eaf74c..587a35f269b 100644
--- a/routing.rst
+++ b/routing.rst
@@ -22,8 +22,7 @@ Creating Routes as Attributes
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 PHP attributes allow to define routes next to the code of the
-:doc:`controllers </controller>` associated to those routes. Attributes are
-native in PHP 8 and higher versions, so you can use them right away.
+:doc:`controllers </controller>` associated to those routes.
 
 You need to add a bit of configuration to your project before using them. If your
 project uses :ref:`Symfony Flex <symfony-flex>`, this file is already created for you.
@@ -707,12 +706,6 @@ URL                       Route          Parameters
     matches any uppercase character in any language, ``\p{Greek}`` matches any
     Greek characters, 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<requirements>}``. This feature makes configuration more
 concise, but it can decrease route readability when requirements are complex:
@@ -998,7 +991,7 @@ controller action. Instead of ``string $slug``, add ``BlogPost $post``::
     {
         // ...
 
-        #[Route('/blog/{slug}', name: 'blog_show')]
+        #[Route('/blog/{slug:post}', name: 'blog_show')]
         public function show(BlogPost $post): Response
         {
             // $post is the object whose slug matches the routing parameter
@@ -1012,9 +1005,37 @@ this case), the "param converter" makes a database request to find the object
 using the request parameters (``slug`` in this case). If no object is found,
 Symfony generates a 404 response automatically.
 
+The ``{slug:post}`` syntax maps the route parameter named ``slug`` to the controller
+argument named ``$post``. It also hints the "param converter" to lookup by slug
+when loading the corresponding ``BlogPost`` object from the database.
+
+.. versionadded:: 7.1
+
+    Route parameter mapping was introduced in Symfony 7.1.
+
+When more than one entity needs to be derived from route parameters, collisions can happen.
+In the following example, the route tries to define two mappings: one to load an author by
+name, two to load a category by name. But this is not allowed because from the side of the
+route definition, this declares a parameter named "name" twice::
+
+    #[Route('/search-book/{name:author}/{name:category}')]
+
+Such routes should instead be defined using the following syntax::
+
+    #[Route('/search-book/{authorName:author.name}/{categoryName:category.name}')]
+
+This way, the route parameter names are unique (``authorName`` and ``categoryName``) and
+the "param converter" can correctly map them to controller arguments (``$author`` and
+``$category``), loading them both by their name.
+
+.. versionadded:: 7.3
+
+    This more advanced style of route parameter mapping was introduced in Symfony 7.3.
+
+More advanced mappings can be achieved using the ``#[MapEntity]`` attribute.
 Check out the :ref:`Doctrine param conversion documentation <doctrine-entity-value-resolver>`
-to learn about the ``#[MapEntity]`` attribute that can be used to customize the
-database queries used to fetch the object from the route parameter.
+to learn how to customize the database queries used to fetch the object from the route
+parameter.
 
 Backed Enum Parameters
 ~~~~~~~~~~~~~~~~~~~~~~


Reviews:
user: login: nicolas-grekas
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-12T13:38:47Z
user: login: OskarStark
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T12:02:28Z
user: login: OskarStark
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T12:02:50Z
user: login: nicolas-grekas
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-17T14:45:11Z

Review comments:
diff_hunk: @@ -22,8 +22,7 @@ Creating Routes as Attributes
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 PHP attributes allow to define routes next to the code of the
-:doc:`controllers </controller>` associated to those routes. Attributes are
-native in PHP 8 and higher versions, so you can use them right away.
+:doc:`controllers </controller>` associated to those routes.
path: routing.rst
user: login: nicolas-grekas
body: PHP 8 is the past now, the removed sentence feels outdated
created_at: 2025-05-12T13:36:19Z
author_association: MEMBER
diff_hunk: @@ -707,12 +706,6 @@ URL                       Route          Parameters
     matches any uppercase character in any language, ``\p{Greek}`` matches any
     Greek characters, 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.
path: routing.rst
user: login: nicolas-grekas
body: utf8 is enabled by default since a few years now
created_at: 2025-05-12T13:36:37Z
author_association: MEMBER
diff_hunk: @@ -998,7 +991,7 @@ controller action. Instead of ``string $slug``, add ``BlogPost $post``::
     {
         // ...
 
-        #[Route('/blog/{slug}', name: 'blog_show')]
+        #[Route('/blog/{slug:post}', name: 'blog_show')]
path: routing.rst
user: login: nicolas-grekas
body: the example is using deprecated implicit mapping that doesn't work anymore with the default recipes since 7.1
created_at: 2025-05-12T13:37:21Z
author_association: MEMBER
diff_hunk: @@ -1012,9 +1005,37 @@ this case), the "param converter" makes a database request to find the object
 using the request parameters (``slug`` in this case). If no object is found,
 Symfony generates a 404 response automatically.
 
+The ``{slug:post}`` syntax maps the route parameter named ``slug`` to the controller
+argument named ``$blog``. It also hints the "param converter" to lookup by slug
path: routing.rst
user: login: OskarStark
body: ```suggestion
argument named ``$post``. It also hints the "param converter" to lookup by slug
```
?
created_at: 2025-05-13T12:02:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1012,9 +1005,37 @@ this case), the "param converter" makes a database request to find the object
 using the request parameters (``slug`` in this case). If no object is found,
 Symfony generates a 404 response automatically.
 
+The ``{slug:post}`` syntax maps the route parameter named ``slug`` to the controller
+argument named ``$blog``. It also hints the "param converter" to lookup by slug
+when loading the corresponding ``BlogPost`` object from the database.
+
+.. versionadded:: 7.1
+
+    Route parameter mapping was introduced in Symfony 7.1.
+
+When more than one entity needs to be derived from route parameters, collisions can happen.
+In the following example, the route tries to define two mappings: one to load an author by
+name, two to load a category by name. But this is not allowed because from the side of the
+route definition, this declares a parameter named "name" twice::
+
+    #[Route('/search-book/{name:author}/{name:category}')]
+
+Such routes should instead be defined using the following syntax::
+
+    #[Route('/search-book/{authorName:author.name}/{categoryName:category.name}')]
+
+This way, the route parameter names are unique (``authorName`` and ``categoryName``) and
+the "param converter" can correctly map them to controller arguments (``$author`` and
+``$category``), loading them both by their name.
+
+.. versionadded:: 7.3
+
+    This more advanced style of route parameter mapping was introduced in Symfony 7.3.
path: routing.rst
user: login: OskarStark
body: This cannot be merged into 7.2 branch then
created_at: 2025-05-13T12:02:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -812,18 +815,6 @@ control behavior:
         ): Response {
         }
 
-``exclude``
path: doctrine.rst
user: login: nicolas-grekas
body: there's no use case for `exclude` anymore IIUC, we could even deprecate it (but it's not strictly need either)
created_at: 2025-05-17T14:45:11Z
author_association: MEMBER

PR comments:
user: login: nicolas-grekas
created_at: 2025-05-13T12:06:13Z
body: Thanks, typo fixed and target updated for 7.3
author_association: MEMBER
user: login: xabbuh
created_at: 2025-05-18T11:12:52Z
body: Does this also make #20638  obsolet?
author_association: MEMBER
user: login: nicolas-grekas
created_at: 2025-05-18T11:17:27Z
body: @xabbuh it's something else
author_association: MEMBER
user: login: javiereguiluz
created_at: 2025-05-20T07:23:46Z
body: Nicolas, thanks a lot for this contribution!

While merging in 7.2, we removed the contents related to the 7.3 feature but readded those back when upmerging in 7.3 branch. Thanks!
author_association: MEMBER