diff --git a/.doctor-rst.yaml b/.doctor-rst.yaml
index 0768119d255..dfb85021586 100644
--- a/.doctor-rst.yaml
+++ b/.doctor-rst.yaml
@@ -8,6 +8,7 @@ rules:
     correct_code_block_directive_based_on_the_content: ~
     deprecated_directive_should_have_version: ~
     ensure_bash_prompt_before_composer_command: ~
+    ensure_class_constant: ~
     ensure_correct_format_for_phpfunction: ~
     ensure_exactly_one_space_before_directive_type: ~
     ensure_exactly_one_space_between_link_definition_and_link: ~
@@ -41,6 +42,7 @@ rules:
     no_composer_req: ~
     no_directive_after_shorthand: ~
     no_duplicate_use_statements: ~
+    no_empty_literals: ~
     no_explicit_use_of_code_block_php: ~
     no_footnotes: ~
     no_inheritdoc: ~
@@ -48,6 +50,7 @@ rules:
     no_namespace_after_use_statements: ~
     no_php_open_tag_in_code_block_php_directive: ~
     no_space_before_self_xml_closing_tag: ~
+    no_typographic_quotes: ~
     non_static_phpunit_assertions: ~
     only_backslashes_in_namespace_in_php_code_block: ~
     only_backslashes_in_use_statements_in_php_code_block: ~
@@ -71,7 +74,6 @@ rules:
     versionadded_directive_should_have_version: ~
     yaml_instead_of_yml_suffix: ~
 
-    #   master
     versionadded_directive_major_version:
         major_version: 7
 
@@ -118,3 +120,4 @@ whitelist:
         - '.. _`a feature to test applications using Mercure`: https://github.com/symfony/panther#creating-isolated-browsers-to-test-apps-using-mercure-or-websocket'
         - 'End to End Tests (E2E)'
         - '.. versionadded:: 2.2.0' # Panther
+        - '* Inline code blocks use double-ticks (````like this````).'
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 061b0bb85b0..42770d55fe3 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -26,9 +26,8 @@ jobs:
             -   name: "Set-up PHP"
                 uses: shivammathur/setup-php@v2
                 with:
-                    php-version: 8.2
+                    php-version: 8.4
                     coverage: none
-                    tools: "composer:v2"
 
             -   name: Get composer cache directory
                 id: composercache
@@ -73,7 +72,7 @@ jobs:
                     key: ${{ runner.os }}-doctor-rst-${{ steps.extract_base_branch.outputs.branch }}
 
             -   name: "Run DOCtor-RST"
-                uses: docker://oskarstark/doctor-rst:1.64.0
+                uses: docker://oskarstark/doctor-rst:1.70.0
                 with:
                     args: --short --error-format=github --cache-file=/github/workspace/.cache/doctor-rst.cache
 
@@ -93,7 +92,7 @@ jobs:
             -   name: Set-up PHP
                 uses: shivammathur/setup-php@v2
                 with:
-                    php-version: 8.2
+                    php-version: 8.4
                     coverage: none
 
             -   name: Fetch branch from where the PR started
diff --git a/_build/build.php b/_build/build.php
index 5298abe779a..b684700a848 100755
--- a/_build/build.php
+++ b/_build/build.php
@@ -15,6 +15,13 @@
     ->addOption('generate-fjson-files', null, InputOption::VALUE_NONE, 'Use this option to generate docs both in HTML and JSON formats')
     ->addOption('disable-cache', null, InputOption::VALUE_NONE, 'Use this option to force a full regeneration of all doc contents')
     ->setCode(function(InputInterface $input, OutputInterface $output) {
+        // the doc building app doesn't work on Windows
+        if ('\\' === DIRECTORY_SEPARATOR) {
+            $output->writeln('<error>ERROR: The application that builds Symfony Docs does not support Windows. You can try using a Linux distribution via WSL (Windows Subsystem for Linux).</error>');
+
+            return 1;
+        }
+
         $io = new SymfonyStyle($input, $output);
         $io->text('Building all Symfony Docs...');
 
diff --git a/_build/composer.json b/_build/composer.json
index e09d79de52f..f77976b10f4 100644
--- a/_build/composer.json
+++ b/_build/composer.json
@@ -3,7 +3,7 @@
     "prefer-stable": true,
     "config": {
         "platform": {
-            "php": "8.1.0"
+            "php": "8.3"
         },
         "preferred-install": {
             "*": "dist"
@@ -14,9 +14,9 @@
         }
     },
     "require": {
-        "php": ">=8.1",
+        "php": ">=8.3",
         "symfony/console": "^6.2",
         "symfony/process": "^6.2",
-        "symfony-tools/docs-builder": "^0.21"
+        "symfony-tools/docs-builder": "^0.27"
     }
 }
diff --git a/_build/composer.lock b/_build/composer.lock
index 89a4e7da3c6..b9a4646f8ae 100644
--- a/_build/composer.lock
+++ b/_build/composer.lock
@@ -4,77 +4,33 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "8a771cef10c68c570bff7875e4bdece3",
+    "content-hash": "e38eca557458275428db96db370d2c74",
     "packages": [
-        {
-            "name": "doctrine/deprecations",
-            "version": "v1.0.0",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/doctrine/deprecations.git",
-                "reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/doctrine/deprecations/zipball/0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de",
-                "reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de",
-                "shasum": ""
-            },
-            "require": {
-                "php": "^7.1|^8.0"
-            },
-            "require-dev": {
-                "doctrine/coding-standard": "^9",
-                "phpunit/phpunit": "^7.5|^8.5|^9.5",
-                "psr/log": "^1|^2|^3"
-            },
-            "suggest": {
-                "psr/log": "Allows logging deprecations via PSR-3 logger implementation"
-            },
-            "type": "library",
-            "autoload": {
-                "psr-4": {
-                    "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.",
-            "homepage": "https://www.doctrine-project.org/",
-            "support": {
-                "issues": "https://github.com/doctrine/deprecations/issues",
-                "source": "https://github.com/doctrine/deprecations/tree/v1.0.0"
-            },
-            "time": "2022-05-02T15:47:09+00:00"
-        },
         {
             "name": "doctrine/event-manager",
-            "version": "1.2.0",
+            "version": "2.0.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/doctrine/event-manager.git",
-                "reference": "95aa4cb529f1e96576f3fda9f5705ada4056a520"
+                "reference": "b680156fa328f1dfd874fd48c7026c41570b9c6e"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/doctrine/event-manager/zipball/95aa4cb529f1e96576f3fda9f5705ada4056a520",
-                "reference": "95aa4cb529f1e96576f3fda9f5705ada4056a520",
+                "url": "https://api.github.com/repos/doctrine/event-manager/zipball/b680156fa328f1dfd874fd48c7026c41570b9c6e",
+                "reference": "b680156fa328f1dfd874fd48c7026c41570b9c6e",
                 "shasum": ""
             },
             "require": {
-                "doctrine/deprecations": "^0.5.3 || ^1",
-                "php": "^7.1 || ^8.0"
+                "php": "^8.1"
             },
             "conflict": {
                 "doctrine/common": "<2.9"
             },
             "require-dev": {
-                "doctrine/coding-standard": "^9 || ^10",
-                "phpstan/phpstan": "~1.4.10 || ^1.8.8",
-                "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
-                "vimeo/psalm": "^4.24"
+                "doctrine/coding-standard": "^12",
+                "phpstan/phpstan": "^1.8.8",
+                "phpunit/phpunit": "^10.5",
+                "vimeo/psalm": "^5.24"
             },
             "type": "library",
             "autoload": {
@@ -123,7 +79,7 @@
             ],
             "support": {
                 "issues": "https://github.com/doctrine/event-manager/issues",
-                "source": "https://github.com/doctrine/event-manager/tree/1.2.0"
+                "source": "https://github.com/doctrine/event-manager/tree/2.0.1"
             },
             "funding": [
                 {
@@ -139,42 +95,42 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2022-10-12T20:51:15+00:00"
+            "time": "2024-05-22T20:47:39+00:00"
         },
         {
             "name": "doctrine/rst-parser",
-            "version": "0.5.3",
+            "version": "0.5.6",
             "source": {
                 "type": "git",
                 "url": "https://github.com/doctrine/rst-parser.git",
-                "reference": "0b1d413d6bb27699ccec1151da6f617554d02c13"
+                "reference": "ca7f5f31f9ea58fde5aeffe0f7b8eb569e71a104"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/doctrine/rst-parser/zipball/0b1d413d6bb27699ccec1151da6f617554d02c13",
-                "reference": "0b1d413d6bb27699ccec1151da6f617554d02c13",
+                "url": "https://api.github.com/repos/doctrine/rst-parser/zipball/ca7f5f31f9ea58fde5aeffe0f7b8eb569e71a104",
+                "reference": "ca7f5f31f9ea58fde5aeffe0f7b8eb569e71a104",
                 "shasum": ""
             },
             "require": {
-                "doctrine/event-manager": "^1.0",
+                "doctrine/event-manager": "^1.0 || ^2.0",
                 "php": "^7.2 || ^8.0",
-                "symfony/filesystem": "^4.1 || ^5.0 || ^6.0",
-                "symfony/finder": "^4.1 || ^5.0 || ^6.0",
+                "symfony/filesystem": "^4.1 || ^5.0 || ^6.0 || ^7.0",
+                "symfony/finder": "^4.1 || ^5.0 || ^6.0 || ^7.0",
                 "symfony/polyfill-mbstring": "^1.0",
-                "symfony/string": "^5.3 || ^6.0",
-                "symfony/translation-contracts": "^1.1 || ^2.0",
+                "symfony/string": "^5.3 || ^6.0 || ^7.0",
+                "symfony/translation-contracts": "^1.1 || ^2.0 || ^3.0",
                 "twig/twig": "^2.9 || ^3.3"
             },
             "require-dev": {
-                "doctrine/coding-standard": "^10.0",
+                "doctrine/coding-standard": "^11.0",
                 "gajus/dindent": "^2.0.2",
                 "phpstan/phpstan": "^1.9",
                 "phpstan/phpstan-deprecation-rules": "^1.0",
                 "phpstan/phpstan-phpunit": "^1.2",
                 "phpstan/phpstan-strict-rules": "^1.4",
                 "phpunit/phpunit": "^7.5 || ^8.0 || ^9.0",
-                "symfony/css-selector": "4.4 || ^5.2 || ^6.0",
-                "symfony/dom-crawler": "4.4 || ^5.2 || ^6.0"
+                "symfony/css-selector": "4.4 || ^5.2 || ^6.0 || ^7.0",
+                "symfony/dom-crawler": "4.4 || ^5.2 || ^6.0 || ^7.0"
             },
             "type": "library",
             "autoload": {
@@ -210,32 +166,30 @@
             ],
             "support": {
                 "issues": "https://github.com/doctrine/rst-parser/issues",
-                "source": "https://github.com/doctrine/rst-parser/tree/0.5.3"
+                "source": "https://github.com/doctrine/rst-parser/tree/0.5.6"
             },
-            "time": "2022-12-29T16:24:52+00:00"
+            "time": "2024-01-14T11:02:23+00:00"
         },
         {
             "name": "masterminds/html5",
-            "version": "2.7.6",
+            "version": "2.9.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/Masterminds/html5-php.git",
-                "reference": "897eb517a343a2281f11bc5556d6548db7d93947"
+                "reference": "f5ac2c0b0a2eefca70b2ce32a5809992227e75a6"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/897eb517a343a2281f11bc5556d6548db7d93947",
-                "reference": "897eb517a343a2281f11bc5556d6548db7d93947",
+                "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/f5ac2c0b0a2eefca70b2ce32a5809992227e75a6",
+                "reference": "f5ac2c0b0a2eefca70b2ce32a5809992227e75a6",
                 "shasum": ""
             },
             "require": {
-                "ext-ctype": "*",
                 "ext-dom": "*",
-                "ext-libxml": "*",
                 "php": ">=5.3.0"
             },
             "require-dev": {
-                "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7"
+                "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9"
             },
             "type": "library",
             "extra": {
@@ -279,9 +233,9 @@
             ],
             "support": {
                 "issues": "https://github.com/Masterminds/html5-php/issues",
-                "source": "https://github.com/Masterminds/html5-php/tree/2.7.6"
+                "source": "https://github.com/Masterminds/html5-php/tree/2.9.0"
             },
-            "time": "2022-08-18T16:18:26+00:00"
+            "time": "2024-03-31T07:05:07+00:00"
         },
         {
             "name": "psr/container",
@@ -338,16 +292,16 @@
         },
         {
             "name": "psr/log",
-            "version": "3.0.0",
+            "version": "3.0.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/php-fig/log.git",
-                "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001"
+                "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001",
-                "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001",
+                "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3",
+                "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3",
                 "shasum": ""
             },
             "require": {
@@ -382,9 +336,9 @@
                 "psr-3"
             ],
             "support": {
-                "source": "https://github.com/php-fig/log/tree/3.0.0"
+                "source": "https://github.com/php-fig/log/tree/3.0.2"
             },
-            "time": "2021-07-14T16:46:02+00:00"
+            "time": "2024-09-11T13:17:53+00:00"
         },
         {
             "name": "scrivo/highlight.php",
@@ -466,37 +420,37 @@
         },
         {
             "name": "symfony-tools/docs-builder",
-            "version": "v0.21.0",
+            "version": "0.27.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony-tools/docs-builder.git",
-                "reference": "7ab92db15e9be7d6af51b86db87c7e41a14ba18b"
+                "reference": "720b52b2805122a4c08376496bd9661944c2624a"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony-tools/docs-builder/zipball/7ab92db15e9be7d6af51b86db87c7e41a14ba18b",
-                "reference": "7ab92db15e9be7d6af51b86db87c7e41a14ba18b",
+                "url": "https://api.github.com/repos/symfony-tools/docs-builder/zipball/720b52b2805122a4c08376496bd9661944c2624a",
+                "reference": "720b52b2805122a4c08376496bd9661944c2624a",
                 "shasum": ""
             },
             "require": {
                 "doctrine/rst-parser": "^0.5",
                 "ext-curl": "*",
                 "ext-json": "*",
-                "php": ">=7.4",
-                "scrivo/highlight.php": "^9.12.0",
-                "symfony/console": "^5.2 || ^6.0",
-                "symfony/css-selector": "^5.2 || ^6.0",
-                "symfony/dom-crawler": "^5.2 || ^6.0",
-                "symfony/filesystem": "^5.2 || ^6.0",
-                "symfony/finder": "^5.2 || ^6.0",
-                "symfony/http-client": "^5.2 || ^6.0",
+                "php": ">=8.3",
+                "scrivo/highlight.php": "^9.18.1",
+                "symfony/console": "^5.2 || ^6.0 || ^7.0",
+                "symfony/css-selector": "^5.2 || ^6.0 || ^7.0",
+                "symfony/dom-crawler": "^5.2 || ^6.0 || ^7.0",
+                "symfony/filesystem": "^5.2 || ^6.0 || ^7.0",
+                "symfony/finder": "^5.2 || ^6.0 || ^7.0",
+                "symfony/http-client": "^5.2 || ^6.0 || ^7.0",
                 "twig/twig": "^2.14 || ^3.3"
             },
             "require-dev": {
                 "gajus/dindent": "^2.0",
                 "masterminds/html5": "^2.7",
-                "symfony/phpunit-bridge": "^5.2 || ^6.0",
-                "symfony/process": "^5.2 || ^6.0"
+                "symfony/phpunit-bridge": "^5.2 || ^6.0 || ^7.0",
+                "symfony/process": "^5.2 || ^6.0 || ^7.0"
             },
             "bin": [
                 "bin/docs-builder"
@@ -514,30 +468,30 @@
             "description": "The build system for Symfony's documentation",
             "support": {
                 "issues": "https://github.com/symfony-tools/docs-builder/issues",
-                "source": "https://github.com/symfony-tools/docs-builder/tree/v0.21.0"
+                "source": "https://github.com/symfony-tools/docs-builder/tree/0.27.0"
             },
-            "time": "2023-07-11T15:21:07+00:00"
+            "time": "2025-03-21T09:48:45+00:00"
         },
         {
             "name": "symfony/console",
-            "version": "v6.2.8",
+            "version": "v6.4.17",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/console.git",
-                "reference": "3582d68a64a86ec25240aaa521ec8bc2342b369b"
+                "reference": "799445db3f15768ecc382ac5699e6da0520a0a04"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/console/zipball/3582d68a64a86ec25240aaa521ec8bc2342b369b",
-                "reference": "3582d68a64a86ec25240aaa521ec8bc2342b369b",
+                "url": "https://api.github.com/repos/symfony/console/zipball/799445db3f15768ecc382ac5699e6da0520a0a04",
+                "reference": "799445db3f15768ecc382ac5699e6da0520a0a04",
                 "shasum": ""
             },
             "require": {
                 "php": ">=8.1",
-                "symfony/deprecation-contracts": "^2.1|^3",
+                "symfony/deprecation-contracts": "^2.5|^3",
                 "symfony/polyfill-mbstring": "~1.0",
-                "symfony/service-contracts": "^1.1|^2|^3",
-                "symfony/string": "^5.4|^6.0"
+                "symfony/service-contracts": "^2.5|^3",
+                "symfony/string": "^5.4|^6.0|^7.0"
             },
             "conflict": {
                 "symfony/dependency-injection": "<5.4",
@@ -551,18 +505,16 @@
             },
             "require-dev": {
                 "psr/log": "^1|^2|^3",
-                "symfony/config": "^5.4|^6.0",
-                "symfony/dependency-injection": "^5.4|^6.0",
-                "symfony/event-dispatcher": "^5.4|^6.0",
-                "symfony/lock": "^5.4|^6.0",
-                "symfony/process": "^5.4|^6.0",
-                "symfony/var-dumper": "^5.4|^6.0"
-            },
-            "suggest": {
-                "psr/log": "For using the console logger",
-                "symfony/event-dispatcher": "",
-                "symfony/lock": "",
-                "symfony/process": ""
+                "symfony/config": "^5.4|^6.0|^7.0",
+                "symfony/dependency-injection": "^5.4|^6.0|^7.0",
+                "symfony/event-dispatcher": "^5.4|^6.0|^7.0",
+                "symfony/http-foundation": "^6.4|^7.0",
+                "symfony/http-kernel": "^6.4|^7.0",
+                "symfony/lock": "^5.4|^6.0|^7.0",
+                "symfony/messenger": "^5.4|^6.0|^7.0",
+                "symfony/process": "^5.4|^6.0|^7.0",
+                "symfony/stopwatch": "^5.4|^6.0|^7.0",
+                "symfony/var-dumper": "^5.4|^6.0|^7.0"
             },
             "type": "library",
             "autoload": {
@@ -596,7 +548,7 @@
                 "terminal"
             ],
             "support": {
-                "source": "https://github.com/symfony/console/tree/v6.2.8"
+                "source": "https://github.com/symfony/console/tree/v6.4.17"
             },
             "funding": [
                 {
@@ -612,24 +564,24 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2023-03-29T21:42:15+00:00"
+            "time": "2024-12-07T12:07:30+00:00"
         },
         {
             "name": "symfony/css-selector",
-            "version": "v6.2.7",
+            "version": "v7.2.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/css-selector.git",
-                "reference": "aedf3cb0f5b929ec255d96bbb4909e9932c769e0"
+                "reference": "601a5ce9aaad7bf10797e3663faefce9e26c24e2"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/css-selector/zipball/aedf3cb0f5b929ec255d96bbb4909e9932c769e0",
-                "reference": "aedf3cb0f5b929ec255d96bbb4909e9932c769e0",
+                "url": "https://api.github.com/repos/symfony/css-selector/zipball/601a5ce9aaad7bf10797e3663faefce9e26c24e2",
+                "reference": "601a5ce9aaad7bf10797e3663faefce9e26c24e2",
                 "shasum": ""
             },
             "require": {
-                "php": ">=8.1"
+                "php": ">=8.2"
             },
             "type": "library",
             "autoload": {
@@ -661,7 +613,7 @@
             "description": "Converts CSS selectors to XPath expressions",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/css-selector/tree/v6.2.7"
+                "source": "https://github.com/symfony/css-selector/tree/v7.2.0"
             },
             "funding": [
                 {
@@ -677,20 +629,20 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2023-02-14T08:44:56+00:00"
+            "time": "2024-09-25T14:21:43+00:00"
         },
         {
             "name": "symfony/deprecation-contracts",
-            "version": "v3.2.1",
+            "version": "v3.5.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/deprecation-contracts.git",
-                "reference": "e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e"
+                "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e",
-                "reference": "e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e",
+                "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6",
+                "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6",
                 "shasum": ""
             },
             "require": {
@@ -698,12 +650,12 @@
             },
             "type": "library",
             "extra": {
-                "branch-alias": {
-                    "dev-main": "3.3-dev"
-                },
                 "thanks": {
-                    "name": "symfony/contracts",
-                    "url": "https://github.com/symfony/contracts"
+                    "url": "https://github.com/symfony/contracts",
+                    "name": "symfony/contracts"
+                },
+                "branch-alias": {
+                    "dev-main": "3.5-dev"
                 }
             },
             "autoload": {
@@ -728,7 +680,7 @@
             "description": "A generic function and convention to trigger deprecation notices",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/deprecation-contracts/tree/v3.2.1"
+                "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.1"
             },
             "funding": [
                 {
@@ -744,33 +696,30 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2023-03-01T10:25:55+00:00"
+            "time": "2024-09-25T14:20:29+00:00"
         },
         {
             "name": "symfony/dom-crawler",
-            "version": "v6.2.8",
+            "version": "v7.2.4",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/dom-crawler.git",
-                "reference": "0e0d0f709997ad1224ef22bb0a28287c44b7840f"
+                "reference": "19cc7b08efe9ad1ab1b56e0948e8d02e15ed3ef7"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/0e0d0f709997ad1224ef22bb0a28287c44b7840f",
-                "reference": "0e0d0f709997ad1224ef22bb0a28287c44b7840f",
+                "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/19cc7b08efe9ad1ab1b56e0948e8d02e15ed3ef7",
+                "reference": "19cc7b08efe9ad1ab1b56e0948e8d02e15ed3ef7",
                 "shasum": ""
             },
             "require": {
                 "masterminds/html5": "^2.6",
-                "php": ">=8.1",
+                "php": ">=8.2",
                 "symfony/polyfill-ctype": "~1.8",
                 "symfony/polyfill-mbstring": "~1.0"
             },
             "require-dev": {
-                "symfony/css-selector": "^5.4|^6.0"
-            },
-            "suggest": {
-                "symfony/css-selector": ""
+                "symfony/css-selector": "^6.4|^7.0"
             },
             "type": "library",
             "autoload": {
@@ -798,7 +747,7 @@
             "description": "Eases DOM navigation for HTML and XML documents",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/dom-crawler/tree/v6.2.8"
+                "source": "https://github.com/symfony/dom-crawler/tree/v7.2.4"
             },
             "funding": [
                 {
@@ -814,27 +763,30 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2023-03-09T16:20:02+00:00"
+            "time": "2025-02-17T15:53:07+00:00"
         },
         {
             "name": "symfony/filesystem",
-            "version": "v6.2.7",
+            "version": "v7.2.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/filesystem.git",
-                "reference": "82b6c62b959f642d000456f08c6d219d749215b3"
+                "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/filesystem/zipball/82b6c62b959f642d000456f08c6d219d749215b3",
-                "reference": "82b6c62b959f642d000456f08c6d219d749215b3",
+                "url": "https://api.github.com/repos/symfony/filesystem/zipball/b8dce482de9d7c9fe2891155035a7248ab5c7fdb",
+                "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb",
                 "shasum": ""
             },
             "require": {
-                "php": ">=8.1",
+                "php": ">=8.2",
                 "symfony/polyfill-ctype": "~1.8",
                 "symfony/polyfill-mbstring": "~1.8"
             },
+            "require-dev": {
+                "symfony/process": "^6.4|^7.0"
+            },
             "type": "library",
             "autoload": {
                 "psr-4": {
@@ -861,7 +813,7 @@
             "description": "Provides basic utilities for the filesystem",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/filesystem/tree/v6.2.7"
+                "source": "https://github.com/symfony/filesystem/tree/v7.2.0"
             },
             "funding": [
                 {
@@ -877,27 +829,27 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2023-02-14T08:44:56+00:00"
+            "time": "2024-10-25T15:15:23+00:00"
         },
         {
             "name": "symfony/finder",
-            "version": "v6.2.7",
+            "version": "v7.2.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/finder.git",
-                "reference": "20808dc6631aecafbe67c186af5dcb370be3a0eb"
+                "reference": "87a71856f2f56e4100373e92529eed3171695cfb"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/finder/zipball/20808dc6631aecafbe67c186af5dcb370be3a0eb",
-                "reference": "20808dc6631aecafbe67c186af5dcb370be3a0eb",
+                "url": "https://api.github.com/repos/symfony/finder/zipball/87a71856f2f56e4100373e92529eed3171695cfb",
+                "reference": "87a71856f2f56e4100373e92529eed3171695cfb",
                 "shasum": ""
             },
             "require": {
-                "php": ">=8.1"
+                "php": ">=8.2"
             },
             "require-dev": {
-                "symfony/filesystem": "^6.0"
+                "symfony/filesystem": "^6.4|^7.0"
             },
             "type": "library",
             "autoload": {
@@ -925,7 +877,7 @@
             "description": "Finds files and directories via an intuitive fluent interface",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/finder/tree/v6.2.7"
+                "source": "https://github.com/symfony/finder/tree/v7.2.2"
             },
             "funding": [
                 {
@@ -941,28 +893,33 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2023-02-16T09:57:23+00:00"
+            "time": "2024-12-30T19:00:17+00:00"
         },
         {
             "name": "symfony/http-client",
-            "version": "v6.2.8",
+            "version": "v7.2.4",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/http-client.git",
-                "reference": "66391ba3a8862c560e1d9134c96d9bd2a619b477"
+                "reference": "78981a2ffef6437ed92d4d7e2a86a82f256c6dc6"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/http-client/zipball/66391ba3a8862c560e1d9134c96d9bd2a619b477",
-                "reference": "66391ba3a8862c560e1d9134c96d9bd2a619b477",
+                "url": "https://api.github.com/repos/symfony/http-client/zipball/78981a2ffef6437ed92d4d7e2a86a82f256c6dc6",
+                "reference": "78981a2ffef6437ed92d4d7e2a86a82f256c6dc6",
                 "shasum": ""
             },
             "require": {
-                "php": ">=8.1",
+                "php": ">=8.2",
                 "psr/log": "^1|^2|^3",
-                "symfony/deprecation-contracts": "^2.1|^3",
-                "symfony/http-client-contracts": "^3",
-                "symfony/service-contracts": "^1.0|^2|^3"
+                "symfony/deprecation-contracts": "^2.5|^3",
+                "symfony/http-client-contracts": "~3.4.4|^3.5.2",
+                "symfony/service-contracts": "^2.5|^3"
+            },
+            "conflict": {
+                "amphp/amp": "<2.5",
+                "php-http/discovery": "<1.15",
+                "symfony/http-foundation": "<6.4"
             },
             "provide": {
                 "php-http/async-client-implementation": "*",
@@ -971,18 +928,20 @@
                 "symfony/http-client-implementation": "3.0"
             },
             "require-dev": {
-                "amphp/amp": "^2.5",
-                "amphp/http-client": "^4.2.1",
-                "amphp/http-tunnel": "^1.0",
+                "amphp/http-client": "^4.2.1|^5.0",
+                "amphp/http-tunnel": "^1.0|^2.0",
                 "amphp/socket": "^1.1",
-                "guzzlehttp/promises": "^1.4",
+                "guzzlehttp/promises": "^1.4|^2.0",
                 "nyholm/psr7": "^1.0",
                 "php-http/httplug": "^1.0|^2.0",
                 "psr/http-client": "^1.0",
-                "symfony/dependency-injection": "^5.4|^6.0",
-                "symfony/http-kernel": "^5.4|^6.0",
-                "symfony/process": "^5.4|^6.0",
-                "symfony/stopwatch": "^5.4|^6.0"
+                "symfony/amphp-http-client-meta": "^1.0|^2.0",
+                "symfony/dependency-injection": "^6.4|^7.0",
+                "symfony/http-kernel": "^6.4|^7.0",
+                "symfony/messenger": "^6.4|^7.0",
+                "symfony/process": "^6.4|^7.0",
+                "symfony/rate-limiter": "^6.4|^7.0",
+                "symfony/stopwatch": "^6.4|^7.0"
             },
             "type": "library",
             "autoload": {
@@ -1013,7 +972,7 @@
                 "http"
             ],
             "support": {
-                "source": "https://github.com/symfony/http-client/tree/v6.2.8"
+                "source": "https://github.com/symfony/http-client/tree/v7.2.4"
             },
             "funding": [
                 {
@@ -1029,36 +988,33 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2023-03-31T09:14:44+00:00"
+            "time": "2025-02-13T10:27:23+00:00"
         },
         {
             "name": "symfony/http-client-contracts",
-            "version": "v3.2.1",
+            "version": "v3.5.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/http-client-contracts.git",
-                "reference": "df2ecd6cb70e73c1080e6478aea85f5f4da2c48b"
+                "reference": "ee8d807ab20fcb51267fdace50fbe3494c31e645"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/df2ecd6cb70e73c1080e6478aea85f5f4da2c48b",
-                "reference": "df2ecd6cb70e73c1080e6478aea85f5f4da2c48b",
+                "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/ee8d807ab20fcb51267fdace50fbe3494c31e645",
+                "reference": "ee8d807ab20fcb51267fdace50fbe3494c31e645",
                 "shasum": ""
             },
             "require": {
                 "php": ">=8.1"
             },
-            "suggest": {
-                "symfony/http-client-implementation": ""
-            },
             "type": "library",
             "extra": {
-                "branch-alias": {
-                    "dev-main": "3.3-dev"
-                },
                 "thanks": {
-                    "name": "symfony/contracts",
-                    "url": "https://github.com/symfony/contracts"
+                    "url": "https://github.com/symfony/contracts",
+                    "name": "symfony/contracts"
+                },
+                "branch-alias": {
+                    "dev-main": "3.5-dev"
                 }
             },
             "autoload": {
@@ -1094,7 +1050,7 @@
                 "standards"
             ],
             "support": {
-                "source": "https://github.com/symfony/http-client-contracts/tree/v3.2.1"
+                "source": "https://github.com/symfony/http-client-contracts/tree/v3.5.2"
             },
             "funding": [
                 {
@@ -1110,24 +1066,24 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2023-03-01T10:32:47+00:00"
+            "time": "2024-12-07T08:49:48+00:00"
         },
         {
             "name": "symfony/polyfill-ctype",
-            "version": "v1.27.0",
+            "version": "v1.31.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-ctype.git",
-                "reference": "5bbc823adecdae860bb64756d639ecfec17b050a"
+                "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/5bbc823adecdae860bb64756d639ecfec17b050a",
-                "reference": "5bbc823adecdae860bb64756d639ecfec17b050a",
+                "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638",
+                "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638",
                 "shasum": ""
             },
             "require": {
-                "php": ">=7.1"
+                "php": ">=7.2"
             },
             "provide": {
                 "ext-ctype": "*"
@@ -1137,12 +1093,9 @@
             },
             "type": "library",
             "extra": {
-                "branch-alias": {
-                    "dev-main": "1.27-dev"
-                },
                 "thanks": {
-                    "name": "symfony/polyfill",
-                    "url": "https://github.com/symfony/polyfill"
+                    "url": "https://github.com/symfony/polyfill",
+                    "name": "symfony/polyfill"
                 }
             },
             "autoload": {
@@ -1176,7 +1129,7 @@
                 "portable"
             ],
             "support": {
-                "source": "https://github.com/symfony/polyfill-ctype/tree/v1.27.0"
+                "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0"
             },
             "funding": [
                 {
@@ -1192,36 +1145,33 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2022-11-03T14:55:06+00:00"
+            "time": "2024-09-09T11:45:10+00:00"
         },
         {
             "name": "symfony/polyfill-intl-grapheme",
-            "version": "v1.27.0",
+            "version": "v1.31.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-intl-grapheme.git",
-                "reference": "511a08c03c1960e08a883f4cffcacd219b758354"
+                "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/511a08c03c1960e08a883f4cffcacd219b758354",
-                "reference": "511a08c03c1960e08a883f4cffcacd219b758354",
+                "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe",
+                "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe",
                 "shasum": ""
             },
             "require": {
-                "php": ">=7.1"
+                "php": ">=7.2"
             },
             "suggest": {
                 "ext-intl": "For best performance"
             },
             "type": "library",
             "extra": {
-                "branch-alias": {
-                    "dev-main": "1.27-dev"
-                },
                 "thanks": {
-                    "name": "symfony/polyfill",
-                    "url": "https://github.com/symfony/polyfill"
+                    "url": "https://github.com/symfony/polyfill",
+                    "name": "symfony/polyfill"
                 }
             },
             "autoload": {
@@ -1257,7 +1207,7 @@
                 "shim"
             ],
             "support": {
-                "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.27.0"
+                "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.31.0"
             },
             "funding": [
                 {
@@ -1273,36 +1223,33 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2022-11-03T14:55:06+00:00"
+            "time": "2024-09-09T11:45:10+00:00"
         },
         {
             "name": "symfony/polyfill-intl-normalizer",
-            "version": "v1.27.0",
+            "version": "v1.31.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-intl-normalizer.git",
-                "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6"
+                "reference": "3833d7255cc303546435cb650316bff708a1c75c"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/19bd1e4fcd5b91116f14d8533c57831ed00571b6",
-                "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6",
+                "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c",
+                "reference": "3833d7255cc303546435cb650316bff708a1c75c",
                 "shasum": ""
             },
             "require": {
-                "php": ">=7.1"
+                "php": ">=7.2"
             },
             "suggest": {
                 "ext-intl": "For best performance"
             },
             "type": "library",
             "extra": {
-                "branch-alias": {
-                    "dev-main": "1.27-dev"
-                },
                 "thanks": {
-                    "name": "symfony/polyfill",
-                    "url": "https://github.com/symfony/polyfill"
+                    "url": "https://github.com/symfony/polyfill",
+                    "name": "symfony/polyfill"
                 }
             },
             "autoload": {
@@ -1341,7 +1288,7 @@
                 "shim"
             ],
             "support": {
-                "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.27.0"
+                "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0"
             },
             "funding": [
                 {
@@ -1357,24 +1304,24 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2022-11-03T14:55:06+00:00"
+            "time": "2024-09-09T11:45:10+00:00"
         },
         {
             "name": "symfony/polyfill-mbstring",
-            "version": "v1.27.0",
+            "version": "v1.31.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-mbstring.git",
-                "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534"
+                "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534",
-                "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534",
+                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341",
+                "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341",
                 "shasum": ""
             },
             "require": {
-                "php": ">=7.1"
+                "php": ">=7.2"
             },
             "provide": {
                 "ext-mbstring": "*"
@@ -1384,12 +1331,9 @@
             },
             "type": "library",
             "extra": {
-                "branch-alias": {
-                    "dev-main": "1.27-dev"
-                },
                 "thanks": {
-                    "name": "symfony/polyfill",
-                    "url": "https://github.com/symfony/polyfill"
+                    "url": "https://github.com/symfony/polyfill",
+                    "name": "symfony/polyfill"
                 }
             },
             "autoload": {
@@ -1424,7 +1368,7 @@
                 "shim"
             ],
             "support": {
-                "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.27.0"
+                "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0"
             },
             "funding": [
                 {
@@ -1440,20 +1384,20 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2022-11-03T14:55:06+00:00"
+            "time": "2024-09-09T11:45:10+00:00"
         },
         {
             "name": "symfony/process",
-            "version": "v6.2.8",
+            "version": "v6.4.19",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/process.git",
-                "reference": "75ed64103df4f6615e15a7fe38b8111099f47416"
+                "reference": "7a1c12e87b08ec9c97abdd188c9b3f5a40e37fc3"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/process/zipball/75ed64103df4f6615e15a7fe38b8111099f47416",
-                "reference": "75ed64103df4f6615e15a7fe38b8111099f47416",
+                "url": "https://api.github.com/repos/symfony/process/zipball/7a1c12e87b08ec9c97abdd188c9b3f5a40e37fc3",
+                "reference": "7a1c12e87b08ec9c97abdd188c9b3f5a40e37fc3",
                 "shasum": ""
             },
             "require": {
@@ -1485,7 +1429,7 @@
             "description": "Executes commands in sub-processes",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/process/tree/v6.2.8"
+                "source": "https://github.com/symfony/process/tree/v6.4.19"
             },
             "funding": [
                 {
@@ -1501,40 +1445,38 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2023-03-09T16:20:02+00:00"
+            "time": "2025-02-04T13:35:48+00:00"
         },
         {
             "name": "symfony/service-contracts",
-            "version": "v3.2.1",
+            "version": "v3.5.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/service-contracts.git",
-                "reference": "a8c9cedf55f314f3a186041d19537303766df09a"
+                "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/service-contracts/zipball/a8c9cedf55f314f3a186041d19537303766df09a",
-                "reference": "a8c9cedf55f314f3a186041d19537303766df09a",
+                "url": "https://api.github.com/repos/symfony/service-contracts/zipball/e53260aabf78fb3d63f8d79d69ece59f80d5eda0",
+                "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0",
                 "shasum": ""
             },
             "require": {
                 "php": ">=8.1",
-                "psr/container": "^2.0"
+                "psr/container": "^1.1|^2.0",
+                "symfony/deprecation-contracts": "^2.5|^3"
             },
             "conflict": {
                 "ext-psr": "<1.1|>=2"
             },
-            "suggest": {
-                "symfony/service-implementation": ""
-            },
             "type": "library",
             "extra": {
-                "branch-alias": {
-                    "dev-main": "3.3-dev"
-                },
                 "thanks": {
-                    "name": "symfony/contracts",
-                    "url": "https://github.com/symfony/contracts"
+                    "url": "https://github.com/symfony/contracts",
+                    "name": "symfony/contracts"
+                },
+                "branch-alias": {
+                    "dev-main": "3.5-dev"
                 }
             },
             "autoload": {
@@ -1570,7 +1512,7 @@
                 "standards"
             ],
             "support": {
-                "source": "https://github.com/symfony/service-contracts/tree/v3.2.1"
+                "source": "https://github.com/symfony/service-contracts/tree/v3.5.1"
             },
             "funding": [
                 {
@@ -1586,38 +1528,39 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2023-03-01T10:32:47+00:00"
+            "time": "2024-09-25T14:20:29+00:00"
         },
         {
             "name": "symfony/string",
-            "version": "v6.2.8",
+            "version": "v7.2.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/string.git",
-                "reference": "193e83bbd6617d6b2151c37fff10fa7168ebddef"
+                "reference": "446e0d146f991dde3e73f45f2c97a9faad773c82"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/string/zipball/193e83bbd6617d6b2151c37fff10fa7168ebddef",
-                "reference": "193e83bbd6617d6b2151c37fff10fa7168ebddef",
+                "url": "https://api.github.com/repos/symfony/string/zipball/446e0d146f991dde3e73f45f2c97a9faad773c82",
+                "reference": "446e0d146f991dde3e73f45f2c97a9faad773c82",
                 "shasum": ""
             },
             "require": {
-                "php": ">=8.1",
+                "php": ">=8.2",
                 "symfony/polyfill-ctype": "~1.8",
                 "symfony/polyfill-intl-grapheme": "~1.0",
                 "symfony/polyfill-intl-normalizer": "~1.0",
                 "symfony/polyfill-mbstring": "~1.0"
             },
             "conflict": {
-                "symfony/translation-contracts": "<2.0"
+                "symfony/translation-contracts": "<2.5"
             },
             "require-dev": {
-                "symfony/error-handler": "^5.4|^6.0",
-                "symfony/http-client": "^5.4|^6.0",
-                "symfony/intl": "^6.2",
-                "symfony/translation-contracts": "^2.0|^3.0",
-                "symfony/var-exporter": "^5.4|^6.0"
+                "symfony/emoji": "^7.1",
+                "symfony/error-handler": "^6.4|^7.0",
+                "symfony/http-client": "^6.4|^7.0",
+                "symfony/intl": "^6.4|^7.0",
+                "symfony/translation-contracts": "^2.5|^3.0",
+                "symfony/var-exporter": "^6.4|^7.0"
             },
             "type": "library",
             "autoload": {
@@ -1656,7 +1599,7 @@
                 "utf8"
             ],
             "support": {
-                "source": "https://github.com/symfony/string/tree/v6.2.8"
+                "source": "https://github.com/symfony/string/tree/v7.2.0"
             },
             "funding": [
                 {
@@ -1672,42 +1615,42 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2023-03-20T16:06:02+00:00"
+            "time": "2024-11-13T13:31:26+00:00"
         },
         {
             "name": "symfony/translation-contracts",
-            "version": "v2.5.2",
+            "version": "v3.5.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/translation-contracts.git",
-                "reference": "136b19dd05cdf0709db6537d058bcab6dd6e2dbe"
+                "reference": "4667ff3bd513750603a09c8dedbea942487fb07c"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/136b19dd05cdf0709db6537d058bcab6dd6e2dbe",
-                "reference": "136b19dd05cdf0709db6537d058bcab6dd6e2dbe",
+                "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/4667ff3bd513750603a09c8dedbea942487fb07c",
+                "reference": "4667ff3bd513750603a09c8dedbea942487fb07c",
                 "shasum": ""
             },
             "require": {
-                "php": ">=7.2.5"
-            },
-            "suggest": {
-                "symfony/translation-implementation": ""
+                "php": ">=8.1"
             },
             "type": "library",
             "extra": {
-                "branch-alias": {
-                    "dev-main": "2.5-dev"
-                },
                 "thanks": {
-                    "name": "symfony/contracts",
-                    "url": "https://github.com/symfony/contracts"
+                    "url": "https://github.com/symfony/contracts",
+                    "name": "symfony/contracts"
+                },
+                "branch-alias": {
+                    "dev-main": "3.5-dev"
                 }
             },
             "autoload": {
                 "psr-4": {
                     "Symfony\\Contracts\\Translation\\": ""
-                }
+                },
+                "exclude-from-classmap": [
+                    "/Test/"
+                ]
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
@@ -1734,7 +1677,7 @@
                 "standards"
             ],
             "support": {
-                "source": "https://github.com/symfony/translation-contracts/tree/v2.5.2"
+                "source": "https://github.com/symfony/translation-contracts/tree/v3.5.1"
             },
             "funding": [
                 {
@@ -1750,38 +1693,41 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2022-06-27T16:58:25+00:00"
+            "time": "2024-09-25T14:20:29+00:00"
         },
         {
             "name": "twig/twig",
-            "version": "v3.5.1",
+            "version": "v3.20.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/twigphp/Twig.git",
-                "reference": "a6e0510cc793912b451fd40ab983a1d28f611c15"
+                "reference": "3468920399451a384bef53cf7996965f7cd40183"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/twigphp/Twig/zipball/a6e0510cc793912b451fd40ab983a1d28f611c15",
-                "reference": "a6e0510cc793912b451fd40ab983a1d28f611c15",
+                "url": "https://api.github.com/repos/twigphp/Twig/zipball/3468920399451a384bef53cf7996965f7cd40183",
+                "reference": "3468920399451a384bef53cf7996965f7cd40183",
                 "shasum": ""
             },
             "require": {
-                "php": ">=7.2.5",
+                "php": ">=8.1.0",
+                "symfony/deprecation-contracts": "^2.5|^3",
                 "symfony/polyfill-ctype": "^1.8",
                 "symfony/polyfill-mbstring": "^1.3"
             },
             "require-dev": {
-                "psr/container": "^1.0",
-                "symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0"
+                "phpstan/phpstan": "^2.0",
+                "psr/container": "^1.0|^2.0",
+                "symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0"
             },
             "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-master": "3.5-dev"
-                }
-            },
             "autoload": {
+                "files": [
+                    "src/Resources/core.php",
+                    "src/Resources/debug.php",
+                    "src/Resources/escaper.php",
+                    "src/Resources/string_loader.php"
+                ],
                 "psr-4": {
                     "Twig\\": "src/"
                 }
@@ -1814,7 +1760,7 @@
             ],
             "support": {
                 "issues": "https://github.com/twigphp/Twig/issues",
-                "source": "https://github.com/twigphp/Twig/tree/v3.5.1"
+                "source": "https://github.com/twigphp/Twig/tree/v3.20.0"
             },
             "funding": [
                 {
@@ -1826,21 +1772,21 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2023-02-08T07:49:20+00:00"
+            "time": "2025-02-13T08:34:43+00:00"
         }
     ],
     "packages-dev": [],
     "aliases": [],
     "minimum-stability": "dev",
-    "stability-flags": [],
+    "stability-flags": {},
     "prefer-stable": true,
     "prefer-lowest": false,
     "platform": {
-        "php": ">=8.1"
+        "php": ">=8.3"
     },
-    "platform-dev": [],
+    "platform-dev": {},
     "platform-overrides": {
-        "php": "8.1.0"
+        "php": "8.3"
     },
-    "plugin-api-version": "2.3.0"
+    "plugin-api-version": "2.6.0"
 }
diff --git a/_build/redirection_map b/_build/redirection_map
index 1701f4a8f70..c30723eac58 100644
--- a/_build/redirection_map
+++ b/_build/redirection_map
@@ -430,10 +430,12 @@
 /email/spool /mailer
 /email/testing /mailer
 /contributing/community/other /contributing/community
+/contributing/code/core_team /contributing/core_team
 /profiler/storage /profiler
 /setup/composer /setup
 /security/security_checker /setup
 /setup/built_in_web_server /setup/symfony_server
+/setup/symfony_server /setup/symfony_cli
 /service_container/parameters /configuration
 /routing/generate_url_javascript /routing
 /routing/slash_in_parameter /routing
@@ -573,3 +575,7 @@
 /doctrine/reverse_engineering /doctrine#doctrine-adding-mapping
 /components/serializer /serializer
 /serializer/custom_encoder /serializer/encoders#serializer-custom-encoder
+/components/string /string
+/form/button_based_validation /form/validation_groups
+/form/data_based_validation /form/validation_groups
+/form/validation_group_service_resolver /form/validation_groups
diff --git a/_images/sources/README.md b/_images/sources/README.md
index 467d4024010..84810a9783d 100644
--- a/_images/sources/README.md
+++ b/_images/sources/README.md
@@ -96,7 +96,7 @@ only the asciicast file).
 
 [1]: http://dia-installer.de/
 [2]: https://fonts.google.com/specimen/PT+Sans+Narrow
-[3]: https://symfony.com/doc/current/contributing/code/core_team.html
+[3]: https://symfony.com/doc/current/contributing/core_team.html
 [4]: https://github.com/asciinema/asciinema
 [5]: https://github.com/asciinema/agg
 [6]: https://www.jetbrains.com/lp/mono/
diff --git a/best_practices.rst b/best_practices.rst
index 2c393cae9c6..7ca5590036a 100644
--- a/best_practices.rst
+++ b/best_practices.rst
@@ -10,7 +10,7 @@ You can even ignore them completely and continue using your own best practices
 and methodologies. Symfony is flexible enough to adapt to your needs.
 
 This article assumes that you already have experience developing Symfony
-applications. If you don't, read first the :doc:`Getting Started </setup>`
+applications. If you don't, first read the :doc:`Getting Started </setup>`
 section of the documentation.
 
 .. tip::
@@ -95,7 +95,7 @@ Use Secrets for Sensitive Information
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 When your application has sensitive configuration, like an API key, you should
-store those securely via :doc:`Symfony’s secrets management system </configuration/secrets>`.
+store those securely via :doc:`Symfony's secrets management system </configuration/secrets>`.
 
 Use Parameters for Application Configuration
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -118,7 +118,7 @@ Use Short and Prefixed Parameter Names
 
 Consider using ``app.`` as the prefix of your :ref:`parameters <configuration-parameters>`
 to avoid collisions with Symfony and third-party bundles/libraries parameters.
-Then, use just one or two words to describe the purpose of the parameter:
+Then, use only one or two words to describe the purpose of the parameter:
 
 .. code-block:: yaml
 
@@ -362,10 +362,6 @@ Unless you have two legitimately different authentication systems and users
 (e.g. form login for the main site and a token system for your API only), it's
 recommended to have only one firewall to keep things simple.
 
-Additionally, you should use the ``anonymous`` key under your firewall. If you
-require users to be logged in for different sections of your site, use the
-:doc:`access_control </security/access_control>` option.
-
 Use the ``auto`` Password Hasher
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
diff --git a/bundles.rst b/bundles.rst
index 878bee3af4a..3e590a4e2aa 100644
--- a/bundles.rst
+++ b/bundles.rst
@@ -11,7 +11,7 @@ The Bundle System
 
 A bundle is similar to a plugin in other software, but even better. The core
 features of Symfony framework are implemented with bundles (FrameworkBundle,
-SecurityBundle, DebugBundle, etc.) They are also used to add new features in
+SecurityBundle, DebugBundle, etc.) Bundles are also used to add new features in
 your application via `third-party bundles`_.
 
 Bundles used in your applications must be enabled per
@@ -42,7 +42,7 @@ file::
 Creating a Bundle
 -----------------
 
-This section creates and enables a new bundle to show there are only a few steps required.
+This section creates and enables a new bundle to show that only a few steps are required.
 The new bundle is called AcmeBlogBundle, where the ``Acme`` portion is an example
 name that should be replaced by some "vendor" name that represents you or your
 organization (e.g. AbcBlogBundle for some company named ``Abc``).
diff --git a/bundles/best_practices.rst b/bundles/best_practices.rst
index 376984388db..8049ebb9a1c 100644
--- a/bundles/best_practices.rst
+++ b/bundles/best_practices.rst
@@ -195,25 +195,24 @@ Continuous Integration
 
 Testing bundle code continuously, including all its commits and pull requests,
 is a good practice called Continuous Integration. There are several services
-providing this feature for free for open source projects, like `GitHub Actions`_
-and `Travis CI`_.
+providing this feature for free for open source projects, like `GitHub Actions`_.
 
 A bundle should at least test:
 
 * The lower bound of their dependencies (by running ``composer update --prefer-lowest``);
 * The supported PHP versions;
-* All supported major Symfony versions (e.g. both ``4.x`` and ``5.x`` if
+* All supported major Symfony versions (e.g. both ``6.4`` and ``7.x`` if
   support is claimed for both).
 
-Thus, a bundle supporting PHP 7.3, 7.4 and 8.0, and Symfony 4.4 and 5.x should
+Thus, a bundle supporting PHP 7.4, 8.3 and 8.4, and Symfony 6.4 and 7.x should
 have at least this test matrix:
 
 ===========  ===============  ===================
 PHP version  Symfony version  Composer flags
 ===========  ===============  ===================
-7.3          ``4.*``          ``--prefer-lowest``
-7.4          ``5.*``
-8.0          ``5.*``
+7.4          ``6.4``          ``--prefer-lowest``
+8.3          ``7.*``
+8.4          ``7.*``
 ===========  ===============  ===================
 
 .. tip::
@@ -233,10 +232,10 @@ with Symfony Flex to install a specific Symfony version:
 
 .. code-block:: bash
 
-    # this requires Symfony 5.x for all Symfony packages
-    export SYMFONY_REQUIRE=5.*
+    # this requires Symfony 7.x for all Symfony packages
+    export SYMFONY_REQUIRE=7.*
     # alternatively you can run this command to update composer.json config
-    # composer config extra.symfony.require "5.*"
+    # composer config extra.symfony.require "7.*"
 
     # install Symfony Flex in the CI environment
     composer global config --no-plugins allow-plugins.symfony/flex true
@@ -397,10 +396,14 @@ Translation Files
 -----------------
 
 If a bundle provides message translations, they must be defined in the XLIFF
-format; the domain should be named after the bundle name (``acme_blog``).
+format; the domain should be named after the bundle name (``AcmeBlog``).
 
 A bundle must not override existing messages from another bundle.
 
+The translation domain must match the translation file names. For example,
+if the translation domain is ``AcmeBlog``, the English translation file name
+should be ``AcmeBlog.en.xlf``.
+
 Configuration
 -------------
 
@@ -556,6 +559,7 @@ Learn more
 
 * :doc:`/bundles/extension`
 * :doc:`/bundles/configuration`
+* :doc:`/frontend/create_ux_bundle`
 
 .. _`PSR-4`: https://www.php-fig.org/psr/psr-4/
 .. _`Symfony Flex recipe`: https://github.com/symfony/recipes
@@ -564,4 +568,3 @@ Learn more
 .. _`choose any license`: https://choosealicense.com/
 .. _`valid license identifier`: https://spdx.org/licenses/
 .. _`GitHub Actions`: https://docs.github.com/en/free-pro-team@latest/actions
-.. _`Travis CI`: https://docs.travis-ci.com/
diff --git a/bundles/extension.rst b/bundles/extension.rst
index 0537eb00c3e..d2792efc477 100644
--- a/bundles/extension.rst
+++ b/bundles/extension.rst
@@ -25,6 +25,7 @@ class, you can define the :method:`Symfony\\Component\\HttpKernel\\Bundle\\Abstr
 method to load service definitions from configuration files::
 
     // ...
+    use Symfony\Component\DependencyInjection\ContainerBuilder;
     use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
     use Symfony\Component\HttpKernel\Bundle\AbstractBundle;
 
diff --git a/cache.rst b/cache.rst
index 833e4d77007..9379511fde8 100644
--- a/cache.rst
+++ b/cache.rst
@@ -32,12 +32,11 @@ You can read more about these at the :doc:`component documentation </components/
 Configuring Cache with FrameworkBundle
 --------------------------------------
 
-When configuring the cache component there are a few concepts you should know
-of:
+When configuring the cache component there are a few concepts you should know:
 
 **Pool**
     This is a service that you will interact with. Each pool will always have
-    its own namespace and cache items. There is never a conflict between pools.
+    its own namespace and cache items. There are never conflicts between pools.
 **Adapter**
     An adapter is a *template* that you use to create pools.
 **Provider**
@@ -539,6 +538,8 @@ Symfony stores the item automatically in all the missing pools.
             ;
         };
 
+.. _cache-using-cache-tags:
+
 Using Cache Tags
 ----------------
 
@@ -590,6 +591,7 @@ to enable this feature. This could be added by using the following configuration
                 pools:
                     my_cache_pool:
                         adapter: cache.adapter.redis_tag_aware
+                        tags: true
 
     .. code-block:: xml
 
@@ -606,7 +608,7 @@ to enable this feature. This could be added by using the following configuration
             <framework:config>
                 <framework:cache>
                     <framework:pool name="my_cache_pool"
-                        adapter="cache.adapter.redis"
+                        adapter="cache.adapter.redis_tag_aware"
                         tags="true"
                     />
                 </framework:cache>
@@ -622,7 +624,7 @@ to enable this feature. This could be added by using the following configuration
             $framework->cache()
                 ->pool('my_cache_pool')
                     ->tags(true)
-                    ->adapters(['cache.adapter.redis'])
+                    ->adapters(['cache.adapter.redis_tag_aware'])
             ;
         };
 
@@ -949,9 +951,9 @@ a message bus to compute values in a worker:
     .. code-block:: php
 
         // config/framework/framework.php
-        use function Symfony\Component\DependencyInjection\Loader\Configurator\env;
         use Symfony\Component\Cache\Messenger\EarlyExpirationMessage;
         use Symfony\Config\FrameworkConfig;
+        use function Symfony\Component\DependencyInjection\Loader\Configurator\env;
 
         return static function (FrameworkConfig $framework): void {
             $framework->cache()
diff --git a/components/browser_kit.rst b/components/browser_kit.rst
index bcb8f7b3c8e..8cf0772298c 100644
--- a/components/browser_kit.rst
+++ b/components/browser_kit.rst
@@ -143,7 +143,7 @@ field values, etc.) before submitting it::
     $crawler = $client->request('GET', 'https://github.com/login');
 
     // find the form with the 'Log in' button and submit it
-    // 'Log in' can be the text content, id, value or name of a <button> or <input type="submit">
+    // 'Log in' can be the text content, id or name of a <button> or <input type="submit">
     $client->submitForm('Log in');
 
     // the second optional argument lets you override the default form field values
diff --git a/components/cache.rst b/components/cache.rst
index f5a76f2119d..44ba8b5c151 100644
--- a/components/cache.rst
+++ b/components/cache.rst
@@ -84,6 +84,69 @@ generate and return the value::
     Use cache tags to delete more than one key at the time. Read more at
     :doc:`/components/cache/cache_invalidation`.
 
+Creating Sub-Namespaces
+-----------------------
+
+.. versionadded:: 7.3
+
+   Cache sub-namespaces were introduced in Symfony 7.3.
+
+Sometimes you need to create context-dependent variations of data that should be
+cached. For example, the data used to render a dashboard page may be expensive
+to generate and unique per user, so you can't cache the same data for everyone.
+
+In such cases, Symfony allows you to create different cache contexts using
+namespaces. A cache namespace is an arbitrary string that identifies a set of
+related cache items. All cache adapters provided by the component implement the
+:class:`Symfony\\Contracts\\Cache\\NamespacedPoolInterface`, which provides the
+:method:`Symfony\\Contracts\\Cache\\NamespacedPoolInterface::withSubNamespace`
+method.
+
+This method allows you to namespace cached items by transparently prefixing their keys::
+
+    $userCache = $cache->withSubNamespace(sprintf('user-%d', $user->getId()));
+
+    $userCache->get('dashboard_data', function (ItemInterface $item): string {
+        $item->expiresAfter(3600);
+
+        return '...';
+    });
+
+In this example, the cache item uses the ``dashboard_data`` key, but it will be
+stored internally under a namespace based on the current user ID. This is handled
+automatically, so you **don't** need to manually prefix keys like ``user-27.dashboard_data``.
+
+There are no guidelines or restrictions on how to define cache namespaces.
+You can make them as granular or as generic as your application requires::
+
+    $localeCache = $cache->withSubNamespace($request->getLocale());
+
+    $flagCache = $cache->withSubNamespace(
+        $featureToggle->isEnabled('new_checkout') ? 'checkout-v2' : 'checkout-v1'
+    );
+
+    $channel = $request->attributes->get('_route')?->startsWith('api_') ? 'api' : 'web';
+    $channelCache = $cache->withSubNamespace($channel);
+
+.. tip::
+
+    You can combine cache namespaces with :ref:`cache tags <cache-using-cache-tags>`
+    for more advanced needs.
+
+There is no built-in way to invalidate caches by namespace. Instead, the recommended
+approach is to change the namespace itself. For this reason, it's common to include
+static or dynamic versioning data in the cache namespace::
+
+    // for simple applications, an incrementing static version number may be enough
+    $userCache = $cache->withSubNamespace(sprintf('v1-user-%d', $user->getId()));
+
+    // other applications may use dynamic versioning based on the date (e.g. monthly)
+    $userCache = $cache->withSubNamespace(sprintf('%s-user-%d', date('Ym'), $user->getId()));
+
+    // or even invalidate the cache when the user data changes
+    $checksum = hash('xxh128', $user->getUpdatedAt()->format(DATE_ATOM));
+    $userCache = $cache->withSubNamespace(sprintf('user-%d-%s', $user->getId(), $checksum));
+
 .. _cache_stampede-prevention:
 
 Stampede Prevention
@@ -208,16 +271,27 @@ Symfony uses *marshallers* (classes which implement
 the cache items before storing them.
 
 The :class:`Symfony\\Component\\Cache\\Marshaller\\DefaultMarshaller` uses PHP's
-``serialize()`` or ``igbinary_serialize()`` if the `Igbinary extension`_ is installed.
-There are other *marshallers* that can encrypt or compress the data before storing it::
+``serialize()`` function by default, but you can optionally use the ``igbinary_serialize()``
+function from the `Igbinary extension`_::
 
     use Symfony\Component\Cache\Adapter\RedisAdapter;
-    use Symfony\Component\Cache\DefaultMarshaller;
-    use Symfony\Component\Cache\DeflateMarshaller;
+    use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
+    use Symfony\Component\Cache\Marshaller\DeflateMarshaller;
 
     $marshaller = new DeflateMarshaller(new DefaultMarshaller());
+    // you can optionally use the Igbinary extension if you have it installed
+    // $marshaller = new DeflateMarshaller(new DefaultMarshaller(useIgbinarySerialize: true));
+
     $cache = new RedisAdapter(new \Redis(), 'namespace', 0, $marshaller);
 
+There are other *marshallers* that can encrypt or compress the data before storing it.
+
+.. versionadded:: 7.2
+
+    In Symfony versions prior to 7.2, the ``igbinary_serialize()`` function was
+    used by default when the Igbinary extension was installed. Starting from
+    Symfony 7.2, you have to enable Igbinary support explicitly.
+
 Advanced Usage
 --------------
 
diff --git a/components/cache/adapters/array_cache_adapter.rst b/components/cache/adapters/array_cache_adapter.rst
index f903771e468..08f8276db3d 100644
--- a/components/cache/adapters/array_cache_adapter.rst
+++ b/components/cache/adapters/array_cache_adapter.rst
@@ -24,5 +24,14 @@ method::
 
         // the maximum number of items that can be stored in the cache. When the limit
         // is reached, cache follows the LRU model (least recently used items are deleted)
-        $maxItems = 0
+        $maxItems = 0,
+
+        // optional implementation of the Psr\Clock\ClockInterface that will be used
+        // to calculate the lifetime of cache items (for example to get predictable
+        // lifetimes in tests)
+        $clock = null,
     );
+
+.. versionadded:: 7.2
+
+    The optional ``$clock`` argument was introduced in Symfony 7.2.
diff --git a/components/cache/adapters/redis_adapter.rst b/components/cache/adapters/redis_adapter.rst
index 3362f4cc2db..c0295b487a0 100644
--- a/components/cache/adapters/redis_adapter.rst
+++ b/components/cache/adapters/redis_adapter.rst
@@ -8,7 +8,8 @@ Redis Cache Adapter
     :ref:`Symfony Cache configuration <cache-configuration-with-frameworkbundle>`
     article if you are using it in a Symfony application.
 
-This adapter stores the values in-memory using one (or more) `Redis server`_ instances.
+This adapter stores the values in-memory using one (or more) `Redis server`_
+of `Valkey`_ server instances.
 
 Unlike the :doc:`APCu adapter </components/cache/adapters/apcu_adapter>`, and similarly to the
 :doc:`Memcached adapter </components/cache/adapters/memcached_adapter>`, it is not limited to the current server's
@@ -19,9 +20,9 @@ to utilize a cluster of servers to provide redundancy and/or fail-over is also a
 
     **Requirements:** At least one `Redis server`_ must be installed and running to use this
     adapter. Additionally, this adapter requires a compatible extension or library that implements
-    ``\Redis``, ``\RedisArray``, ``RedisCluster``, ``\Relay\Relay`` or ``\Predis``.
+    ``\Redis``, ``\RedisArray``, ``RedisCluster``, ``\Relay\Relay``, ``\Relay\Cluster`` or ``\Predis``.
 
-This adapter expects a `Redis`_, `RedisArray`_, `RedisCluster`_, `Relay`_ or `Predis`_ instance to be
+This adapter expects a `Redis`_, `RedisArray`_, `RedisCluster`_, `Relay`_, `RelayCluster`_ or `Predis`_ instance to be
 passed as the first parameter. A namespace and default cache lifetime can optionally be passed
 as the second and third parameters::
 
@@ -47,6 +48,10 @@ as the second and third parameters::
         ?MarshallerInterface $marshaller = null
     );
 
+.. versionadded:: 7.3
+
+    Support for ``Relay\Cluster`` was introduced in Symfony 7.3.
+
 Configure the Connection
 ------------------------
 
@@ -61,6 +66,11 @@ helper method allows creating and configuring the Redis client class instance us
         'redis://localhost'
     );
 
+.. versionadded:: 7.3
+
+    Starting in Symfony 7.3, when using Valkey servers you can use the
+    ``valkey[s]:`` scheme instead of the ``redis[s]:`` one in your DSNs.
+
 The DSN can specify either an IP/host (and an optional port) or a socket path, as well as a
 password and a database index. To enable TLS for connections, the scheme ``redis`` must be
 replaced by ``rediss`` (the second ``s`` means "secure").
@@ -220,11 +230,34 @@ Available Options
 ``ssl`` (type: ``array``, default: ``null``)
     SSL context options. See `php.net/context.ssl`_ for more information.
 
+``relay_cluster_context`` (type: ``array``, default: ``[]``)
+    Defines configuration options specific to ``\Relay\Cluster``. For example, to
+    user a self-signed certificate for testing in local environment::
+
+        $options = [
+            // ...
+            'relay_cluster_context' => [
+                // ...
+                'stream' => [
+                    'verify_peer' => false,
+                    'verify_peer_name' => false,
+                    'allow_self_signed' => true,
+                    'local_cert' => '/valkey.crt',
+                    'local_pk' => '/valkey.key',
+                    'cafile' => '/valkey.crt',
+                ],
+            ],
+        ];
+
 .. versionadded:: 7.1
 
-    The option `sentinel_master` as an alias for `redis_sentinel` was introduced
+    The option ``sentinel_master`` as an alias for ``redis_sentinel`` was introduced
     in Symfony 7.1.
 
+.. versionadded:: 7.3
+
+    The ``relay_cluster_context`` option was introduced in Symfony 7.3.
+
 .. note::
 
     When using the `Predis`_ library some additional Predis-specific options are available.
@@ -348,10 +381,12 @@ Supports key rotation, ensuring secure decryption with both old and new keys::
 
 .. _`Data Source Name (DSN)`: https://en.wikipedia.org/wiki/Data_source_name
 .. _`Redis server`: https://redis.io/
+.. _`Valkey`: https://valkey.io/
 .. _`Redis`: https://github.com/phpredis/phpredis
 .. _`RedisArray`: https://github.com/phpredis/phpredis/blob/develop/arrays.md
 .. _`RedisCluster`: https://github.com/phpredis/phpredis/blob/develop/cluster.md
 .. _`Relay`: https://relay.so/
+.. _`RelayCluster`: https://relay.so/docs/1.x/connections#cluster
 .. _`Predis`: https://packagist.org/packages/predis/predis
 .. _`Predis Connection Parameters`: https://github.com/nrk/predis/wiki/Connection-Parameters#list-of-connection-parameters
 .. _`TCP-keepalive`: https://redis.io/topics/clients#tcp-keepalive
diff --git a/components/clock.rst b/components/clock.rst
index 5b20e6000b9..c4ac88e9092 100644
--- a/components/clock.rst
+++ b/components/clock.rst
@@ -267,6 +267,36 @@ timestamps::
     :method:`Symfony\\Component\\Clock\\DatePoint::getMicrosecond` methods were
     introduced in Symfony 7.1.
 
+Storing DatePoints in the Database
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If you :doc:`use Doctrine </doctrine>` to work with databases, consider using the
+``date_point`` Doctrine type, which converts to/from ``DatePoint`` objects automatically::
+
+    // src/Entity/Product.php
+    namespace App\Entity;
+
+    use Doctrine\ORM\Mapping as ORM;
+    use Symfony\Component\Clock\DatePoint;
+
+    #[ORM\Entity]
+    class Product
+    {
+        // if you don't define the Doctrine type explicitly, Symfony will autodetect it:
+        #[ORM\Column]
+        private DatePoint $createdAt;
+
+        // if you prefer to define the Doctrine type explicitly:
+        #[ORM\Column(type: 'date_point')]
+        private DatePoint $updatedAt;
+
+        // ...
+    }
+
+.. versionadded:: 7.3
+
+    The ``DatePointType`` was introduced in Symfony 7.3.
+
 .. _clock_writing-tests:
 
 Writing Time-Sensitive Tests
diff --git a/components/config/caching.rst b/components/config/caching.rst
index 18620c0d8cf..80e23a4fdfb 100644
--- a/components/config/caching.rst
+++ b/components/config/caching.rst
@@ -3,7 +3,7 @@ Caching based on Resources
 
 When all configuration resources are loaded, you may want to process the
 configuration values and combine them all in one file. This file acts
-like a cache. Its contents don’t have to be regenerated every time the
+like a cache. Its contents don't have to be regenerated every time the
 application runs – only when the configuration resources are modified.
 
 For example, the Symfony Routing component allows you to load all routes,
diff --git a/components/config/definition.rst b/components/config/definition.rst
index 19e4f5fd40c..2b1841bc24a 100644
--- a/components/config/definition.rst
+++ b/components/config/definition.rst
@@ -83,9 +83,19 @@ reflect the real structure of the configuration values::
             ->scalarNode('default_connection')
                 ->defaultValue('mysql')
             ->end()
+            ->stringNode('username')
+                ->defaultValue('root')
+            ->end()
+            ->stringNode('password')
+                ->defaultValue('root')
+            ->end()
         ->end()
     ;
 
+.. versionadded:: 7.2
+
+    The ``stringNode()`` method was introduced in Symfony 7.2.
+
 The root node itself is an array node, and has children, like the boolean
 node ``auto_connect`` and the scalar node ``default_connection``. In general:
 after defining a node, a call to ``end()`` takes you one step up in the
@@ -100,6 +110,7 @@ node definition. Node types are available for:
 * scalar (generic type that includes booleans, strings, integers, floats
   and ``null``)
 * boolean
+* string
 * integer
 * float
 * enum (similar to scalar, but it only allows a finite set of values)
@@ -109,6 +120,10 @@ node definition. Node types are available for:
 and are created with ``node($name, $type)`` or their associated shortcut
 ``xxxxNode($name)`` method.
 
+.. versionadded:: 7.2
+
+    Support for the ``string`` type was introduced in Symfony 7.2.
+
 Numeric Node Constraints
 ~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -171,6 +186,25 @@ The configuration can now be written like this::
         ->end()
     ;
 
+You can also use the ``enumFqcn()`` method to pass the FQCN of an enum
+class to the node. This will automatically set the values of the node to
+the cases of the enum::
+
+    $rootNode
+        ->children()
+            ->enumNode('delivery')
+                ->enumFqcn(Delivery::class)
+            ->end()
+        ->end()
+    ;
+
+When using a backed enum, the values provided to the node will be cast
+to one of the enum cases if possible.
+
+.. versionadded:: 7.3
+
+    The ``enumFqcn()`` method was introduced in Symfony 7.3.
+
 Array Nodes
 ~~~~~~~~~~~
 
@@ -512,6 +546,30 @@ and in XML:
     <!-- entries-per-page: This value is only used for the search results page. -->
     <config entries-per-page="25"/>
 
+You can also provide a URL to a full documentation page::
+
+    $rootNode
+        ->docUrl('Full documentation is available at https://example.com/docs/{version:major}.{version:minor}/reference.html')
+        ->children()
+            ->integerNode('entries_per_page')
+                ->defaultValue(25)
+            ->end()
+        ->end()
+    ;
+
+A few placeholders are available to customize the URL:
+
+* ``{version:major}``: The major version of the package currently installed
+* ``{version:minor}``: The minor version of the package currently installed
+* ``{package}``: The name of the package
+
+The placeholders will be replaced when printing the configuration tree with the
+``config:dump-reference`` command.
+
+.. versionadded:: 7.3
+
+    The ``docUrl()`` method was introduced in Symfony 7.3.
+
 Optional Sections
 -----------------
 
@@ -800,6 +858,7 @@ A validation rule always has an "if" part. You can specify this part in
 the following ways:
 
 - ``ifTrue()``
+- ``ifFalse()``
 - ``ifString()``
 - ``ifNull()``
 - ``ifEmpty()``
@@ -818,6 +877,10 @@ A validation rule also requires a "then" part:
 Usually, "then" is a closure. Its return value will be used as a new value
 for the node, instead of the node's original value.
 
+.. versionadded:: 7.3
+
+    The ``ifFalse()`` method was introduced in Symfony 7.3.
+
 Configuring the Node Path Separator
 -----------------------------------
 
diff --git a/components/console/changing_default_command.rst b/components/console/changing_default_command.rst
index c69995ea395..2195bbd2697 100644
--- a/components/console/changing_default_command.rst
+++ b/components/console/changing_default_command.rst
@@ -9,20 +9,14 @@ name to the ``setDefaultCommand()`` method::
 
     use Symfony\Component\Console\Attribute\AsCommand;
     use Symfony\Component\Console\Command\Command;
-    use Symfony\Component\Console\Input\InputInterface;
-    use Symfony\Component\Console\Output\OutputInterface;
+    use Symfony\Component\Console\Style\SymfonyStyle;
 
-    #[AsCommand(name: 'hello:world')]
+    #[AsCommand(name: 'hello:world', description: 'Outputs "Hello World"')]
     class HelloWorldCommand extends Command
     {
-        protected function configure(): void
+        public function __invoke(SymfonyStyle $io): int
         {
-            $this->setDescription('Outputs "Hello World"');
-        }
-
-        protected function execute(InputInterface $input, OutputInterface $output): int
-        {
-            $output->writeln('Hello World');
+            $io->writeln('Hello World');
 
             return Command::SUCCESS;
         }
diff --git a/components/console/events.rst b/components/console/events.rst
index e550025b7dd..699ba444747 100644
--- a/components/console/events.rst
+++ b/components/console/events.rst
@@ -209,36 +209,32 @@ method::
     for these constants to be available.
 
 If you use the Console component inside a Symfony application, commands can
-handle signals themselves. To do so, implement the
-:class:`Symfony\\Component\\Console\\Command\\SignalableCommandInterface` and subscribe to one or more signals::
+handle signals themselves by subscribing to the :class:`Symfony\\Component\\Console\\Event\\ConsoleSignalEvent` event::
 
-    // src/Command/SomeCommand.php
+    // src/Command/MyCommand.php
     namespace App\Command;
 
-    use Symfony\Component\Console\Command\Command;
-    use Symfony\Component\Console\Command\SignalableCommandInterface;
+    use Symfony\Component\Console\Attribute\AsCommand;
+    use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
 
-    class SomeCommand extends Command implements SignalableCommandInterface
+    #[AsCommand(name: 'app:my-command')]
+    class MyCommand
     {
         // ...
 
-        public function getSubscribedSignals(): array
+        #[AsEventListener(ConsoleSignalEvent::class)]
+        public function handleSignal(ConsoleSignalEvent $event): void
         {
-            // return here any of the constants defined by PCNTL extension
-            return [\SIGINT, \SIGTERM];
-        }
-
-        public function handleSignal(int $signal): int|false
-        {
-            if (\SIGINT === $signal) {
+            // set here any of the constants defined by PCNTL extension
+            if (in_array($event->getHandlingSignal(), [\SIGINT, \SIGTERM], true)) {
                 // ...
             }
 
             // ...
 
-            // return an integer to set the exit code, or
+            // set an integer exit code, or
             // false to continue normal execution
-            return 0;
+            $event->setExitCode(0);
         }
     }
 
diff --git a/components/console/helpers/cursor.rst b/components/console/helpers/cursor.rst
index c5cab6c6d0b..63045f178ad 100644
--- a/components/console/helpers/cursor.rst
+++ b/components/console/helpers/cursor.rst
@@ -13,16 +13,16 @@ of the output:
     // src/Command/MyCommand.php
     namespace App\Command;
 
-    use Symfony\Component\Console\Command\Command;
+    use Symfony\Component\Console\Attribute\AsCommand;
     use Symfony\Component\Console\Cursor;
-    use Symfony\Component\Console\Input\InputInterface;
     use Symfony\Component\Console\Output\OutputInterface;
 
-    class MyCommand extends Command
+    #[AsCommand(name: 'app:my-command')]
+    class MyCommand
     {
         // ...
 
-        public function execute(InputInterface $input, OutputInterface $output): int
+        public function __invoke(OutputInterface $output): int
         {
             // ...
 
diff --git a/components/console/helpers/debug_formatter.rst b/components/console/helpers/debug_formatter.rst
index 10d3c67a79a..8fa59c319c9 100644
--- a/components/console/helpers/debug_formatter.rst
+++ b/components/console/helpers/debug_formatter.rst
@@ -10,15 +10,14 @@ this:
 .. image:: /_images/components/console/debug_formatter.png
     :alt: Console output, with the first line showing "RUN Running figlet", followed by lines showing the output of the command prefixed with "OUT" and "RES Finished the command" as last line in the output.
 
-Using the debug_formatter
+Using the Debug Formatter
 -------------------------
 
-The formatter is included in the default helper set and you can get it by
-calling :method:`Symfony\\Component\\Console\\Command\\Command::getHelper`::
+The debug formatter helper can be instantiated directly as shown::
 
-    $debugFormatter = $this->getHelper('debug_formatter');
+    $debugFormatter = new DebugFormatterHelper();
 
-The formatter accepts strings and returns a formatted string, which you then
+It accepts strings and returns a formatted string, which you then
 output to the console (or even log the information or do anything else).
 
 All methods of this helper have an identifier as the first argument. This is a
diff --git a/components/console/helpers/formatterhelper.rst b/components/console/helpers/formatterhelper.rst
index 3cb87c4c307..cf9bacdeb9c 100644
--- a/components/console/helpers/formatterhelper.rst
+++ b/components/console/helpers/formatterhelper.rst
@@ -1,15 +1,11 @@
 Formatter Helper
 ================
 
-The Formatter helper provides functions to format the output with colors.
-You can do more advanced things with this helper than you can in
-:doc:`/console/coloring`.
+The :class:`Symfony\\Component\\Console\\Helper\\FormatterHelper` helper provides
+functions to format the output with colors. You can do more advanced things with
+this helper than you can with the :doc:`basic colors and styles </console/coloring>`::
 
-The :class:`Symfony\\Component\\Console\\Helper\\FormatterHelper` is included
-in the default helper set and you can get it by calling
-:method:`Symfony\\Component\\Console\\Command\\Command::getHelper`::
-
-    $formatter = $this->getHelper('formatter');
+    $formatter = new FormatterHelper();
 
 The methods return a string, which you'll usually render to the console by
 passing it to the
@@ -129,10 +125,16 @@ Sometimes you want to format seconds to time. This is possible with the
 The first argument is the seconds to format and the second argument is the
 precision (default ``1``) of the result::
 
-    Helper::formatTime(42);        // 42 secs
-    Helper::formatTime(125);       // 2 mins
-    Helper::formatTime(125, 2);    // 2 mins, 5 secs
-    Helper::formatTime(172799, 4); // 1 day, 23 hrs, 59 mins, 59 secs
+    Helper::formatTime(0.001);         // 1 ms
+    Helper::formatTime(42);            // 42 s
+    Helper::formatTime(125);           // 2 min
+    Helper::formatTime(125, 2);        // 2 min, 5 s
+    Helper::formatTime(172799, 4);     // 1 d, 23 h, 59 min, 59 s
+    Helper::formatTime(172799.056, 5); // 1 d, 23 h, 59 min, 59 s, 56 ms
+
+.. versionadded:: 7.3
+
+    Support for formatting up to milliseconds was introduced in Symfony 7.3.
 
 Formatting Memory
 -----------------
diff --git a/components/console/helpers/map.rst.inc b/components/console/helpers/map.rst.inc
index 8f9ce0ca0f3..73d5d4da7a0 100644
--- a/components/console/helpers/map.rst.inc
+++ b/components/console/helpers/map.rst.inc
@@ -1,7 +1,9 @@
 * :doc:`/components/console/helpers/formatterhelper`
 * :doc:`/components/console/helpers/processhelper`
 * :doc:`/components/console/helpers/progressbar`
+* :doc:`/components/console/helpers/progressindicator`
 * :doc:`/components/console/helpers/questionhelper`
 * :doc:`/components/console/helpers/table`
+* :doc:`/components/console/helpers/tree`
 * :doc:`/components/console/helpers/debug_formatter`
 * :doc:`/components/console/helpers/cursor`
diff --git a/components/console/helpers/processhelper.rst b/components/console/helpers/processhelper.rst
index a02aabfd85d..df9a8efe45b 100644
--- a/components/console/helpers/processhelper.rst
+++ b/components/console/helpers/processhelper.rst
@@ -11,7 +11,7 @@ a very verbose verbosity (e.g. ``-vv``)::
 
     use Symfony\Component\Process\Process;
 
-    $helper = $this->getHelper('process');
+    $helper = new ProcessHelper();
     $process = new Process(['figlet', 'Symfony']);
 
     $helper->run($output, $process);
@@ -42,12 +42,7 @@ In case the process fails, debugging is easier:
 Arguments
 ---------
 
-There are three ways to use the process helper:
-
-* Using a command line string::
-
-    // ...
-    $helper->run($output, 'figlet Symfony');
+There are two ways to use the process helper:
 
 * An array of arguments::
 
diff --git a/components/console/helpers/progressindicator.rst b/components/console/helpers/progressindicator.rst
index d64ec6367b7..0defe7c83fd 100644
--- a/components/console/helpers/progressindicator.rst
+++ b/components/console/helpers/progressindicator.rst
@@ -44,18 +44,21 @@ level of verbosity of the ``OutputInterface`` instance:
      | Processing...
      / Processing...
      - Processing...
+     ✔ Finished
 
     # OutputInterface::VERBOSITY_VERBOSE (-v)
      \ Processing... (1 sec)
      | Processing... (1 sec)
      / Processing... (1 sec)
      - Processing... (1 sec)
+     ✔ Finished (1 sec)
 
     # OutputInterface::VERBOSITY_VERY_VERBOSE (-vv) and OutputInterface::VERBOSITY_DEBUG (-vvv)
      \ Processing... (1 sec, 6.0 MiB)
      | Processing... (1 sec, 6.0 MiB)
      / Processing... (1 sec, 6.0 MiB)
      - Processing... (1 sec, 6.0 MiB)
+     ✔ Finished (1 sec, 6.0 MiB)
 
 .. tip::
 
@@ -94,6 +97,34 @@ The progress indicator will now look like this:
      ⠛ Processing...
      ⠹ Processing...
      ⢸ Processing...
+     ✔ Finished
+
+Once the progress finishes, it displays a special finished indicator (which defaults
+to ✔). You can replace it with your own::
+
+    $progressIndicator = new ProgressIndicator($output, finishedIndicatorValue: '🎉');
+    
+    try {
+        /* do something */
+        $progressIndicator->finish('Finished');
+    } catch (\Exception) {
+        $progressIndicator->finish('Failed', '🚨');
+    }
+
+The progress indicator will now look like this:
+
+.. code-block:: text
+
+     \ Processing...
+     | Processing...
+     / Processing...
+     - Processing...
+     🎉 Finished
+
+.. versionadded:: 7.2
+
+    The ``finishedIndicator`` parameter for the constructor was introduced in Symfony 7.2.
+    The ``finishedIndicator`` parameter for method ``finish()`` was introduced in Symfony 7.2.
 
 Customize Placeholders
 ~~~~~~~~~~~~~~~~~~~~~~
diff --git a/components/console/helpers/questionhelper.rst b/components/console/helpers/questionhelper.rst
index 3dc97d5c0d3..6d22a2de2af 100644
--- a/components/console/helpers/questionhelper.rst
+++ b/components/console/helpers/questionhelper.rst
@@ -2,11 +2,9 @@ Question Helper
 ===============
 
 The :class:`Symfony\\Component\\Console\\Helper\\QuestionHelper` provides
-functions to ask the user for more information. It is included in the default
-helper set and you can get it by calling
-:method:`Symfony\\Component\\Console\\Command\\Command::getHelper`::
+functions to ask the user for more information::
 
-    $helper = $this->getHelper('question');
+    $helper = new QuestionHelper();
 
 The Question Helper has a single method
 :method:`Symfony\\Component\\Console\\Helper\\QuestionHelper::ask` that needs an
@@ -27,18 +25,18 @@ Suppose you want to confirm an action before actually executing it. Add
 the following to your command::
 
     // ...
+    use Symfony\Component\Console\Attribute\AsCommand;
     use Symfony\Component\Console\Command\Command;
     use Symfony\Component\Console\Input\InputInterface;
     use Symfony\Component\Console\Output\OutputInterface;
     use Symfony\Component\Console\Question\ConfirmationQuestion;
 
-    class YourCommand extends Command
+    #[AsCommand(name: 'app:my-command')]
+    class MyCommand
     {
-        // ...
-
-        public function execute(InputInterface $input, OutputInterface $output): int
+        public function __invoke(InputInterface $input, OutputInterface $output): int
         {
-            $helper = $this->getHelper('question');
+            $helper = new QuestionHelper();
             $question = new ConfirmationQuestion('Continue with this action?', false);
 
             if (!$helper->ask($input, $output, $question)) {
@@ -91,7 +89,7 @@ if you want to know a bundle name, you can add this to your command::
     use Symfony\Component\Console\Question\Question;
 
     // ...
-    public function execute(InputInterface $input, OutputInterface $output): int
+    public function __invoke(InputInterface $input, OutputInterface $output): int
     {
         // ...
         $question = new Question('Please enter the name of the bundle', 'AcmeDemoBundle');
@@ -121,10 +119,10 @@ but ``red`` could be set instead (could be more explicit)::
     use Symfony\Component\Console\Question\ChoiceQuestion;
 
     // ...
-    public function execute(InputInterface $input, OutputInterface $output): int
+    public function __invoke(InputInterface $input, OutputInterface $output): int
     {
         // ...
-        $helper = $this->getHelper('question');
+        $helper = new QuestionHelper();
         $question = new ChoiceQuestion(
             'Please select your favorite color (defaults to red)',
             // choices can also be PHP objects that implement __toString() method
@@ -184,10 +182,10 @@ this use :method:`Symfony\\Component\\Console\\Question\\ChoiceQuestion::setMult
     use Symfony\Component\Console\Question\ChoiceQuestion;
 
     // ...
-    public function execute(InputInterface $input, OutputInterface $output): int
+    public function __invoke(InputInterface $input, OutputInterface $output): int
     {
         // ...
-        $helper = $this->getHelper('question');
+        $helper = new QuestionHelper();
         $question = new ChoiceQuestion(
             'Please select your favorite colors (defaults to red and blue)',
             ['red', 'blue', 'yellow'],
@@ -218,10 +216,10 @@ will be autocompleted as the user types::
     use Symfony\Component\Console\Question\Question;
 
     // ...
-    public function execute(InputInterface $input, OutputInterface $output): int
+    public function __invoke(InputInterface $input, OutputInterface $output): int
     {
         // ...
-        $helper = $this->getHelper('question');
+        $helper = new QuestionHelper();
 
         $bundles = ['AcmeDemoBundle', 'AcmeBlogBundle', 'AcmeStoreBundle'];
         $question = new Question('Please enter the name of a bundle', 'FooBundle');
@@ -241,9 +239,9 @@ provide a callback function to dynamically generate suggestions::
     use Symfony\Component\Console\Question\Question;
 
     // ...
-    public function execute(InputInterface $input, OutputInterface $output): int
+    public function __invoke(InputInterface $input, OutputInterface $output): int
     {
-        $helper = $this->getHelper('question');
+        $helper = new QuestionHelper();
 
         // This function is called whenever the input changes and new
         // suggestions are needed.
@@ -282,10 +280,10 @@ You can also specify if you want to not trim the answer by setting it directly w
     use Symfony\Component\Console\Question\Question;
 
     // ...
-    public function execute(InputInterface $input, OutputInterface $output): int
+    public function __invoke(InputInterface $input, OutputInterface $output): int
     {
         // ...
-        $helper = $this->getHelper('question');
+        $helper = new QuestionHelper();
 
         $question = new Question('What is the name of the child?');
         $question->setTrimmable(false);
@@ -308,10 +306,10 @@ the response to a question should allow multiline answers by passing ``true`` to
     use Symfony\Component\Console\Question\Question;
 
     // ...
-    public function execute(InputInterface $input, OutputInterface $output): int
+    public function __invoke(InputInterface $input, OutputInterface $output): int
     {
         // ...
-        $helper = $this->getHelper('question');
+        $helper = new QuestionHelper();
 
         $question = new Question('How do you solve world peace?');
         $question->setMultiline(true);
@@ -335,10 +333,10 @@ convenient for passwords::
     use Symfony\Component\Console\Question\Question;
 
     // ...
-    public function execute(InputInterface $input, OutputInterface $output): int
+    public function __invoke(InputInterface $input, OutputInterface $output): int
     {
         // ...
-        $helper = $this->getHelper('question');
+        $helper = new QuestionHelper();
 
         $question = new Question('What is the database password?');
         $question->setHidden(true);
@@ -372,10 +370,10 @@ convenient for passwords::
         use Symfony\Component\Console\Question\ChoiceQuestion;
 
         // ...
-        public function execute(InputInterface $input, OutputInterface $output): int
+        public function __invoke(InputInterface $input, OutputInterface $output): int
         {
             // ...
-            $helper = $this->getHelper('question');
+            $helper = new QuestionHelper();
             QuestionHelper::disableStty();
 
             // ...
@@ -396,10 +394,10 @@ method::
     use Symfony\Component\Console\Question\Question;
 
     // ...
-    public function execute(InputInterface $input, OutputInterface $output): int
+    public function __invoke(InputInterface $input, OutputInterface $output): int
     {
         // ...
-        $helper = $this->getHelper('question');
+        $helper = new QuestionHelper();
 
         $question = new Question('Please enter the name of the bundle', 'AcmeDemoBundle');
         $question->setNormalizer(function (string $value): string {
@@ -434,10 +432,10 @@ method::
     use Symfony\Component\Console\Question\Question;
 
     // ...
-    public function execute(InputInterface $input, OutputInterface $output): int
+    public function __invoke(InputInterface $input, OutputInterface $output): int
     {
         // ...
-        $helper = $this->getHelper('question');
+        $helper = new QuestionHelper();
 
         $question = new Question('Please enter the name of the bundle', 'AcmeDemoBundle');
         $question->setValidator(function (string $answer): string {
@@ -480,10 +478,10 @@ invalid answer and will only be able to proceed if their input is valid.
         use Symfony\Component\Validator\Validation;
 
         $question = new Question('Please enter the name of the bundle', 'AcmeDemoBundle');
-        $validation = Validation::createCallable(new Regex([
-            'pattern' => '/^[a-zA-Z]+Bundle$/',
-            'message' => 'The name of the bundle should be suffixed with \'Bundle\'',
-        ]));
+        $validation = Validation::createCallable(new Regex(
+            pattern: '/^[a-zA-Z]+Bundle$/',
+            message: 'The name of the bundle should be suffixed with \'Bundle\'',
+        ));
         $question->setValidator($validation);
 
 Validating a Hidden Response
@@ -494,10 +492,10 @@ You can also use a validator with a hidden question::
     use Symfony\Component\Console\Question\Question;
 
     // ...
-    public function execute(InputInterface $input, OutputInterface $output): int
+    public function __invoke(InputInterface $input, OutputInterface $output): int
     {
         // ...
-        $helper = $this->getHelper('question');
+        $helper = new QuestionHelper();
 
         $question = new Question('Please enter your password');
         $question->setNormalizer(function (?string $value): string {
diff --git a/components/console/helpers/table.rst b/components/console/helpers/table.rst
index 13bdeb491f0..e36b1570b70 100644
--- a/components/console/helpers/table.rst
+++ b/components/console/helpers/table.rst
@@ -1,36 +1,25 @@
-Table
-=====
+Table Helper
+============
 
-When building a console application it may be useful to display tabular data:
-
-.. code-block:: terminal
-
-    +---------------+--------------------------+------------------+
-    | ISBN          | Title                    | Author           |
-    +---------------+--------------------------+------------------+
-    | 99921-58-10-7 | Divine Comedy            | Dante Alighieri  |
-    | 9971-5-0210-0 | A Tale of Two Cities     | Charles Dickens  |
-    | 960-425-059-0 | The Lord of the Rings    | J. R. R. Tolkien |
-    | 80-902734-1-6 | And Then There Were None | Agatha Christie  |
-    +---------------+--------------------------+------------------+
-
-.. note::
-
-    As an alternative, consider using the
-    :ref:`SymfonyStyle <symfony-style-content>` to display a table.
+When building console applications, Symfony provides several utilities for
+rendering tabular data. The simplest option is to use the table methods from
+:ref:`Symfony Style <symfony-style-content>`. While convenient, this approach
+doesn't allow customization of the table's design. For more control and advanced
+features, use the ``Table`` console helper explained in this article.
 
 To display a table, use :class:`Symfony\\Component\\Console\\Helper\\Table`,
 set the headers, set the rows and then render the table::
 
+    use Symfony\Component\Console\Attribute\AsCommand;
     use Symfony\Component\Console\Command\Command;
     use Symfony\Component\Console\Helper\Table;
-    use Symfony\Component\Console\Input\InputInterface;
     use Symfony\Component\Console\Output\OutputInterface;
     // ...
 
-    class SomeCommand extends Command
+    #[AsCommand(name: 'app:my-command')]
+    class MyCommand
     {
-        public function execute(InputInterface $input, OutputInterface $output): int
+        public function __invoke(OutputInterface $output): int
         {
             $table = new Table($output);
             $table
@@ -48,6 +37,22 @@ set the headers, set the rows and then render the table::
         }
     }
 
+This outputs:
+
+.. code-block:: terminal
+
+    +---------------+--------------------------+------------------+
+    | ISBN          | Title                    | Author           |
+    +---------------+--------------------------+------------------+
+    | 99921-58-10-7 | Divine Comedy            | Dante Alighieri  |
+    | 9971-5-0210-0 | A Tale of Two Cities     | Charles Dickens  |
+    | 960-425-059-0 | The Lord of the Rings    | J. R. R. Tolkien |
+    | 80-902734-1-6 | And Then There Were None | Agatha Christie  |
+    +---------------+--------------------------+------------------+
+
+Adding Table Separators
+-----------------------
+
 You can add a table separator anywhere in the output by passing an instance of
 :class:`Symfony\\Component\\Console\\Helper\\TableSeparator` as a row::
 
@@ -61,6 +66,8 @@ You can add a table separator anywhere in the output by passing an instance of
         ['80-902734-1-6', 'And Then There Were None', 'Agatha Christie'],
     ]);
 
+This outputs:
+
 .. code-block:: terminal
 
     +---------------+--------------------------+------------------+
@@ -73,6 +80,9 @@ You can add a table separator anywhere in the output by passing an instance of
     | 80-902734-1-6 | And Then There Were None | Agatha Christie  |
     +---------------+--------------------------+------------------+
 
+Adding Table Titles
+-------------------
+
 You can optionally display titles at the top and the bottom of the table::
 
     // ...
@@ -80,6 +90,8 @@ You can optionally display titles at the top and the bottom of the table::
     $table->setFooterTitle('Page 1/2');
     $table->render();
 
+This outputs:
+
 .. code-block:: terminal
 
     +---------------+----------- Books --------+------------------+
@@ -92,6 +104,9 @@ You can optionally display titles at the top and the bottom of the table::
     | 80-902734-1-6 | And Then There Were None | Agatha Christie  |
     +---------------+--------- Page 1/2 -------+------------------+
 
+Setting the Column Widths Explicitly
+------------------------------------
+
 By default, the width of the columns is calculated automatically based on their
 contents. Use the :method:`Symfony\\Component\\Console\\Helper\\Table::setColumnWidths`
 method to set the column widths explicitly::
@@ -114,7 +129,7 @@ argument is the column width::
     $table->setColumnWidth(2, 30);
     $table->render();
 
-The output of this command will be:
+This outputs:
 
 .. code-block:: terminal
 
@@ -141,7 +156,7 @@ If you prefer to wrap long contents in multiple rows, use the
     $table->setColumnMaxWidth(1, 10);
     $table->render();
 
-The output of this command will be:
+This outputs:
 
 .. code-block:: terminal
 
@@ -154,6 +169,9 @@ The output of this command will be:
     |                (the rest of the rows...)            |
     +-------+------------+--------------------------------+
 
+Rendering Vertical Tables
+-------------------------
+
 By default, table contents are displayed horizontally. You can change this behavior
 via the :method:`Symfony\\Component\\Console\\Helper\\Table::setVertical` method::
 
@@ -161,7 +179,7 @@ via the :method:`Symfony\\Component\\Console\\Helper\\Table::setVertical` method
     $table->setVertical();
     $table->render();
 
-The output of this command will be:
+This outputs:
 
 .. code-block:: terminal
 
@@ -175,17 +193,24 @@ The output of this command will be:
     | Author: Charles Dickens      |
     +------------------------------+
 
+Customizing the Table Style
+---------------------------
+
 The table style can be changed to any built-in styles via
 :method:`Symfony\\Component\\Console\\Helper\\Table::setStyle`::
 
-    // same as calling nothing
+    // this 'default' style is the one used when no style is specified
     $table->setStyle('default');
 
-    // changes the default style to compact
+Built-in Table Styles
+~~~~~~~~~~~~~~~~~~~~~
+
+**Compact**::
+
     $table->setStyle('compact');
     $table->render();
 
-This code results in:
+This outputs:
 
 .. code-block:: terminal
 
@@ -195,12 +220,12 @@ This code results in:
      960-425-059-0 The Lord of the Rings    J. R. R. Tolkien
      80-902734-1-6 And Then There Were None Agatha Christie
 
-You can also set the style to ``borderless``::
+**Borderless**::
 
     $table->setStyle('borderless');
     $table->render();
 
-which outputs:
+This outputs:
 
 .. code-block:: terminal
 
@@ -213,12 +238,12 @@ which outputs:
       80-902734-1-6   And Then There Were None   Agatha Christie
      =============== ========================== ==================
 
-You can also set the style to ``box``::
+**Box**::
 
     $table->setStyle('box');
     $table->render();
 
-which outputs:
+This outputs:
 
 .. code-block:: terminal
 
@@ -231,12 +256,12 @@ which outputs:
     │ 80-902734-1-6 │ And Then There Were None │ Agatha Christie  │
     └───────────────┴──────────────────────────┴──────────────────┘
 
-You can also set the style to ``box-double``::
+**Double box**::
 
     $table->setStyle('box-double');
     $table->render();
 
-which outputs:
+This outputs:
 
 .. code-block:: terminal
 
@@ -249,7 +274,30 @@ which outputs:
     ║ 80-902734-1-6 │ And Then There Were None │ Agatha Christie  ║
     ╚═══════════════╧══════════════════════════╧══════════════════╝
 
-If the built-in styles do not fit your need, define your own::
+**Markdown**::
+
+    $table->setStyle('markdown');
+    $table->render();
+
+This outputs:
+
+.. code-block:: terminal
+
+    | ISBN          | Title                    | Author           |
+    |---------------|--------------------------|------------------|
+    | 99921-58-10-7 | Divine Comedy            | Dante Alighieri  |
+    | 9971-5-0210-0 | A Tale of Two Cities     | Charles Dickens  |
+    | 960-425-059-0 | The Lord of the Rings    | J. R. R. Tolkien |
+    | 80-902734-1-6 | And Then There Were None | Agatha Christie  |
+
+.. versionadded:: 7.3
+
+    The ``markdown`` style was introduced in Symfony 7.3.
+
+Making a Custom Table Style
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If the built-in styles do not fit your needs, define your own::
 
     use Symfony\Component\Console\Helper\TableStyle;
 
@@ -339,7 +387,7 @@ To make a table cell that spans multiple columns you can use a :class:`Symfony\\
     ;
     $table->render();
 
-This results in:
+This outputs:
 
 .. code-block:: terminal
 
@@ -362,7 +410,7 @@ This results in:
         ]);
         // ...
 
-    This generates:
+    This outputs:
 
     .. code-block:: terminal
 
@@ -425,9 +473,10 @@ The only requirement to append rows is that the table must be rendered inside a
     use Symfony\Component\Console\Helper\Table;
     // ...
 
-    class SomeCommand extends Command
+    #[AsCommand(name: 'app:my-command')]
+    class MyCommand
     {
-        public function execute(InputInterface $input, OutputInterface $output): int
+        public function __invoke(OutputInterface $output): int
         {
             $section = $output->section();
             $table = new Table($section);
@@ -441,7 +490,7 @@ The only requirement to append rows is that the table must be rendered inside a
         }
     }
 
-This will display the following table in the terminal:
+This outputs:
 
 .. code-block:: terminal
 
@@ -462,7 +511,7 @@ This will display the following table in the terminal:
         $table->render();
         // ...
 
-    This will display:
+    This outputs:
 
     .. code-block:: terminal
 
diff --git a/components/console/helpers/tree.rst b/components/console/helpers/tree.rst
new file mode 100644
index 00000000000..5e08e684e51
--- /dev/null
+++ b/components/console/helpers/tree.rst
@@ -0,0 +1,336 @@
+Tree Helper
+===========
+
+The Tree Helper allows you to build and display tree structures in the console.
+It's commonly used to render directory hierarchies, but you can also use it to render
+any tree-like content, such us organizational charts, product category trees, taxonomies, etc.
+
+.. versionadded:: 7.3
+
+    The ``TreeHelper`` class was introduced in Symfony 7.3.
+
+Rendering a Tree
+----------------
+
+The :method:`Symfony\\Component\\Console\\Helper\\TreeHelper::createTree` method
+creates a tree structure from an array and returns a :class:`Symfony\\Component\\Console\\Helper\\Tree`
+object that can be rendered in the console.
+
+Rendering a Tree from an Array
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You can build a tree from an array by passing the array to the
+:method:`Symfony\\Component\\Console\\Helper\\TreeHelper::createTree` method
+inside your console command::
+
+    namespace App\Command;
+
+    use Symfony\Component\Console\Attribute\AsCommand;
+    use Symfony\Component\Console\Helper\TreeHelper;
+    use Symfony\Component\Console\Helper\TreeNode;
+    use Symfony\Component\Console\Style\SymfonyStyle;
+
+    #[AsCommand(name: 'app:my-command', description: '...')]
+    class MyCommand
+    {
+        // ...
+
+        public function __invoke(SymfonyStyle $io): int
+        {
+            $node = TreeNode::fromValues([
+                'config/',
+                'public/',
+                'src/',
+                'templates/',
+                'tests/',
+            ]);
+
+            $tree = TreeHelper::createTree($io, $node);
+            $tree->render();
+
+            // ...
+        }
+    }
+
+This exampe would output the following:
+
+.. code-block:: terminal
+
+    ├── config/
+    ├── public/
+    ├── src/
+    ├── templates/
+    └── tests/
+
+The given contents can be defined in a multi-dimensional array::
+
+    $tree = TreeHelper::createTree($io, null, [
+        'src' =>  [
+            'Command',
+            'Controller' => [
+                'DefaultController.php',
+            ],
+            'Kernel.php',
+        ],
+        'templates' => [
+            'base.html.twig',
+        ],
+    ]);
+
+    $tree->render();
+
+The above code will output the following tree:
+
+.. code-block:: terminal
+
+    ├── src
+    │   ├── Command
+    │   ├── Controller
+    │   │   └── DefaultController.php
+    │   └── Kernel.php
+    └── templates
+        └── base.html.twig
+
+Building a Tree Programmatically
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If you don't know the tree elements beforehand, you can build the tree programmatically
+by creating a new instance of the :class:`Symfony\\Component\\Console\\Helper\\Tree`
+class and adding nodes to it::
+
+    use Symfony\Component\Console\Helper\TreeHelper;
+    use Symfony\Component\Console\Helper\TreeNode;
+
+    $root = new TreeNode('my-project/');
+    // you can pass a string directly or create a TreeNode object
+    $root->addChild('src/');
+    $root->addChild(new TreeNode('templates/'));
+
+    // create nested structures by adding child nodes to other nodes
+    $testsNode = new TreeNode('tests/');
+    $functionalTestsNode = new TreeNode('Functional/');
+    $testsNode->addChild($functionalTestsNode);
+    $root->addChild($testsNode);
+
+    $tree = TreeHelper::createTree($io, $root);
+    $tree->render();
+
+This example outputs:
+
+.. code-block:: terminal
+
+    my-project/
+    ├── src/
+    ├── templates/
+    └── tests/
+        └── Functional/
+
+If you prefer, you can build the array of elements programmatically and then
+create and render the tree like this::
+
+    $tree = TreeHelper::createTree($io, null, $array);
+    $tree->render();
+
+You can also build part of the tree from an array and then add other nodes::
+
+    $node = TreeNode::fromValues($array);
+    $node->addChild('templates');
+    // ...
+    $tree = TreeHelper::createTree($io, $node);
+    $tree->render();
+
+Customizing the Tree Style
+--------------------------
+
+Built-in Tree Styles
+~~~~~~~~~~~~~~~~~~~~
+
+The tree helper provides a few built-in styles that you can use to customize the
+output of the tree.
+
+**Default**::
+
+    TreeHelper::createTree($io, $node, [], TreeStyle::default());
+
+This outputs:
+
+.. code-block:: terminal
+
+    ├── config
+    │   ├── packages
+    │   └── routes
+    │      ├── framework.yaml
+    │      └── web_profiler.yaml
+    ├── src
+    │   ├── Command
+    │   ├── Controller
+    │   │   └── DefaultController.php
+    │   └── Kernel.php
+    └── templates
+       └── base.html.twig
+
+**Box**::
+
+    TreeHelper::createTree($io, $node, [], TreeStyle::box());
+
+This outputs:
+
+.. code-block:: terminal
+
+    ┃╸ config
+    ┃  ┃╸ packages
+    ┃  ┗╸ routes
+    ┃     ┃╸ framework.yaml
+    ┃     ┗╸ web_profiler.yaml
+    ┃╸ src
+    ┃  ┃╸ Command
+    ┃  ┃╸ Controller
+    ┃  ┃  ┗╸ DefaultController.php
+    ┃  ┗╸ Kernel.php
+    ┗╸ templates
+       ┗╸ base.html.twig
+
+**Double box**::
+
+    TreeHelper::createTree($io, $node, [], TreeStyle::doubleBox());
+
+This outputs:
+
+.. code-block:: terminal
+
+    ╠═ config
+    ║  ╠═ packages
+    ║  ╚═ routes
+    ║    ╠═ framework.yaml
+    ║    ╚═ web_profiler.yaml
+    ╠═ src
+    ║  ╠═ Command
+    ║  ╠═ Controller
+    ║  ║  ╚═ DefaultController.php
+    ║  ╚═ Kernel.php
+    ╚═ templates
+      ╚═ base.html.twig
+
+**Compact**::
+
+    TreeHelper::createTree($io, $node, [], TreeStyle::compact());
+
+This outputs:
+
+.. code-block:: terminal
+
+    ├ config
+    │ ├ packages
+    │ └ routes
+    │   ├ framework.yaml
+    │   └ web_profiler.yaml
+    ├ src
+    │ ├ Command
+    │ ├ Controller
+    │ │ └ DefaultController.php
+    │ └ Kernel.php
+    └ templates
+      └ base.html.twig
+
+**Light**::
+
+    TreeHelper::createTree($io, $node, [], TreeStyle::light());
+
+This outputs:
+
+.. code-block:: terminal
+
+    |-- config
+    |   |-- packages
+    |   `-- routes
+    |       |-- framework.yaml
+    |       `-- web_profiler.yaml
+    |-- src
+    |   |-- Command
+    |   |-- Controller
+    |   |   `-- DefaultController.php
+    |   `-- Kernel.php
+    `-- templates
+        `-- base.html.twig
+
+**Minimal**::
+
+    TreeHelper::createTree($io, $node, [], TreeStyle::minimal());
+
+This outputs:
+
+.. code-block:: terminal
+
+    . config
+    . . packages
+    . . routes
+    .   . framework.yaml
+    .   . web_profiler.yaml
+    . src
+    . . Command
+    . . Controller
+    . . . DefaultController.php
+    . . Kernel.php
+    . templates
+      . base.html.twig
+
+**Rounded**::
+
+    TreeHelper::createTree($io, $node, [], TreeStyle::rounded());
+
+This outputs:
+
+.. code-block:: terminal
+
+    ├─ config
+    │  ├─ packages
+    │  ╰─ routes
+    │     ├─ framework.yaml
+    │     ╰─ web_profiler.yaml
+    ├─ src
+    │  ├─ Command
+    │  ├─ Controller
+    │  │  ╰─ DefaultController.php
+    │  ╰─ Kernel.php
+    ╰─ templates
+       ╰─ base.html.twig
+
+Making a Custom Tree Style
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You can create your own tree style by passing the characters to the constructor
+of the :class:`Symfony\\Component\\Console\\Helper\\TreeStyle` class::
+
+    use Symfony\Component\Console\Helper\TreeHelper;
+    use Symfony\Component\Console\Helper\TreeStyle;
+
+    $customStyle = new TreeStyle('🟣 ', '🟠 ', '🔵 ', '🟢 ', '🔴 ', '🟡 ');
+
+    // Pass the custom style to the createTree method
+
+    $tree = TreeHelper::createTree($io, null, [
+        'src' =>  [
+            'Command',
+            'Controller' => [
+                'DefaultController.php',
+            ],
+            'Kernel.php',
+        ],
+        'templates' => [
+            'base.html.twig',
+        ],
+    ], $customStyle);
+
+    $tree->render();
+
+The above code will output the following tree:
+
+.. code-block:: terminal
+
+    🔵 🟣 🟡 src
+    🔵 🟢 🟣 🟡 Command
+    🔵 🟢 🟣 🟡 Controller
+    🔵 🟢 🟢 🟠 🟡 DefaultController.php
+    🔵 🟢 🟠 🟡 Kernel.php
+    🔵 🟠 🟡 templates
+    🔵 🔴 🟠 🟡 base.html.twig
diff --git a/components/console/logger.rst b/components/console/logger.rst
index c3d5c447a89..cc182821a0a 100644
--- a/components/console/logger.rst
+++ b/components/console/logger.rst
@@ -34,7 +34,6 @@ You can rely on the logger to use this dependency inside a command::
     use Acme\MyDependency;
     use Symfony\Component\Console\Attribute\AsCommand;
     use Symfony\Component\Console\Command\Command;
-    use Symfony\Component\Console\Input\InputInterface;
     use Symfony\Component\Console\Logger\ConsoleLogger;
     use Symfony\Component\Console\Output\OutputInterface;
 
@@ -42,9 +41,9 @@ You can rely on the logger to use this dependency inside a command::
         name: 'my:command',
         description: 'Use an external dependency requiring a PSR-3 logger'
     )]
-    class MyCommand extends Command
+    class MyCommand
     {
-        protected function execute(InputInterface $input, OutputInterface $output): int
+        public function __invoke(OutputInterface $output): int
         {
             $logger = new ConsoleLogger($output);
 
diff --git a/components/console/single_command_tool.rst b/components/console/single_command_tool.rst
index 97cb09bf030..9c6b06537e2 100644
--- a/components/console/single_command_tool.rst
+++ b/components/console/single_command_tool.rst
@@ -9,19 +9,18 @@ it is possible to remove this need by declaring a single command application::
     <?php
     require __DIR__.'/vendor/autoload.php';
 
-    use Symfony\Component\Console\Input\InputArgument;
-    use Symfony\Component\Console\Input\InputInterface;
-    use Symfony\Component\Console\Input\InputOption;
+    use Symfony\Component\Console\Attribute\Argument;
+    use Symfony\Component\Console\Attribute\Option;
     use Symfony\Component\Console\Output\OutputInterface;
     use Symfony\Component\Console\SingleCommandApplication;
 
     (new SingleCommandApplication())
         ->setName('My Super Command') // Optional
         ->setVersion('1.0.0') // Optional
-        ->addArgument('foo', InputArgument::OPTIONAL, 'The directory')
-        ->addOption('bar', null, InputOption::VALUE_REQUIRED)
-        ->setCode(function (InputInterface $input, OutputInterface $output): int {
+        ->setCode(function (OutputInterface $output, #[Argument] string $foo = 'The directory', #[Option] string $bar = ''): int {
             // output arguments and options
+
+            return 0;
         })
         ->run();
 
diff --git a/components/console/usage.rst b/components/console/usage.rst
index d7725e8926e..591994948b8 100644
--- a/components/console/usage.rst
+++ b/components/console/usage.rst
@@ -65,9 +65,17 @@ You can suppress output with:
 
 .. code-block:: terminal
 
+    # suppresses all output, including errors
+    $ php application.php list --silent
+
+    # suppresses all output except errors
     $ php application.php list --quiet
     $ php application.php list -q
 
+.. versionadded:: 7.2
+
+    The ``--silent`` option was introduced in Symfony 7.2.
+
 You can get more verbose messages (if this is supported for a command)
 with:
 
diff --git a/components/dependency_injection.rst b/components/dependency_injection.rst
index 93e8af711cf..d146f553a0c 100644
--- a/components/dependency_injection.rst
+++ b/components/dependency_injection.rst
@@ -180,16 +180,16 @@ You can override this behavior as follows::
 
 These are all the possible behaviors:
 
- * ``ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE``: throws an exception
-   at compile time (this is the **default** behavior);
- * ``ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE``: throws an
-   exception at runtime, when trying to access the missing service;
- * ``ContainerInterface::NULL_ON_INVALID_REFERENCE``: returns ``null``;
- * ``ContainerInterface::IGNORE_ON_INVALID_REFERENCE``: ignores the wrapping
-   command asking for the reference (for instance, ignore a setter if the service
-   does not exist);
- * ``ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE``: ignores/returns
-   ``null`` for uninitialized services or invalid references.
+* ``ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE``: throws an exception
+  at compile time (this is the **default** behavior);
+* ``ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE``: throws an
+  exception at runtime, when trying to access the missing service;
+* ``ContainerInterface::NULL_ON_INVALID_REFERENCE``: returns ``null``;
+* ``ContainerInterface::IGNORE_ON_INVALID_REFERENCE``: ignores the wrapping
+  command asking for the reference (for instance, ignore a setter if the service
+  does not exist);
+* ``ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE``: ignores/returns
+  ``null`` for uninitialized services or invalid references.
 
 Avoiding your Code Becoming Dependent on the Container
 ------------------------------------------------------
diff --git a/components/dependency_injection/compilation.rst b/components/dependency_injection/compilation.rst
index 7f991e85b72..c79281b5c27 100644
--- a/components/dependency_injection/compilation.rst
+++ b/components/dependency_injection/compilation.rst
@@ -497,11 +497,12 @@ serves at dumping the compiled container::
 
 .. tip::
 
-    The ``file_put_contents()`` function is not atomic. That could cause issues
-    in a production environment with multiple concurrent requests. Instead, use
-    the :ref:`dumpFile() method <filesystem-dumpfile>` from Symfony Filesystem
-    component or other methods provided by Symfony (e.g. ``$containerConfigCache->write()``)
-    which are atomic.
+    The ``file_put_contents()`` function is not atomic. This can cause issues in
+    production environments with multiple concurrent requests. Instead, use the
+    :ref:`dumpFile() method <filesystem-dumpfile>` from the
+    :doc:`Filesystem component </components/filesystem>` or other atomic methods
+    provided by Symfony (e.g. the ``$containerConfigCache->write()`` method from
+    the :doc:`Config component </components/config>`).
 
 ``ProjectServiceContainer`` is the default name given to the dumped container
 class. However, you can change this with the ``class`` option when you
@@ -607,3 +608,25 @@ have the cache will be considered stale.
 
     In the full-stack framework the compilation and caching of the container
     is taken care of for you.
+
+.. _resolving-env-vars-at-compile-time:
+
+Resolving Environment Variable At Compile Time
+----------------------------------------------
+
+.. warning::
+
+    **This practice is discouraged**. Use it only if you fully understand the implications.
+
+By default, environment variables are resolved at runtime. However, you can
+force their resolution at compile time using the following code::
+
+    $parameterValue = $container->resolveEnvPlaceholders(
+        $container->getParameter('%env(ENV_VAR_NAME)%'),
+        true // resolve to actual values
+    );
+
+However, a **major drawback** of this approach is that you must manually clear
+the cache when changing the value of an environment variable. This goes
+against the typical behavior of environment variables, which are designed
+to be dynamic and not require cache invalidation.
diff --git a/components/event_dispatcher.rst b/components/event_dispatcher.rst
index 8cd676dd5fe..62a3707bb39 100644
--- a/components/event_dispatcher.rst
+++ b/components/event_dispatcher.rst
@@ -277,8 +277,9 @@ Dispatch the Event
 
 The :method:`Symfony\\Component\\EventDispatcher\\EventDispatcher::dispatch`
 method notifies all listeners of the given event. It takes two arguments:
-the ``Event`` instance to pass to each listener of that event and the name
-of the event to dispatch::
+the ``Event`` instance to pass to each listener of that event and optionally the
+name of the event to dispatch. If it's not defined, the class of the ``Event``
+instance will be used::
 
     use Acme\Store\Event\OrderPlacedEvent;
     use Acme\Store\Order;
diff --git a/components/filesystem.rst b/components/filesystem.rst
index dabf3f81872..4eae6aaad27 100644
--- a/components/filesystem.rst
+++ b/components/filesystem.rst
@@ -337,7 +337,8 @@ Dealing with file paths usually involves some difficulties:
 - Platform differences: file paths look different on different platforms. UNIX
   file paths start with a slash ("/"), while Windows file paths start with a
   system drive ("C:"). UNIX uses forward slashes, while Windows uses backslashes
-  by default.
+  by default. However, Windows also accepts forward slashes, so both types of
+  separators generally work.
 - Absolute/relative paths: web applications frequently need to deal with absolute
   and relative paths. Converting one to the other properly is tricky and repetitive.
 
@@ -375,6 +376,45 @@ Malformed paths are returned unchanged::
     echo Path::canonicalize('C:Programs/PHP/php.ini');
     // => C:Programs/PHP/php.ini
 
+Joining Paths
+~~~~~~~~~~~~~
+
+The :method:`Symfony\\Component\\Filesystem\\Path::join` method concatenates
+the given paths and normalizes separators. It's a cleaner alternative to
+string concatenation for building file paths::
+
+    echo Path::join('/var/www', 'vhost', 'config.ini');
+    // => /var/www/vhost/config.ini
+
+    echo Path::join('C:\\Program Files', 'PHP', 'php.ini');
+    // => C:/Program Files/PHP/php.ini
+    // (both forward slashes and backslashes work on Windows)
+
+The ``join()`` method handles multiple scenarios correctly:
+
+Empty parts are ignored::
+
+    echo Path::join('/var/www', '', 'config.ini');
+    // => /var/www/config.ini
+
+Leading slashes in subsequent arguments are removed::
+
+    echo Path::join('/var/www', '/etc', 'config.ini');
+    // => /var/www/etc/config.ini
+
+Trailing slashes are preserved only for root paths::
+
+    echo Path::join('/var/www', 'vhost/');
+    // => /var/www/vhost
+
+    echo Path::join('/', '');
+    // => /
+
+Works with any number of arguments::
+
+    echo Path::join('/var', 'www', 'vhost', 'symfony', 'config', 'config.ini');
+    // => /var/www/vhost/symfony/config/config.ini
+
 Converting Absolute/Relative Paths
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
diff --git a/components/form.rst b/components/form.rst
index 5e09f38812f..44f407e4c8e 100644
--- a/components/form.rst
+++ b/components/form.rst
@@ -123,8 +123,7 @@ The following snippet adds CSRF protection to the form factory::
     use Symfony\Component\Security\Csrf\TokenStorage\SessionTokenStorage;
 
     // creates a RequestStack object using the current request
-    $requestStack = new RequestStack();
-    $requestStack->push($request);
+    $requestStack = new RequestStack([$request]);
 
     $csrfGenerator = new UriSafeTokenGenerator();
     $csrfStorage = new SessionTokenStorage($requestStack);
@@ -135,6 +134,11 @@ The following snippet adds CSRF protection to the form factory::
         ->addExtension(new CsrfExtension($csrfManager))
         ->getFormFactory();
 
+.. versionadded:: 7.2
+
+    Support for passing requests to the constructor of the ``RequestStack``
+    class was introduced in Symfony 7.2.
+
 Internally, this extension will automatically add a hidden field to every
 form (called ``_token`` by default) whose value is automatically generated by
 the CSRF generator and validated when binding the form.
diff --git a/components/http_foundation.rst b/components/http_foundation.rst
index 21e9bbfb13e..1cb87aafb24 100644
--- a/components/http_foundation.rst
+++ b/components/http_foundation.rst
@@ -362,6 +362,25 @@ analysis purposes. Use the ``anonymize()`` method from the
     $anonymousIpv6 = IpUtils::anonymize($ipv6);
     // $anonymousIpv6 = '2a01:198:603:10::'
 
+If you need even more anonymization, you can use the second and third parameters
+of the ``anonymize()`` method to specify the number of bytes that should be
+anonymized depending on the IP address format::
+
+    $ipv4 = '123.234.235.236';
+    $anonymousIpv4 = IpUtils::anonymize($ipv4, 3);
+    // $anonymousIpv4 = '123.0.0.0'
+
+    $ipv6 = '2a01:198:603:10:396e:4789:8e99:890f';
+    // (you must define the second argument (bytes to anonymize in IPv4 addresses)
+    // even when you are only anonymizing IPv6 addresses)
+    $anonymousIpv6 = IpUtils::anonymize($ipv6, 3, 10);
+    // $anonymousIpv6 = '2a01:198:603::'
+
+.. versionadded:: 7.2
+
+    The ``v4Bytes`` and ``v6Bytes`` parameters of the ``anonymize()`` method
+    were introduced in Symfony 7.2.
+
 Check If an IP Belongs to a CIDR Subnet
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -662,8 +681,19 @@ Streaming a Response
 ~~~~~~~~~~~~~~~~~~~~
 
 The :class:`Symfony\\Component\\HttpFoundation\\StreamedResponse` class allows
-you to stream the Response back to the client. The response content is
-represented by a PHP callable instead of a string::
+you to stream the Response back to the client. The response content can be
+represented by a string iterable::
+
+    use Symfony\Component\HttpFoundation\StreamedResponse;
+
+    $chunks = ['Hello', ' World'];
+
+    $response = new StreamedResponse();
+    $response->setChunks($chunks);
+    $response->send();
+
+For most complex use cases, the response content can be instead represented by
+a PHP callable::
 
     use Symfony\Component\HttpFoundation\StreamedResponse;
 
@@ -691,6 +721,10 @@ represented by a PHP callable instead of a string::
         // disables FastCGI buffering in nginx only for this response
         $response->headers->set('X-Accel-Buffering', 'no');
 
+.. versionadded:: 7.3
+
+    Support for using string iterables was introduced in Symfony 7.3.
+
 Streaming a JSON Response
 ~~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -818,9 +852,10 @@ Alternatively, if you are serving a static file, you can use a
 
 The ``BinaryFileResponse`` will automatically handle ``Range`` and
 ``If-Range`` headers from the request. It also supports ``X-Sendfile``
-(see for `nginx`_ and `Apache`_). To make use of it, you need to determine
-whether or not the ``X-Sendfile-Type`` header should be trusted and call
-:method:`Symfony\\Component\\HttpFoundation\\BinaryFileResponse::trustXSendfileTypeHeader`
+(see `FrankenPHP X-Sendfile and X-Accel-Redirect headers`_,
+`nginx X-Accel-Redirect header`_ and `Apache mod_xsendfile module`_). To make use
+of it, you need to determine whether or not the ``X-Sendfile-Type`` header should
+be trusted and call :method:`Symfony\\Component\\HttpFoundation\\BinaryFileResponse::trustXSendfileTypeHeader`
 if it should::
 
     BinaryFileResponse::trustXSendfileTypeHeader();
@@ -1042,8 +1077,9 @@ Learn More
     /session
     /http_cache/*
 
-.. _nginx: https://mattbrictson.com/blog/accelerated-rails-downloads
-.. _Apache: https://tn123.org/mod_xsendfile/
+.. _`FrankenPHP X-Sendfile and X-Accel-Redirect headers`: https://frankenphp.dev/docs/x-sendfile/
+.. _`nginx X-Accel-Redirect header`: https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_ignore_headers
+.. _`Apache mod_xsendfile module`: https://github.com/nmaier/mod_xsendfile
 .. _`JSON Hijacking`: https://haacked.com/archive/2009/06/25/json-hijacking.aspx/
 .. _`valid JSON top-level value`: https://www.json.org/json-en.html
 .. _OWASP guidelines: https://cheatsheetseries.owasp.org/cheatsheets/AJAX_Security_Cheat_Sheet.html#always-return-json-with-an-object-on-the-outside
diff --git a/components/http_kernel.rst b/components/http_kernel.rst
index 02791b370bc..62d1e92d89b 100644
--- a/components/http_kernel.rst
+++ b/components/http_kernel.rst
@@ -474,8 +474,8 @@ as possible to the client (e.g. sending emails).
 .. warning::
 
     Internally, the HttpKernel makes use of the :phpfunction:`fastcgi_finish_request`
-    PHP function. This means that at the moment, only the `PHP FPM`_ server
-    API is able to send a response to the client while the server's PHP process
+    PHP function. This means that at the moment, only the `PHP FPM`_ API and the
+    `FrankenPHP`_ server are able to send a response to the client while the server's PHP process
     still performs some tasks. With all other server APIs, listeners to ``kernel.terminate``
     are still executed, but the response is not sent to the client until they
     are all completed.
@@ -758,3 +758,4 @@ Learn more
 .. _FOSRestBundle: https://github.com/friendsofsymfony/FOSRestBundle
 .. _`PHP FPM`: https://www.php.net/manual/en/install.fpm.php
 .. _variadic: https://www.php.net/manual/en/functions.arguments.php#functions.variable-arg-list
+.. _`FrankenPHP`: https://frankenphp.dev
diff --git a/components/json_path.rst b/components/json_path.rst
new file mode 100644
index 00000000000..9db8e48885e
--- /dev/null
+++ b/components/json_path.rst
@@ -0,0 +1,330 @@
+The JsonPath Component
+======================
+
+.. versionadded:: 7.3
+
+    The JsonPath component was introduced in Symfony 7.3 as an
+    :doc:`experimental feature </contributing/code/experimental>`.
+
+The JsonPath component lets you query and extract data from JSON structures.
+It implements the `RFC 9535 – JSONPath`_ standard, allowing you to navigate
+complex JSON data.
+
+Similar to the :doc:`DomCrawler component </components/dom_crawler>`, which lets
+you navigate and query HTML or XML documents with XPath, the JsonPath component
+offers the same convenience for traversing and searching JSON structures through
+JSONPath expressions. The component also provides an abstraction layer for data
+extraction.
+
+Installation
+------------
+
+You can install the component in your project using Composer:
+
+.. code-block:: terminal
+
+    $ composer require symfony/json-path
+
+.. include:: /components/require_autoload.rst.inc
+
+Usage
+-----
+
+To start querying a JSON document, first create a :class:`Symfony\\Component\\JsonPath\\JsonCrawler`
+object from a JSON string. The following examples use this sample "bookstore"
+JSON data::
+
+    use Symfony\Component\JsonPath\JsonCrawler;
+
+    $json = <<<'JSON'
+    {
+        "store": {
+            "book": [
+                {
+                    "category": "reference",
+                    "author": "Nigel Rees",
+                    "title": "Sayings of the Century",
+                    "price": 8.95
+                },
+                {
+                    "category": "fiction",
+                    "author": "Evelyn Waugh",
+                    "title": "Sword of Honour",
+                    "price": 12.99
+                },
+                {
+                    "category": "fiction",
+                    "author": "Herman Melville",
+                    "title": "Moby Dick",
+                    "isbn": "0-553-21311-3",
+                    "price": 8.99
+                },
+                {
+                    "category": "fiction",
+                    "author": "John Ronald Reuel Tolkien",
+                    "title": "The Lord of the Rings",
+                    "isbn": "0-395-19395-8",
+                    "price": 22.99
+                }
+            ],
+            "bicycle": {
+                "color": "red",
+                "price": 399
+            }
+        }
+    }
+    JSON;
+
+    $crawler = new JsonCrawler($json);
+
+Once you have the crawler instance, use its :method:`Symfony\\Component\\JsonPath\\JsonCrawler::find`
+method to start querying the data. This method returns an array of matching values.
+
+Querying with Expressions
+-------------------------
+
+The primary way to query the JSON is by passing a JSONPath expression string
+to the :method:`Symfony\\Component\\JsonPath\\JsonCrawler::find` method.
+
+Accessing a Specific Property
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Use dot notation for object keys and square brackets for array indices. The root
+of the document is represented by ``$``::
+
+    // get the title of the first book in the store
+    $titles = $crawler->find('$.store.book[0].title');
+
+    // $titles is ['Sayings of the Century']
+
+Dot notation is the default, but JSONPath provides other syntaxes for cases
+where it doesn't work. Use bracket notation (``['...']``) when a key contains
+spaces or special characters::
+
+    // this is equivalent to the previous example
+    $titles = $crawler->find('$["store"]["book"][0]["title"]');
+
+    // this expression requires brackets because some keys use dots or spaces
+    $titles = $crawler->find('$["store"]["book collection"][0]["title.original"]');
+
+    // you can combine both notations
+    $titles = $crawler->find('$["store"].book[0].title');
+
+Searching with the Descendant Operator
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The descendant operator (``..``) recursively searches for a given key, allowing
+you to find values without specifying the full path::
+
+    // get all authors from anywhere in the document
+    $authors = $crawler->find('$..author');
+
+    // $authors is ['Nigel Rees', 'Evelyn Waugh', 'Herman Melville', 'John Ronald Reuel Tolkien']
+
+Filtering Results
+~~~~~~~~~~~~~~~~~
+
+JSONPath includes a filter syntax (``?(expression)``) to select items based on
+a condition. The current item within the filter is referenced by ``@``::
+
+    // get all books with a price less than 10
+    $cheapBooks = $crawler->find('$.store.book[?(@.price < 10)]');
+
+Building Queries Programmatically
+---------------------------------
+
+For more dynamic or complex query building, use the fluent API provided
+by the :class:`Symfony\\Component\\JsonPath\\JsonPath` class. This lets you
+construct a query object step by step. The ``JsonPath`` object can then be passed
+to the crawler's :method:`Symfony\\Component\\JsonPath\\JsonCrawler::find` method.
+
+The main advantage of the programmatic builder is that it automatically handles
+escaping of keys and values, preventing syntax errors::
+
+    use Symfony\Component\JsonPath\JsonPath;
+
+    $path = (new JsonPath())
+        ->key('store') // selects the 'store' key
+        ->key('book')  // then the 'book' key
+        ->index(1);    // then the second item (indexes start at 0)
+
+    // the created $path object is equivalent to the string '$["store"]["book"][1]'
+    $book = $crawler->find($path);
+
+    // $book contains the book object for "Sword of Honour"
+
+The :class:`Symfony\\Component\\JsonPath\\JsonPath` class provides several
+methods to build your query:
+
+* :method:`Symfony\\Component\\JsonPath\\JsonPath::key`
+  Adds a key selector. The key name is properly escaped::
+
+      // creates the path '$["key\"with\"quotes"]'
+      $path = (new JsonPath())->key('key"with"quotes');
+
+* :method:`Symfony\\Component\\JsonPath\\JsonPath::deepScan`
+  Adds the descendant operator ``..`` to perform a recursive search from the
+  current point in the path::
+
+      // get all prices in the store: '$["store"]..["price"]'
+      $path = (new JsonPath())->key('store')->deepScan()->key('price');
+
+* :method:`Symfony\\Component\\JsonPath\\JsonPath::all`
+  Adds the wildcard operator ``[*]`` to select all items in an array or object::
+
+        // creates the path '$["store"]["book"][*]'
+        $path = (new JsonPath())->key('store')->key('book')->all();
+
+* :method:`Symfony\\Component\\JsonPath\\JsonPath::index`
+  Adds an array index selector. Index numbers start at ``0``.
+
+* :method:`Symfony\\Component\\JsonPath\\JsonPath::first` /
+  :method:`Symfony\\Component\\JsonPath\\JsonPath::last`
+  Shortcuts for ``index(0)`` and ``index(-1)`` respectively::
+
+      // get the last book: '$["store"]["book"][-1]'
+      $path = (new JsonPath())->key('store')->key('book')->last();
+
+* :method:`Symfony\\Component\\JsonPath\\JsonPath::slice`
+  Adds an array slice selector ``[start:end:step]``::
+
+      // get books from index 1 up to (but not including) index 3
+      // creates the path '$["store"]["book"][1:3]'
+      $path = (new JsonPath())->key('store')->key('book')->slice(1, 3);
+
+      // get every second book from the first four books
+      // creates the path '$["store"]["book"][0:4:2]'
+      $path = (new JsonPath())->key('store')->key('book')->slice(0, 4, 2);
+
+* :method:`Symfony\\Component\\JsonPath\\JsonPath::filter`
+  Adds a filter expression. The expression string is the part that goes inside
+  the ``?()`` syntax::
+
+      // get expensive books: '$["store"]["book"][?(@.price > 20)]'
+      $path = (new JsonPath())
+          ->key('store')
+          ->key('book')
+          ->filter('@.price > 20');
+
+Advanced Querying
+-----------------
+
+For a complete overview of advanced operators like wildcards and functions within
+filters, refer to the `Querying with Expressions`_ section above. All these
+features are supported and can be combined with the programmatic builder when
+appropriate (e.g., inside a ``filter()`` expression).
+
+Testing with JSON Assertions
+----------------------------
+
+The component provides a set of PHPUnit assertions to make testing JSON data
+more convenient. Use the :class:`Symfony\\Component\\JsonPath\\Test\\JsonPathAssertionsTrait`
+in your test class::
+
+    use PHPUnit\Framework\TestCase;
+    use Symfony\Component\JsonPath\Test\JsonPathAssertionsTrait;
+
+    class MyTest extends TestCase
+    {
+        use JsonPathAssertionsTrait;
+
+        public function testSomething(): void
+        {
+            $json = '{"books": [{"title": "A"}, {"title": "B"}]}';
+
+            self::assertJsonPathCount(2, '$.books[*]', $json);
+        }
+    }
+
+The trait provides the following assertion methods:
+
+* :method:`Symfony\\Component\\JsonPath\\Test\\JsonPathAssertionsTrait::assertJsonPathCount`
+  Asserts that the number of elements found by the JSONPath expression matches
+  an expected count::
+
+      $json = '{"a": [1, 2, 3]}';
+      self::assertJsonPathCount(3, '$.a[*]', $json);
+
+* :method:`Symfony\\Component\\JsonPath\\Test\\JsonPathAssertionsTrait::assertJsonPathEquals`
+  Asserts that the result of a JSONPath expression is equal to an expected
+  value. The comparison uses ``==`` (type coercion) instead of ``===``::
+
+      $json = '{"a": [1, 2, 3]}';
+
+      // passes because "1" == 1
+      self::assertJsonPathEquals(['1'], '$.a[0]', $json);
+
+* :method:`Symfony\\Component\\JsonPath\\Test\\JsonPathAssertionsTrait::assertJsonPathNotEquals`
+  Asserts that the result of a JSONPath expression is not equal to an expected
+  value. The comparison uses ``!=`` (type coercion) instead of ``!==``::
+
+      $json = '{"a": [1, 2, 3]}';
+      self::assertJsonPathNotEquals([42], '$.a[0]', $json);
+
+* :method:`Symfony\\Component\\JsonPath\\Test\\JsonPathAssertionsTrait::assertJsonPathSame`
+  Asserts that the result of a JSONPath expression is identical (``===``) to an
+  expected value. This is a strict comparison and does not perform type
+  coercion::
+
+      $json = '{"a": [1, 2, 3]}';
+
+      // fails because "1" !== 1
+      // self::assertJsonPathSame(['1'], '$.a[0]', $json);
+
+      self::assertJsonPathSame([1], '$.a[0]', $json);
+
+* :method:`Symfony\\Component\\JsonPath\\Test\\JsonPathAssertionsTrait::assertJsonPathNotSame`
+  Asserts that the result of a JSONPath expression is not identical (``!==``) to
+  an expected value::
+
+      $json = '{"a": [1, 2, 3]}';
+      self::assertJsonPathNotSame(['1'], '$.a[0]', $json);
+
+* :method:`Symfony\\Component\\JsonPath\\Test\\JsonPathAssertionsTrait::assertJsonPathContains`
+  Asserts that a given value is found within the array of results from the
+  JSONPath expression::
+
+      $json = '{"tags": ["php", "symfony", "json"]}';
+      self::assertJsonPathContains('symfony', '$.tags[*]', $json);
+
+* :method:`Symfony\\Component\\JsonPath\\Test\\JsonPathAssertionsTrait::assertJsonPathNotContains`
+  Asserts that a given value is NOT found within the array of results from the
+  JSONPath expression::
+
+      $json = '{"tags": ["php", "symfony", "json"]}';
+      self::assertJsonPathNotContains('java', '$.tags[*]', $json);
+
+Error Handling
+--------------
+
+The component throws specific exceptions for invalid input or queries:
+
+* :class:`Symfony\\Component\\JsonPath\\Exception\\InvalidArgumentException`:
+  Thrown if the input to the ``JsonCrawler`` constructor is not a valid JSON string;
+* :class:`Symfony\\Component\\JsonPath\\Exception\\InvalidJsonStringInputException`:
+  Thrown during a ``find()`` call if the JSON string is malformed (e.g., syntax error);
+* :class:`Symfony\\Component\\JsonPath\\Exception\\JsonCrawlerException`:
+  Thrown for errors within the JsonPath expression itself, such as using an
+  unknown function
+
+Example of handling errors::
+
+    use Symfony\Component\JsonPath\Exception\InvalidJsonStringInputException;
+    use Symfony\Component\JsonPath\Exception\JsonCrawlerException;
+
+    try {
+        // the following line contains malformed JSON
+        $crawler = new JsonCrawler('{"store": }');
+        $crawler->find('$..*');
+    } catch (InvalidJsonStringInputException $e) {
+        // ... handle error
+    }
+
+    try {
+        // the following line contains an invalid query
+        $crawler->find('$.store.book[?unknown_function(@.price)]');
+    } catch (JsonCrawlerException $e) {
+        // ... handle error
+    }
+
+.. _`RFC 9535 – JSONPath`: https://datatracker.ietf.org/doc/html/rfc9535
diff --git a/components/ldap.rst b/components/ldap.rst
index f5a142ced9f..e52a341986c 100644
--- a/components/ldap.rst
+++ b/components/ldap.rst
@@ -74,6 +74,19 @@ distinguished name (DN) and the password of a user::
 
     When the LDAP server allows unauthenticated binds, a blank password will always be valid.
 
+You can also use the :method:`Symfony\\Component\\Ldap\\Ldap::saslBind` method
+for binding to an LDAP server using `SASL`_::
+
+    // this method defines other optional arguments like $mech, $realm, $authcId, etc.
+    $ldap->saslBind($dn, $password);
+
+After binding to the LDAP server, you can use the :method:`Symfony\\Component\\Ldap\\Ldap::whoami`
+method to get the distinguished name (DN) of the authenticated and authorized user.
+
+.. versionadded:: 7.2
+
+    The ``saslBind()`` and ``whoami()`` methods were introduced in Symfony 7.2.
+
 Once bound (or if you enabled anonymous authentication on your
 LDAP server), you may query the LDAP server using the
 :method:`Symfony\\Component\\Ldap\\Ldap::query` method::
@@ -183,3 +196,5 @@ Possible operation types are ``LDAP_MODIFY_BATCH_ADD``, ``LDAP_MODIFY_BATCH_REMO
 ``LDAP_MODIFY_BATCH_REMOVE_ALL``, ``LDAP_MODIFY_BATCH_REPLACE``. Parameter
 ``$values`` must be ``NULL`` when using ``LDAP_MODIFY_BATCH_REMOVE_ALL``
 operation type.
+
+.. _`SASL`: https://en.wikipedia.org/wiki/Simple_Authentication_and_Security_Layer
diff --git a/components/lock.rst b/components/lock.rst
index b0240019e8d..e9fe61ecd1a 100644
--- a/components/lock.rst
+++ b/components/lock.rst
@@ -403,8 +403,14 @@ Store                                                       Scope   Blocking  Ex
 
 .. tip::
 
-    A special ``InMemoryStore`` is available for saving locks in memory during
-    a process, and can be useful for testing.
+    Symfony includes two other special stores that are mostly useful for testing:
+
+    * ``InMemoryStore`` (``LOCK_DSN=in-memory``), which saves locks in memory during a process;
+    * ``NullStore`` (``LOCK_DSN=null``) which doesn't persist anything.
+
+.. versionadded:: 7.2
+
+    The :class:`Symfony\\Component\\Lock\\Store\\NullStore` was introduced in Symfony 7.2.
 
 .. _lock-store-flock:
 
@@ -607,9 +613,9 @@ RedisStore
 ~~~~~~~~~~
 
 The RedisStore saves locks on a Redis server, it requires a Redis connection
-implementing the ``\Redis``, ``\RedisArray``, ``\RedisCluster``, ``\Relay\Relay`` or
-``\Predis`` classes. This store does not support blocking, and expects a TTL to
-avoid stalled locks::
+implementing the ``\Redis``, ``\RedisArray``, ``\RedisCluster``, ``\Relay\Relay``,
+``\Relay\Cluster`` or ``\Predis`` classes. This store does not support blocking,
+and expects a TTL to avoid stalled locks::
 
     use Symfony\Component\Lock\Store\RedisStore;
 
@@ -618,6 +624,10 @@ avoid stalled locks::
 
     $store = new RedisStore($redis);
 
+.. versionadded:: 7.3
+
+    Support for ``Relay\Cluster`` was introduced in Symfony 7.3.
+
 .. _lock-store-semaphore:
 
 SemaphoreStore
diff --git a/components/messenger.rst b/components/messenger.rst
index 8d6652fb160..a8ff1e5290e 100644
--- a/components/messenger.rst
+++ b/components/messenger.rst
@@ -361,5 +361,5 @@ Learn more
     /messenger
     /messenger/*
 
-.. _`blog posts about command buses`: https://matthiasnoback.nl/tags/command%20bus/
+.. _`blog posts about command buses`: https://matthiasnoback.nl/tags/command-bus/
 .. _`SimpleBus project`: https://docs.simplebus.io/en/latest/
diff --git a/components/options_resolver.rst b/components/options_resolver.rst
index ff25f9e0fc4..17ec46c2fc9 100644
--- a/components/options_resolver.rst
+++ b/components/options_resolver.rst
@@ -305,13 +305,21 @@ correctly. To validate the types of the options, call
 
             // specify multiple allowed types
             $resolver->setAllowedTypes('port', ['null', 'int']);
+            // if you prefer, you can also use the following equivalent syntax
+            $resolver->setAllowedTypes('port', 'int|null');
 
             // check all items in an array recursively for a type
             $resolver->setAllowedTypes('dates', 'DateTime[]');
             $resolver->setAllowedTypes('ports', 'int[]');
+            // the following syntax means "an array of integers or an array of strings"
+            $resolver->setAllowedTypes('endpoints', '(int|string)[]');
         }
     }
 
+.. versionadded:: 7.3
+
+    Defining type unions with the ``|`` syntax was introduced in Symfony 7.3.
+
 You can pass any type for which an ``is_<type>()`` function is defined in PHP.
 You may also pass fully qualified class or interface names (which is checked
 using ``instanceof``). Additionally, you can validate all items in an array
@@ -386,7 +394,7 @@ returns ``true`` for acceptable values and ``false`` for invalid values::
 
         // ...
         $resolver->setAllowedValues('transport', Validation::createIsValidCallable(
-            new Length(['min' => 10 ])
+            new Length(min: 10)
         ));
 
 In sub-classes, you can use :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::addAllowedValues`
@@ -654,7 +662,7 @@ default value::
 
         public function configureOptions(OptionsResolver $resolver): void
         {
-            $resolver->setDefault('spool', function (OptionsResolver $spoolResolver): void {
+            $resolver->setOptions('spool', function (OptionsResolver $spoolResolver): void {
                 $spoolResolver->setDefaults([
                     'type' => 'file',
                     'path' => '/path/to/spool',
@@ -678,6 +686,16 @@ default value::
         ],
     ]);
 
+.. deprecated:: 7.3
+
+    Defining nested options via :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::setDefault`
+    is deprecated since Symfony 7.3. Use the :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::setOptions`
+    method instead, which also allows defining default values for prototyped options.
+
+.. versionadded:: 7.3
+
+    The ``setOptions()`` method was introduced in Symfony 7.3.
+
 Nested options also support required options, validation (type, value) and
 normalization of their values. If the default value of a nested option depends
 on another option defined in the parent level, add a second ``Options`` argument
@@ -690,7 +708,7 @@ to the closure to access to them::
         public function configureOptions(OptionsResolver $resolver): void
         {
             $resolver->setDefault('sandbox', false);
-            $resolver->setDefault('spool', function (OptionsResolver $spoolResolver, Options $parent): void {
+            $resolver->setOptions('spool', function (OptionsResolver $spoolResolver, Options $parent): void {
                 $spoolResolver->setDefaults([
                     'type' => $parent['sandbox'] ? 'memory' : 'file',
                     // ...
@@ -713,13 +731,13 @@ In same way, parent options can access to the nested options as normal arrays::
 
         public function configureOptions(OptionsResolver $resolver): void
         {
-            $resolver->setDefault('spool', function (OptionsResolver $spoolResolver): void {
+            $resolver->setOptions('spool', function (OptionsResolver $spoolResolver): void {
                 $spoolResolver->setDefaults([
                     'type' => 'file',
                     // ...
                 ]);
             });
-            $resolver->setDefault('profiling', function (Options $options): void {
+            $resolver->setOptions('profiling', function (Options $options): void {
                 return 'file' === $options['spool']['type'];
             });
         }
@@ -740,7 +758,7 @@ with ``host``, ``database``, ``user`` and ``password`` each.
 
 The best way to implement this is to define the ``connections`` option as prototype::
 
-    $resolver->setDefault('connections', function (OptionsResolver $connResolver): void {
+    $resolver->setOptions('connections', function (OptionsResolver $connResolver): void {
         $connResolver
             ->setPrototype(true)
             ->setRequired(['host', 'database'])
@@ -918,7 +936,7 @@ can change your code to do the configuration only once per class::
         public function __construct(array $options = [])
         {
             // What type of Mailer is this, a Mailer, a GoogleMailer, ... ?
-            $class = get_class($this);
+            $class = $this::class;
 
             // Was configureOptions() executed before for this class?
             if (!isset(self::$resolversByClass[$class])) {
diff --git a/components/process.rst b/components/process.rst
index f6c8837d2c3..9c25c931510 100644
--- a/components/process.rst
+++ b/components/process.rst
@@ -114,6 +114,8 @@ You can configure the options passed to the ``other_options`` argument of
     and ``suppress_errors``) are only supported on Windows operating systems.
     Check out the `PHP documentation for proc_open()`_ before using them.
 
+.. _process-using-features-from-the-os-shell:
+
 Using Features From the OS Shell
 --------------------------------
 
@@ -428,11 +430,14 @@ However, if you run the command via the Symfony ``Process`` class, PHP will use
 the settings defined in the ``php.ini`` file. You can solve this issue by using
 the :class:`Symfony\\Component\\Process\\PhpSubprocess` class to run the command::
 
+    use Symfony\Component\Console\Attribute\AsCommand;
+    use Symfony\Component\Console\Style\SymfonyStyle;
     use Symfony\Component\Process\Process;
 
-    class MyCommand extends Command
+    #[AsCommand(name: 'app:my-command')]
+    class MyCommand
     {
-        protected function execute(InputInterface $input, OutputInterface $output): int
+        public function __invoke(SymfonyStyle $io): int
         {
             // the memory_limit (and any other config option) of this command is
             // the one defined in php.ini instead of the new values (optionally)
@@ -442,6 +447,8 @@ the :class:`Symfony\\Component\\Process\\PhpSubprocess` class to run the command
             // the memory_limit (and any other config option) of this command takes
             // into account the values (optionally) passed via the '-d' command option
             $childProcess = new PhpSubprocess(['bin/console', 'cache:pool:prune']);
+
+            return 0;
         }
     }
 
diff --git a/components/property_info.rst b/components/property_info.rst
index 2e1ee42dd3f..865a36c5941 100644
--- a/components/property_info.rst
+++ b/components/property_info.rst
@@ -131,7 +131,7 @@ class exposes public methods to extract several types of information:
         $propertyInfo->getProperties($awesomeObject);
 
         // Good!
-        $propertyInfo->getProperties(get_class($awesomeObject));
+        $propertyInfo->getProperties($awesomeObject::class);
         $propertyInfo->getProperties('Example\Namespace\YourAwesomeClass');
         $propertyInfo->getProperties(YourAwesomeClass::class);
 
@@ -469,7 +469,18 @@ information from annotations of properties and methods, such as ``@var``,
     use App\Domain\Foo;
 
     $phpStanExtractor = new PhpStanExtractor();
+
+    // Type information.
     $phpStanExtractor->getTypesFromConstructor(Foo::class, 'bar');
+    // Description information.
+    $phpStanExtractor->getShortDescription($class, 'bar');
+    $phpStanExtractor->getLongDescription($class, 'bar');
+
+.. versionadded:: 7.3
+
+    The :method:`Symfony\\Component\\PropertyInfo\\Extractor\\PhpStanExtractor::getShortDescription`
+    and :method:`Symfony\\Component\\PropertyInfo\\Extractor\\PhpStanExtractor::getLongDescription`
+    methods were introduced in Symfony 7.3.
 
 SerializerExtractor
 ~~~~~~~~~~~~~~~~~~~
@@ -527,6 +538,8 @@ with the ``property_info`` service in the Symfony Framework::
     // Type information.
     $doctrineExtractor->getTypes($class, $property);
 
+.. _components-property-information-constructor-extractor:
+
 ConstructorExtractor
 ~~~~~~~~~~~~~~~~~~~~
 
@@ -559,6 +572,7 @@ Creating Your Own Extractors
 
 You can create your own property information extractors by creating a
 class that implements one or more of the following interfaces:
+:class:`Symfony\\Component\\PropertyInfo\\Extractor\\ConstructorArgumentTypeExtractorInterface`,
 :class:`Symfony\\Component\\PropertyInfo\\PropertyAccessExtractorInterface`,
 :class:`Symfony\\Component\\PropertyInfo\\PropertyDescriptionExtractorInterface`,
 :class:`Symfony\\Component\\PropertyInfo\\PropertyListExtractorInterface`,
@@ -576,6 +590,11 @@ service by defining it as a service with one or more of the following
 * ``property_info.access_extractor`` if it provides access information.
 * ``property_info.initializable_extractor`` if it provides initializable information
   (it checks if a property can be initialized through the constructor).
+* ``property_info.constructor_extractor`` if it provides type information from the constructor argument.
+
+  .. versionadded:: 7.3
+
+      The ``property_info.constructor_extractor`` tag was introduced in Symfony 7.3.
 
 .. _`PSR-1`: https://www.php-fig.org/psr/psr-1/
 .. _`phpDocumentor Reflection`: https://github.com/phpDocumentor/ReflectionDocBlock
diff --git a/components/runtime.rst b/components/runtime.rst
index 4eb75de2a75..770ea102563 100644
--- a/components/runtime.rst
+++ b/components/runtime.rst
@@ -36,6 +36,9 @@ So how does this front-controller work? At first, the special
 the component. This file runs the following logic:
 
 #. It instantiates a :class:`Symfony\\Component\\Runtime\\RuntimeInterface`;
+#. The front-controller script (e.g. ``public/index.php``) is included by the
+   runtime, making it run again. Ensure this doesn't produce any side effects
+   in your code;
 #. The callable (returned by ``public/index.php``) is passed to the Runtime, whose job
    is to resolve the arguments (in this example: ``array $context``);
 #. Then, this callable is called to get the application (``App\Kernel``);
diff --git a/components/type_info.rst b/components/type_info.rst
index 2fd64b9bc5d..817c7f1d61a 100644
--- a/components/type_info.rst
+++ b/components/type_info.rst
@@ -11,11 +11,6 @@ This component provides:
 * A way to get types from PHP elements such as properties, method arguments,
   return types, and raw strings.
 
-.. warning::
-
-    This component is :doc:`experimental </contributing/code/experimental>` and
-    could be changed at any time without prior notice.
-
 Installation
 ------------
 
@@ -42,15 +37,37 @@ to the :class:`Symfony\\Component\\TypeInfo\\Type` static methods as following::
     Type::list(Type::bool());
     Type::intersection(Type::object(\Stringable::class), Type::object(\Iterator::class));
 
-    // Many others are available and can be
-    // found in Symfony\Component\TypeInfo\TypeFactoryTrait
+Many others methods are available and can be found
+in :class:`Symfony\\Component\\TypeInfo\\TypeFactoryTrait`.
+
+You can also use a generic method that detects the type automatically::
+
+    Type::fromValue(1.1);   // same as Type::float()
+    Type::fromValue('...'); // same as Type::string()
+    Type::fromValue(false); // same as Type::false()
+
+.. versionadded:: 7.3
 
-The second way of using the component is to use ``TypeInfo`` to resolve a type
-based on reflection or a simple string::
+    The ``fromValue()`` method was introduced in Symfony 7.3.
+
+Resolvers
+~~~~~~~~~
+
+The second way to use the component is by using ``TypeInfo`` to resolve a type
+based on reflection or a simple string. This approach is designed for libraries
+that need a simple way to describe a class or anything with a type::
 
     use Symfony\Component\TypeInfo\Type;
     use Symfony\Component\TypeInfo\TypeResolver\TypeResolver;
 
+    class Dummy
+    {
+        public function __construct(
+            public int $id,
+        ) {
+        }
+    }
+
     // Instantiate a new resolver
     $typeResolver = TypeResolver::create();
 
@@ -63,12 +80,6 @@ based on reflection or a simple string::
 
     // Type instances have several helper methods
 
-    // returns the main type (e.g. in this example it returns an "array" Type instance);
-    // for nullable types (e.g. string|null) it returns the non-null type (e.g. string)
-    // and for compound types (e.g. int|string) it throws an exception because both types
-    // can be considered the main one, so there's no way to pick one
-    $baseType = $type->getBaseType();
-
     // for collections, it returns the type of the item used as the key;
     // in this example, the collection is a list, so it returns an "int" Type instance
     $keyType = $type->getCollectionKeyType();
@@ -81,6 +92,111 @@ Each of these calls will return you a ``Type`` instance that corresponds to the
 static method used. You can also resolve types from a string (as shown in the
 ``bool`` parameter of the previous example)
 
-.. note::
+PHPDoc Parsing
+~~~~~~~~~~~~~~
+
+In many cases, you may not have cleanly typed properties or may need more precise
+type definitions provided by advanced PHPDoc. To achieve this, you can use a string
+resolver based on the PHPDoc annotations.
+
+First, run the command ``composer require phpstan/phpdoc-parser`` to install the
+PHP package required for string resolving. Then, follow these steps::
+
+    use Symfony\Component\TypeInfo\TypeResolver\TypeResolver;
+
+    class Dummy
+    {
+        public function __construct(
+            public int $id,
+            /** @var string[] $tags */
+            public array $tags,
+        ) {
+        }
+    }
+
+    $typeResolver = TypeResolver::create();
+    $typeResolver->resolve(new \ReflectionProperty(Dummy::class, 'id')); // returns an "int" Type
+    $typeResolver->resolve(new \ReflectionProperty(Dummy::class, 'tags')); // returns a collection with "int" as key and "string" as values Type
+
+Advanced Usages
+~~~~~~~~~~~~~~~
+
+The TypeInfo component provides various methods to manipulate and check types,
+depending on your needs.
+
+**Identify** a type::
+
+    // define a simple integer type
+    $type = Type::int();
+    // check if the type matches a specific identifier
+    $type->isIdentifiedBy(TypeIdentifier::INT);    // true
+    $type->isIdentifiedBy(TypeIdentifier::STRING); // false
+
+    // define a union type (equivalent to PHP's int|string)
+    $type = Type::union(Type::string(), Type::int());
+    // now the second check is true because the union type contains the string type
+    $type->isIdentifiedBy(TypeIdentifier::INT);    // true
+    $type->isIdentifiedBy(TypeIdentifier::STRING); // true
+
+    class DummyParent {}
+    class Dummy extends DummyParent implements DummyInterface {}
+
+    // define an object type
+    $type = Type::object(Dummy::class);
+
+    // check if the type is an object or matches a specific class
+    $type->isIdentifiedBy(TypeIdentifier::OBJECT); // true
+    $type->isIdentifiedBy(Dummy::class);           // true
+    // check if it inherits/implements something
+    $type->isIdentifiedBy(DummyParent::class);     // true
+    $type->isIdentifiedBy(DummyInterface::class);  // true
+
+Checking if a type **accepts a value**::
+
+    $type = Type::int();
+    // check if the type accepts a given value
+    $type->accepts(123); // true
+    $type->accepts('z'); // false
+
+    $type = Type::union(Type::string(), Type::int());
+    // now the second check is true because the union type accepts either an int or a string value
+    $type->accepts(123); // true
+    $type->accepts('z'); // true
+
+.. versionadded:: 7.3
+
+    The :method:`Symfony\\Component\\TypeInfo\\Type::accepts`
+    method was introduced in Symfony 7.3.
+
+Using callables for **complex checks**::
+
+    class Foo
+    {
+        private int $integer;
+        private string $string;
+        private ?float $float;
+    }
+
+    $reflClass = new \ReflectionClass(Foo::class);
+
+    $resolver = TypeResolver::create();
+    $integerType = $resolver->resolve($reflClass->getProperty('integer'));
+    $stringType = $resolver->resolve($reflClass->getProperty('string'));
+    $floatType = $resolver->resolve($reflClass->getProperty('float'));
+
+    // define a callable to validate non-nullable number types
+    $isNonNullableNumber = function (Type $type): bool {
+        if ($type->isNullable()) {
+            return false;
+        }
+
+        if ($type->isIdentifiedBy(TypeIdentifier::INT) || $type->isIdentifiedBy(TypeIdentifier::FLOAT)) {
+            return true;
+        }
+
+        return false;
+    };
 
-    To support raw string resolving, you need to install ``phpstan/phpdoc-parser`` package.
+    $integerType->isSatisfiedBy($isNonNullableNumber); // true
+    $stringType->isSatisfiedBy($isNonNullableNumber);  // false
+    $floatType->isSatisfiedBy($isNonNullableNumber);   // false
diff --git a/components/uid.rst b/components/uid.rst
index 5077fb4f1c8..b4083765436 100644
--- a/components/uid.rst
+++ b/components/uid.rst
@@ -32,13 +32,13 @@ to create each type of UUID:
 **UUID v1** (time-based)
 
 Generates the UUID using a timestamp and the MAC address of your device
-(`read UUIDv1 spec <https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis#name-uuid-version-1>`__).
+(`read the UUIDv1 spec <https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis#name-uuid-version-1>`__).
 Both are obtained automatically, so you don't have to pass any constructor argument::
 
     use Symfony\Component\Uid\Uuid;
 
-    // $uuid is an instance of Symfony\Component\Uid\UuidV1
     $uuid = Uuid::v1();
+    // $uuid is an instance of Symfony\Component\Uid\UuidV1
 
 .. tip::
 
@@ -48,7 +48,7 @@ Both are obtained automatically, so you don't have to pass any constructor argum
 **UUID v2** (DCE security)
 
 Similar to UUIDv1 but with a very high likelihood of ID collision
-(`read UUIDv2 spec <https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis#name-uuid-version-2>`__).
+(`read the UUIDv2 spec <https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis#name-uuid-version-2>`__).
 It's part of the authentication mechanism of DCE (Distributed Computing Environment)
 and the UUID includes the POSIX UIDs (user/group ID) of the user who generated it.
 This UUID variant is **not implemented** by the Uid component.
@@ -56,7 +56,7 @@ This UUID variant is **not implemented** by the Uid component.
 **UUID v3** (name-based, MD5)
 
 Generates UUIDs from names that belong, and are unique within, some given namespace
-(`read UUIDv3 spec <https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis#name-uuid-version-3>`__).
+(`read the UUIDv3 spec <https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis#name-uuid-version-3>`__).
 This variant is useful to generate deterministic UUIDs from arbitrary strings.
 It works by populating the UUID contents with the``md5`` hash of concatenating
 the namespace and the name::
@@ -69,8 +69,8 @@ the namespace and the name::
     // $namespace = Uuid::v4();
 
     // $name can be any arbitrary string
-    // $uuid is an instance of Symfony\Component\Uid\UuidV3
     $uuid = Uuid::v3($namespace, $name);
+    // $uuid is an instance of Symfony\Component\Uid\UuidV3
 
 These are the default namespaces defined by the standard:
 
@@ -81,20 +81,20 @@ These are the default namespaces defined by the standard:
 
 **UUID v4** (random)
 
-Generates a random UUID (`read UUIDv4 spec <https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis#name-uuid-version-4>`__).
+Generates a random UUID (`read the UUIDv4 spec <https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis#name-uuid-version-4>`__).
 Because of its randomness, it ensures uniqueness across distributed systems
 without the need for a central coordinating entity. It's privacy-friendly
 because it doesn't contain any information about where and when it was generated::
 
     use Symfony\Component\Uid\Uuid;
 
-    // $uuid is an instance of Symfony\Component\Uid\UuidV4
     $uuid = Uuid::v4();
+    // $uuid is an instance of Symfony\Component\Uid\UuidV4
 
 **UUID v5** (name-based, SHA-1)
 
 It's the same as UUIDv3 (explained above) but it uses ``sha1`` instead of
-``md5`` to hash the given namespace and name (`read UUIDv5 spec <https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis#name-uuid-version-5>`__).
+``md5`` to hash the given namespace and name (`read the UUIDv5 spec <https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis#name-uuid-version-5>`__).
 This makes it more secure and less prone to hash collisions.
 
 .. _uid-uuid-v6:
@@ -103,12 +103,12 @@ This makes it more secure and less prone to hash collisions.
 
 It rearranges the time-based fields of the UUIDv1 to make it lexicographically
 sortable (like :ref:`ULIDs <ulid>`). It's more efficient for database indexing
-(`read UUIDv6 spec <https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis#name-uuid-version-6>`__)::
+(`read the UUIDv6 spec <https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis#name-uuid-version-6>`__)::
 
     use Symfony\Component\Uid\Uuid;
 
-    // $uuid is an instance of Symfony\Component\Uid\UuidV6
     $uuid = Uuid::v6();
+    // $uuid is an instance of Symfony\Component\Uid\UuidV6
 
 .. tip::
 
@@ -121,26 +121,28 @@ sortable (like :ref:`ULIDs <ulid>`). It's more efficient for database indexing
 
 Generates time-ordered UUIDs based on a high-resolution Unix Epoch timestamp
 source (the number of milliseconds since midnight 1 Jan 1970 UTC, leap seconds excluded)
-(`read UUIDv7 spec <https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis#name-uuid-version-7>`__).
+(`read the UUIDv7 spec <https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis#name-uuid-version-7>`__).
 It's recommended to use this version over UUIDv1 and UUIDv6 because it provides
 better entropy (and a more strict chronological order of UUID generation)::
 
     use Symfony\Component\Uid\Uuid;
 
-    // $uuid is an instance of Symfony\Component\Uid\UuidV7
     $uuid = Uuid::v7();
+    // $uuid is an instance of Symfony\Component\Uid\UuidV7
 
 **UUID v8** (custom)
 
-Provides an RFC-compatible format for experimental or vendor-specific use cases
-(`read UUIDv8 spec <https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis#name-uuid-version-8>`__).
-The only requirement is to set the variant and version bits of the UUID. The rest
-of the UUID value is specific to each implementation and no format should be assumed::
+Provides an RFC-compatible format intended for experimental or vendor-specific use cases
+(`read the UUIDv8 spec <https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis#name-uuid-version-8>`__).
+You must generate the UUID value yourself. The only requirement is to set the
+variant and version bits of the UUID correctly. The rest of the UUID content is
+implementation-specific, and no particular format should be assumed::
 
     use Symfony\Component\Uid\Uuid;
 
+    // pass your custom UUID value as the argument
+    $uuid = Uuid::v8('d9e7a184-5d5b-11ea-a62a-3499710062d0');
     // $uuid is an instance of Symfony\Component\Uid\UuidV8
-    $uuid = Uuid::v8();
 
 If your UUID value is already generated in another format, use any of the
 following methods to create a ``Uuid`` object from it::
@@ -314,6 +316,31 @@ UUID objects created with the ``Uuid`` class can use the following methods
     //   * int < 0 if $uuid1 is less than $uuid4
     $uuid1->compare($uuid4); // e.g. int(4)
 
+If you're working with different UUIDs format and want to validate them,
+you can use the ``$format`` parameter of the :method:`Symfony\\Component\\Uid\\Uuid::isValid`
+method to specify the UUID format you're expecting::
+
+    use Symfony\Component\Uid\Uuid;
+
+    $isValid = Uuid::isValid('90067ce4-f083-47d2-a0f4-c47359de0f97', Uuid::FORMAT_RFC_4122); // accept only RFC 4122 UUIDs
+    $isValid = Uuid::isValid('3aJ7CNpDMfXPZrCsn4Cgey', Uuid::FORMAT_BASE_32 | Uuid::FORMAT_BASE_58); // accept multiple formats
+
+The following constants are available:
+
+* ``Uuid::FORMAT_BINARY``
+* ``Uuid::FORMAT_BASE_32``
+* ``Uuid::FORMAT_BASE_58``
+* ``Uuid::FORMAT_RFC_4122``
+* ``Uuid::FORMAT_RFC_9562`` (equivalent to ``Uuid::FORMAT_RFC_4122``)
+
+You can also use the ``Uuid::FORMAT_ALL`` constant to accept any UUID format.
+By default, only the RFC 4122 format is accepted.
+
+.. versionadded:: 7.2
+
+    The ``$format`` parameter of the :method:`Symfony\\Component\\Uid\\Uuid::isValid`
+    method and the related constants were introduced in Symfony 7.2.
+
 Storing UUIDs in Databases
 ~~~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -342,6 +369,7 @@ entity primary keys::
     namespace App\Entity;
 
     use Doctrine\ORM\Mapping as ORM;
+    use Symfony\Bridge\Doctrine\IdGenerator\UuidGenerator;
     use Symfony\Bridge\Doctrine\Types\UuidType;
     use Symfony\Component\Uid\Uuid;
 
@@ -350,7 +378,7 @@ entity primary keys::
         #[ORM\Id]
         #[ORM\Column(type: UuidType::NAME, unique: true)]
         #[ORM\GeneratedValue(strategy: 'CUSTOM')]
-        #[ORM\CustomIdGenerator(class: 'doctrine.uuid_generator')]
+        #[ORM\CustomIdGenerator(class: UuidGenerator::class)]
         private ?Uuid $id;
 
         public function getId(): ?Uuid
@@ -530,6 +558,7 @@ entity primary keys::
     namespace App\Entity;
 
     use Doctrine\ORM\Mapping as ORM;
+    use Symfony\Bridge\Doctrine\IdGenerator\UlidGenerator;
     use Symfony\Bridge\Doctrine\Types\UlidType;
     use Symfony\Component\Uid\Ulid;
 
@@ -538,7 +567,7 @@ entity primary keys::
         #[ORM\Id]
         #[ORM\Column(type: UlidType::NAME, unique: true)]
         #[ORM\GeneratedValue(strategy: 'CUSTOM')]
-        #[ORM\CustomIdGenerator(class: 'doctrine.ulid_generator')]
+        #[ORM\CustomIdGenerator(class: UlidGenerator::class)]
         private ?Ulid $id;
 
         public function getId(): ?Ulid
diff --git a/components/validator.rst b/components/validator.rst
index 085c77a7946..12c61507257 100644
--- a/components/validator.rst
+++ b/components/validator.rst
@@ -36,7 +36,7 @@ characters long::
 
     $validator = Validation::createValidator();
     $violations = $validator->validate('Bernhard', [
-        new Length(['min' => 10]),
+        new Length(min: 10),
         new NotBlank(),
     ]);
 
diff --git a/components/validator/metadata.rst b/components/validator/metadata.rst
index e7df42413bc..782e1ee216f 100755
--- a/components/validator/metadata.rst
+++ b/components/validator/metadata.rst
@@ -24,7 +24,7 @@ the ``Author`` class has at least 3 characters::
             $metadata->addPropertyConstraint('firstName', new Assert\NotBlank());
             $metadata->addPropertyConstraint(
                 'firstName',
-                new Assert\Length(["min" => 3])
+                new Assert\Length(min: 3)
             );
         }
     }
@@ -55,9 +55,9 @@ Then, add the Validator component configuration to the class::
     {
         public static function loadValidatorMetadata(ClassMetadata $metadata): void
         {
-            $metadata->addGetterConstraint('passwordSafe', new Assert\IsTrue([
-                'message' => 'The password cannot match your first name',
-            ]));
+            $metadata->addGetterConstraint('passwordSafe', new Assert\IsTrue(
+                message: 'The password cannot match your first name',
+            ));
         }
     }
 
diff --git a/components/validator/resources.rst b/components/validator/resources.rst
index 5b1448dfba1..7d6cd0e8e5d 100644
--- a/components/validator/resources.rst
+++ b/components/validator/resources.rst
@@ -42,10 +42,10 @@ In this example, the validation metadata is retrieved executing the
         public static function loadValidatorMetadata(ClassMetadata $metadata): void
         {
             $metadata->addPropertyConstraint('name', new Assert\NotBlank());
-            $metadata->addPropertyConstraint('name', new Assert\Length([
-                'min' => 5,
-                'max' => 20,
-            ]));
+            $metadata->addPropertyConstraint('name', new Assert\Length(
+                min: 5,
+                max: 20,
+            ));
         }
     }
 
@@ -102,7 +102,7 @@ the metadata from the attributes of the class. For example::
 To enable the attribute loader, call the
 :method:`Symfony\\Component\\Validator\\ValidatorBuilder::enableAttributeMapping` method.
 
-To disable the annotation loader after it was enabled, call
+To disable the attribute loader after it was enabled, call
 :method:`Symfony\\Component\\Validator\\ValidatorBuilder::disableAttributeMapping`.
 
 Using Multiple Loaders
diff --git a/components/var_dumper.rst b/components/var_dumper.rst
index 3f59ff1b796..c6966a692af 100644
--- a/components/var_dumper.rst
+++ b/components/var_dumper.rst
@@ -809,7 +809,7 @@ They are called in registration order.
 Casters are responsible for returning the properties of the object or resource
 being cloned in an array. They are callables that accept five arguments:
 
-* the object or resource being casted;
+* the object or resource being cast;
 * an array modeled for objects after PHP's native ``(array)`` cast operator;
 * a :class:`Symfony\\Component\\VarDumper\\Cloner\\Stub` object
   representing the main properties of the object (class, type, etc.);
diff --git a/components/var_exporter.rst b/components/var_exporter.rst
index 6aa4279788e..c7ec9cd90d0 100644
--- a/components/var_exporter.rst
+++ b/components/var_exporter.rst
@@ -180,18 +180,50 @@ populated by using the special ``"\0"`` property name to define their internal v
 Creating Lazy Objects
 ---------------------
 
-Lazy-objects are objects instantiated empty and populated on-demand. This is
-particularly useful when you have for example properties in your classes that
-requires some heavy computation to determine their value. In this case, you
-may want to trigger the property's value processing only when you actually need
-its value. Thanks to this, the heavy computation won't be done if you never use
-this property. The VarExporter component is bundled with two traits helping
-you implement such mechanism easily in your classes.
+Lazy objects are objects instantiated empty and populated on demand. This is
+particularly useful when, for example, a class has properties that require
+heavy computation to determine their values. In such cases, you may want to
+trigger the computation only when the property is actually accessed. This way,
+the expensive processing is avoided entirely if the property is never used.
+
+Since version 8.4, PHP provides support for lazy objects via the reflection API.
+This native API works with concrete classes, but not with abstract or internal ones.
+This component provides helpers to generate lazy objects using the decorator
+pattern, which also works with abstract classes, internal classes, and interfaces::
+
+    $proxyCode = ProxyHelper::generateLazyProxy(new \ReflectionClass(SomeInterface::class));
+    // $proxyCode should be dumped into a file in production environments
+    eval('class ProxyDecorator'.$proxyCode);
+
+    $proxy = ProxyDecorator::createLazyProxy(initializer: function (): SomeInterface {
+        // use whatever heavy logic you need here
+        // to compute the $dependencies of the proxied class
+        $instance = new SomeHeavyClass(...$dependencies);
+        // call setters, etc. if needed
+
+        return $instance;
+    });
+
+Use this mechanism only when native lazy objects cannot be leveraged
+(otherwise you'll get a deprecation notice).
+
+Legacy Creation of Lazy Objects
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When using a PHP version earlier than 8.4, native lazy objects are not available.
+In these cases, the VarExporter component provides two traits that help you
+implement lazy-loading mechanisms in your classes.
 
 .. _var-exporter_ghost-objects:
 
 LazyGhostTrait
-~~~~~~~~~~~~~~
+..............
+
+.. deprecated:: 7.3
+
+    ``LazyGhostTrait`` is deprecated since Symfony 7.3. Use PHP 8.4's native lazy
+    objects instead. Note that using the trait with PHP versions earlier than 8.4
+    does not trigger a deprecation, to ease the transition.
 
 Ghost objects are empty objects, which see their properties populated the first
 time any method is called. Thanks to :class:`Symfony\\Component\\VarExporter\\LazyGhostTrait`,
@@ -264,17 +296,6 @@ While you never query ``$processor->hash`` value, heavy methods will never be
 triggered. But still, the ``$processor`` object exists and can be used in your
 code, passed to methods, functions, etc.
 
-Additionally and by adding two arguments to the initializer function, it is
-possible to initialize properties one-by-one::
-
-    $processor = LazyHashProcessor::createLazyGhost(initializer: function (HashProcessor $instance, string $propertyName, ?string $propertyScope): mixed {
-        if (HashProcessor::class === $propertyScope && 'hash' === $propertyName) {
-            // Return $hash value
-        }
-
-        // Then you can add more logic for the other properties
-    });
-
 Ghost objects unfortunately can't work with abstract classes or internal PHP
 classes. Nevertheless, the VarExporter component covers this need with the help
 of :ref:`Virtual Proxies <var-exporter_virtual-proxies>`.
@@ -282,7 +303,13 @@ of :ref:`Virtual Proxies <var-exporter_virtual-proxies>`.
 .. _var-exporter_virtual-proxies:
 
 LazyProxyTrait
-~~~~~~~~~~~~~~
+..............
+
+.. deprecated:: 7.3
+
+    ``LazyProxyTrait`` is deprecated since Symfony 7.3. Use PHP 8.4's native lazy
+    objects instead. Note that using the trait with PHP versions earlier than 8.4
+    does not trigger a deprecation, to ease the transition.
 
 The purpose of virtual proxies in the same one as
 :ref:`ghost objects <var-exporter_ghost-objects>`, but their internal behavior is
diff --git a/components/yaml.rst b/components/yaml.rst
index ea1c1f4af3a..efaf84f04e6 100644
--- a/components/yaml.rst
+++ b/components/yaml.rst
@@ -214,6 +214,8 @@ During the parsing of the YAML contents, all the ``_`` characters are removed
 from the numeric literal contents, so there is not a limit in the number of
 underscores you can include or the way you group contents.
 
+.. _yaml-flags:
+
 Advanced Usage: Flags
 ---------------------
 
@@ -296,7 +298,7 @@ You can make it convert to a ``DateTime`` instance by using the ``PARSE_DATETIME
 flag::
 
     $date = Yaml::parse('2016-05-27', Yaml::PARSE_DATETIME);
-    var_dump(get_class($date)); // DateTime
+    var_dump($date::class); // DateTime
 
 Dumping Multi-line Literal Blocks
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -426,6 +428,16 @@ you can dump them as ``~`` with the ``DUMP_NULL_AS_TILDE`` flag::
     $dumped = Yaml::dump(['foo' => null], 2, 4, Yaml::DUMP_NULL_AS_TILDE);
     // foo: ~
 
+Another valid representation of the ``null`` value is an empty string. You can
+use the ``DUMP_NULL_AS_EMPTY`` flag to dump null values as empty strings::
+
+    $dumped = Yaml::dump(['foo' => null], 2, 4, Yaml::DUMP_NULL_AS_EMPTY);
+    // foo:
+
+.. versionadded:: 7.3
+
+    The ``DUMP_NULL_AS_EMPTY`` flag was introduced in Symfony 7.3.
+
 Dumping Numeric Keys as Strings
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -438,6 +450,58 @@ By default, digit-only array keys are dumped as integers. You can use the
     $dumped = Yaml::dump([200 => 'foo'], 2, 4, Yaml::DUMP_NUMERIC_KEY_AS_STRING);
     // '200': foo
 
+Dumping Double Quotes on Values
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+By default, only unsafe string values are enclosed in double quotes (for example,
+if they are reserved words or contain newlines and spaces). Use the
+``DUMP_FORCE_DOUBLE_QUOTES_ON_VALUES`` flag to add double quotes to all string values::
+
+    $dumped = Yaml::dump([
+        'foo' => 'bar', 'some foo' => 'some bar', 'x' => 3.14, 'y' => true, 'z' => null,
+    ]);
+    // foo: bar, 'some foo': 'some bar', x: 3.14, 'y': true, z: null
+
+    $dumped = Yaml::dump([
+        'foo' => 'bar', 'some foo' => 'some bar', 'x' => 3.14, 'y' => true, 'z' => null,
+    ], 2, 4, Yaml::DUMP_FORCE_DOUBLE_QUOTES_ON_VALUES);
+    // "foo": "bar", "some foo": "some bar", "x": 3.14, "y": true, "z": null
+
+.. versionadded:: 7.3
+
+    The ``Yaml::DUMP_FORCE_DOUBLE_QUOTES_ON_VALUES`` flag was introduced in Symfony 7.3.
+
+Dumping Collection of Maps
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When the YAML component dumps collections of maps, it uses a hyphen on a separate
+line as a delimiter:
+
+.. code-block:: yaml
+
+    planets:
+      -
+        name: Mercury
+        distance: 57910000
+      -
+        name: Jupiter
+        distance: 778500000
+
+To produce a more compact output where the delimiter is included within the map,
+use the ``Yaml::DUMP_COMPACT_NESTED_MAPPING`` flag:
+
+.. code-block:: yaml
+
+    planets:
+      - name: Mercury
+        distance: 57910000
+      - name: Jupiter
+        distance: 778500000
+
+.. versionadded:: 7.3
+
+    The ``Yaml::DUMP_COMPACT_NESTED_MAPPING`` flag was introduced in Symfony 7.3.
+
 Syntax Validation
 ~~~~~~~~~~~~~~~~~
 
diff --git a/configuration.rst b/configuration.rst
index 30d9ade5869..4b1e75dcabe 100644
--- a/configuration.rst
+++ b/configuration.rst
@@ -37,7 +37,7 @@ example, this is the default file created by the "API Platform" bundle:
         mapping:
             paths: ['%kernel.project_dir%/src/Entity']
 
-Splitting the configuration into lots of small files might appear intimidating for some
+Splitting the configuration into lots of small files might seem intimidating to some
 Symfony newcomers. However, you'll get used to them quickly and you rarely need
 to change these files after package installation.
 
@@ -382,6 +382,20 @@ a new ``locale`` parameter is added to the ``config/services.yaml`` file).
     They are useful when working with :doc:`Compiler Passes </service_container/compiler_passes>`
     to declare some temporary parameters that won't be available later in the application.
 
+Configuration parameters are usually validation-free, but you can ensure that
+essential parameters for your application's functionality are not empty::
+
+    /** @var ContainerBuilder $container */
+    $container->parameterCannotBeEmpty('app.private_key', 'Did you forget to set a value for the "app.private_key" parameter?');
+
+If a non-empty parameter is ``null``, an empty string ``''``, or an empty array ``[]``,
+Symfony will throw an exception. This validation is **not** made at compile time
+but when attempting to retrieve the value of the parameter.
+
+.. versionadded:: 7.2
+
+    Validating non-empty parameters was introduced in Symfony 7.2.
+
 .. seealso::
 
     Later in this article you can read how to
diff --git a/configuration/env_var_processors.rst b/configuration/env_var_processors.rst
index 2e82104db66..936d93c1061 100644
--- a/configuration/env_var_processors.rst
+++ b/configuration/env_var_processors.rst
@@ -951,3 +951,9 @@ To enable the new processor in the app, register it as a service and
 tag. If you're using the
 :ref:`default services.yaml configuration <service-container-services-load-example>`,
 this is already done for you, thanks to :ref:`autoconfiguration <services-autoconfigure>`.
+
+Resolving Environment Variable At Compile Time
+----------------------------------------------
+
+Environment variables are resolved at runtime, but you can also resolve them
+:ref:`at compile time <resolving-env-vars-at-compile-time>`.
diff --git a/configuration/micro_kernel_trait.rst b/configuration/micro_kernel_trait.rst
index b67335514a1..542532ee1af 100644
--- a/configuration/micro_kernel_trait.rst
+++ b/configuration/micro_kernel_trait.rst
@@ -16,9 +16,7 @@ via Composer:
 
 .. code-block:: terminal
 
-    $ composer require symfony/config symfony/http-kernel \
-      symfony/http-foundation symfony/routing \
-      symfony/dependency-injection symfony/framework-bundle
+    $ composer require symfony/framework-bundle symfony/runtime
 
 Next, create an ``index.php`` file that defines the kernel class and runs it:
 
@@ -34,19 +32,12 @@ Next, create an ``index.php`` file that defines the kernel class and runs it:
         use Symfony\Component\HttpKernel\Kernel as BaseKernel;
         use Symfony\Component\Routing\Attribute\Route;
 
-        require __DIR__.'/vendor/autoload.php';
+        require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
 
         class Kernel extends BaseKernel
         {
             use MicroKernelTrait;
 
-            public function registerBundles(): array
-            {
-                return [
-                    new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
-                ];
-            }
-
             protected function configureContainer(ContainerConfigurator $container): void
             {
                 // PHP equivalent of config/packages/framework.yaml
@@ -64,11 +55,9 @@ Next, create an ``index.php`` file that defines the kernel class and runs it:
             }
         }
 
-        $kernel = new Kernel('dev', true);
-        $request = Request::createFromGlobals();
-        $response = $kernel->handle($request);
-        $response->send();
-        $kernel->terminate($request, $response);
+        return static function (array $context) {
+            return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
+        };
 
     .. code-block:: php
 
@@ -80,19 +69,12 @@ Next, create an ``index.php`` file that defines the kernel class and runs it:
         use Symfony\Component\HttpKernel\Kernel as BaseKernel;
         use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
 
-        require __DIR__.'/vendor/autoload.php';
+        require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
 
         class Kernel extends BaseKernel
         {
             use MicroKernelTrait;
 
-            public function registerBundles(): array
-            {
-                return [
-                    new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
-                ];
-            }
-
             protected function configureContainer(ContainerConfigurator $container): void
             {
                 // PHP equivalent of config/packages/framework.yaml
@@ -114,20 +96,11 @@ Next, create an ``index.php`` file that defines the kernel class and runs it:
             }
         }
 
-        $kernel = new Kernel('dev', true);
-        $request = Request::createFromGlobals();
-        $response = $kernel->handle($request);
-        $response->send();
-        $kernel->terminate($request, $response);
-
-.. note::
-
-    In addition to the ``index.php`` file, you'll need to create a directory called
-    ``config/`` in your project (even if it's empty because you define the configuration
-    options inside the ``configureContainer()`` method).
+        return static function (array $context) {
+            return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
+        };
 
-That's it! To test it, start the :doc:`Symfony Local Web Server
-</setup/symfony_server>`:
+That's it! To test it, start the :ref:`Symfony local web server <symfony-cli-server>`:
 
 .. code-block:: terminal
 
@@ -135,6 +108,23 @@ That's it! To test it, start the :doc:`Symfony Local Web Server
 
 Then see the JSON response in your browser: http://localhost:8000/random/10
 
+.. tip::
+
+    If your kernel only defines a single controller, you can use an invokable method::
+
+        class Kernel extends BaseKernel
+        {
+            use MicroKernelTrait;
+
+            // ...
+
+            #[Route('/random/{limit}', name: 'random_number')]
+            public function __invoke(int $limit): JsonResponse
+            {
+                // ...
+            }
+        }
+
 The Methods of a "Micro" Kernel
 -------------------------------
 
@@ -142,7 +132,26 @@ When you use the ``MicroKernelTrait``, your kernel needs to have exactly three m
 that define your bundles, your services and your routes:
 
 **registerBundles()**
-    This is the same ``registerBundles()`` that you see in a normal kernel.
+    This is the same ``registerBundles()`` that you see in a normal kernel. By
+    default, the micro kernel only registers the ``FrameworkBundle``. If you need
+    to register more bundles, override this method::
+
+        use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
+        use Symfony\Bundle\TwigBundle\TwigBundle;
+        // ...
+
+        class Kernel extends BaseKernel
+        {
+            use MicroKernelTrait;
+
+            // ...
+
+            public function registerBundles(): array
+            {
+                yield new FrameworkBundle();
+                yield new TwigBundle();
+            }
+        }
 
 **configureContainer(ContainerConfigurator $container)**
     This method builds and configures the container. In practice, you will use
@@ -151,9 +160,13 @@ that define your bundles, your services and your routes:
     services directly in PHP or load external configuration files (shown below).
 
 **configureRoutes(RoutingConfigurator $routes)**
-    Your job in this method is to add routes to the application. The
-    ``RoutingConfigurator`` has methods that make adding routes in PHP more
-    fun. You can also load external routing files (shown below).
+    In this method, you can use the ``RoutingConfigurator`` object to define routes
+    in your application and associate them to the controllers defined in this very
+    same file.
+
+    However, it's more convenient to define the controller routes using PHP attributes,
+    as shown above. That's why this method is commonly used only to load external
+    routing files (e.g. from bundles) as shown below.
 
 Adding Interfaces to "Micro" Kernel
 -----------------------------------
@@ -231,7 +244,10 @@ Now it looks like this::
     namespace App;
 
     use App\DependencyInjection\AppExtension;
+    use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
     use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
+    use Symfony\Bundle\TwigBundle\TwigBundle;
+    use Symfony\Bundle\WebProfilerBundle\WebProfilerBundle;
     use Symfony\Component\DependencyInjection\ContainerBuilder;
     use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
     use Symfony\Component\HttpKernel\Kernel as BaseKernel;
@@ -241,18 +257,14 @@ Now it looks like this::
     {
         use MicroKernelTrait;
 
-        public function registerBundles(): array
+        public function registerBundles(): iterable
         {
-            $bundles = [
-                new \Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
-                new \Symfony\Bundle\TwigBundle\TwigBundle(),
-            ];
+            yield new FrameworkBundle();
+            yield new TwigBundle();
 
             if ('dev' === $this->getEnvironment()) {
-                $bundles[] = new \Symfony\Bundle\WebProfilerBundle\WebProfilerBundle();
+                yield new WebProfilerBundle();
             }
-
-            return $bundles;
         }
 
         protected function build(ContainerBuilder $containerBuilder): void
@@ -284,8 +296,8 @@ Now it looks like this::
         {
             // import the WebProfilerRoutes, only if the bundle is enabled
             if (isset($this->bundles['WebProfilerBundle'])) {
-                $routes->import('@WebProfilerBundle/Resources/config/routing/wdt.xml')->prefix('/_wdt');
-                $routes->import('@WebProfilerBundle/Resources/config/routing/profiler.xml')->prefix('/_profiler');
+                $routes->import('@WebProfilerBundle/Resources/config/routing/wdt.php', 'php')->prefix('/_wdt');
+                $routes->import('@WebProfilerBundle/Resources/config/routing/profiler.php', 'php')->prefix('/_profiler');
             }
 
             // load the routes defined as PHP attributes
@@ -293,19 +305,16 @@ Now it looks like this::
             $routes->import(__DIR__.'/Controller/', 'attribute');
         }
 
-        // optional, to use the standard Symfony cache directory
-        public function getCacheDir(): string
-        {
-            return __DIR__.'/../var/cache/'.$this->getEnvironment();
-        }
-
-        // optional, to use the standard Symfony logs directory
-        public function getLogDir(): string
-        {
-            return __DIR__.'/../var/log';
-        }
+        // optionally, you can define the getCacheDir() and getLogDir() methods
+        // to override the default locations for these directories
     }
 
+
+.. versionadded:: 7.3
+
+    The ``wdt.php`` and ``profiler.php`` files were introduced in Symfony 7.3.
+    Previously, you had to import ``wdt.xml`` and ``profiler.xml``
+
 Before continuing, run this command to add support for the new dependencies:
 
 .. code-block:: terminal
@@ -461,8 +470,7 @@ this:
     ├─ composer.json
     └─ composer.lock
 
-As before you can use the :doc:`Symfony Local Web Server
-</setup/symfony_server>`:
+As before you can use the :ref:`Symfony local web server <symfony-cli-server>`:
 
 .. code-block:: terminal
 
diff --git a/configuration/secrets.rst b/configuration/secrets.rst
index f717456a22c..285b89d521e 100644
--- a/configuration/secrets.rst
+++ b/configuration/secrets.rst
@@ -311,7 +311,7 @@ The secrets system is enabled by default and some of its behavior can be configu
                 xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd
                     http://symfony.com/schema/dic/framework https://symfony.com/schema/dic/framework/framework-1.0.xsd"
             >
-                <framework:config secret="%env(APP_SECRET)%">
+                <framework:config>
                     <framework:secrets
                         vault_directory="%kernel.project_dir%/config/secrets/%kernel.environment%"
                         local_dotenv_file="%kernel.project_dir%/.env.%kernel.environment%.local"
diff --git a/console.rst b/console.rst
index 6a3ba70300f..be9292f92a5 100644
--- a/console.rst
+++ b/console.rst
@@ -18,14 +18,14 @@ the ``list`` command to view all available commands in the application:
     ...
 
     Available commands:
-      about                                      Display information about the current project
-      completion                                 Dump the shell completion script
-      help                                       Display help for a command
-      list                                       List commands
+      about             Display information about the current project
+      completion        Dump the shell completion script
+      help              Display help for a command
+      list              List commands
      assets
-      assets:install                             Install bundle's web assets under a public directory
+      assets:install    Install bundle's web assets under a public directory
      cache
-      cache:clear                                Clear the cache
+      cache:clear       Clear the cache
     ...
 
 .. note::
@@ -100,33 +100,28 @@ completion (by default, by pressing the Tab key).
 
 .. tip::
 
-    If you are using the :doc:`Symfony local web server
-    </setup/symfony_server>`, it is recommended to use the built-in completion
-    script that will ensure the right PHP version and configuration are used when
-    running the Console Completion. Run ``symfony completion --help`` for the
-    installation instructions for your shell. The Symfony CLI will provide
-    completion for the ``console`` and ``composer`` commands.
+    If you are using the :doc:`Symfony CLI </setup/symfony_cli>` tool, follow
+    :ref:`these instructions <symfony-cli-autocompletion>` to enable autocompletion.
+
+.. _console_creating-command:
 
 Creating a Command
 ------------------
 
-Commands are defined in classes extending
-:class:`Symfony\\Component\\Console\\Command\\Command`. For example, you may
-want a command to create a user::
+Commands are defined in classes and auto-registered using the ``#[AsCommand]``
+attribute. For example, you may want a command to create a user::
 
     // src/Command/CreateUserCommand.php
     namespace App\Command;
 
     use Symfony\Component\Console\Attribute\AsCommand;
     use Symfony\Component\Console\Command\Command;
-    use Symfony\Component\Console\Input\InputInterface;
-    use Symfony\Component\Console\Output\OutputInterface;
 
     // the name of the command is what users type after "php bin/console"
     #[AsCommand(name: 'app:create-user')]
-    class CreateUserCommand extends Command
+    class CreateUserCommand
     {
-        protected function execute(InputInterface $input, OutputInterface $output): int
+        public function __invoke(): int
         {
             // ... put here the code to create the user
 
@@ -147,104 +142,54 @@ want a command to create a user::
         }
     }
 
-Configuring the Command
-~~~~~~~~~~~~~~~~~~~~~~~
-
-You can optionally define a description, help message and the
-:doc:`input options and arguments </console/input>` by overriding the
-``configure()`` method::
+If you can't use PHP attributes, register the command as a service and
+:doc:`tag it </service_container/tags>` with the ``console.command`` tag. If you're using the
+:ref:`default services.yaml configuration <service-container-services-load-example>`,
+this is already done for you, thanks to :ref:`autoconfiguration <services-autoconfigure>`.
 
-    // src/Command/CreateUserCommand.php
+You can also use ``#[AsCommand]`` to add a description and longer help text for the command::
 
-    // ...
-    class CreateUserCommand extends Command
+    #[AsCommand(
+        name: 'app:create-user',
+        description: 'Creates a new user.', // the command description shown when running "php bin/console list"
+        help: 'This command allows you to create a user...', // the command help shown when running the command with the "--help" option
+    )]
+    class CreateUserCommand
     {
-        // ...
-        protected function configure(): void
+        public function __invoke(): int
         {
-            $this
-                // the command description shown when running "php bin/console list"
-                ->setDescription('Creates a new user.')
-                // the command help shown when running the command with the "--help" option
-                ->setHelp('This command allows you to create a user...')
-            ;
+            // ...
         }
     }
 
-.. tip::
-
-    Using the ``#[AsCommand]`` attribute to define a description instead of
-    using the ``setDescription()`` method allows to get the command description without
-    instantiating its class. This makes the ``php bin/console list`` command run
-    much faster.
-
-    If you want to always run the ``list`` command fast, add the ``--short`` option
-    to it (``php bin/console list --short``). This will avoid instantiating command
-    classes, but it won't show any description for commands that use the
-    ``setDescription()`` method instead of the attribute to define the command
-    description.
-
-The ``configure()`` method is called automatically at the end of the command
-constructor. If your command defines its own constructor, set the properties
-first and then call to the parent constructor, to make those properties
-available in the ``configure()`` method::
+Additionally, you can extend the :class:`Symfony\\Component\\Console\\Command\\Command` class to
+leverage advanced features like lifecycle hooks (e.g. :method:`Symfony\\Component\\Console\\Command\\Command::initialize` and
+and :method:`Symfony\\Component\\Console\\Command\\Command::interact`)::
 
-    // ...
+    use Symfony\Component\Console\Attribute\AsCommand;
     use Symfony\Component\Console\Command\Command;
-    use Symfony\Component\Console\Input\InputArgument;
+    use Symfony\Component\Console\Input\InputInterface;
+    use Symfony\Component\Console\Output\OutputInterface;
 
+    #[AsCommand(name: 'app:create-user')]
     class CreateUserCommand extends Command
     {
-        // ...
-
-        public function __construct(bool $requirePassword = false)
+        public function initialize(InputInterface $input, OutputInterface $output): void
         {
-            // best practices recommend to call the parent constructor first and
-            // then set your own properties. That wouldn't work in this case
-            // because configure() needs the properties set in this constructor
-            $this->requirePassword = $requirePassword;
-
-            parent::__construct();
+            // ...
         }
 
-        protected function configure(): void
+        public function interact(InputInterface $input, OutputInterface $output): void
         {
-            $this
-                // ...
-                ->addArgument('password', $this->requirePassword ? InputArgument::REQUIRED : InputArgument::OPTIONAL, 'User password')
-            ;
+            // ...
         }
-    }
-
-.. _console_registering-the-command:
-
-Registering the Command
-~~~~~~~~~~~~~~~~~~~~~~~
 
-You can register the command by adding the ``AsCommand`` attribute to it::
-
-    // src/Command/CreateUserCommand.php
-    namespace App\Command;
-
-    use Symfony\Component\Console\Attribute\AsCommand;
-    use Symfony\Component\Console\Command\Command;
-
-    #[AsCommand(
-        name: 'app:create-user',
-        description: 'Creates a new user.',
-        hidden: false,
-        aliases: ['app:add-user']
-    )]
-    class CreateUserCommand extends Command
-    {
-        // ...
+        public function __invoke(): int
+        {
+            // ...
+        }
     }
 
-If you can't use PHP attributes, register the command as a service and
-:doc:`tag it </service_container/tags>` with the ``console.command`` tag. If you're using the
-:ref:`default services.yaml configuration <service-container-services-load-example>`,
-this is already done for you, thanks to :ref:`autoconfiguration <services-autoconfigure>`.
-
 Running the Command
 ~~~~~~~~~~~~~~~~~~~
 
@@ -255,16 +200,16 @@ After configuring and registering the command, you can run it in the terminal:
     $ php bin/console app:create-user
 
 As you might expect, this command will do nothing as you didn't write any logic
-yet. Add your own logic inside the ``execute()`` method.
+yet. Add your own logic inside the ``__invoke()`` method.
 
 Console Output
 --------------
 
-The ``execute()`` method has access to the output stream to write messages to
+The ``__invoke()`` method has access to the output stream to write messages to
 the console::
 
     // ...
-    protected function execute(InputInterface $input, OutputInterface $output): int
+    public function __invoke(OutputInterface $output): int
     {
         // outputs multiple lines to the console (adding "\n" at the end of each line)
         $output->writeln([
@@ -315,9 +260,10 @@ method, which returns an instance of
     // ...
     use Symfony\Component\Console\Output\ConsoleOutputInterface;
 
-    class MyCommand extends Command
+    #[AsCommand(name: 'app:my-command')]
+    class MyCommand
     {
-        protected function execute(InputInterface $input, OutputInterface $output): int
+        public function __invoke(OutputInterface $output): int
         {
             if (!$output instanceof ConsoleOutputInterface) {
                 throw new \LogicException('This command accepts only an instance of "ConsoleOutputInterface".');
@@ -376,20 +322,12 @@ Console Input
 
 Use input options or arguments to pass information to the command::
 
-    use Symfony\Component\Console\Input\InputArgument;
+    use Symfony\Component\Console\Attribute\Argument;
 
-    // ...
-    protected function configure(): void
-    {
-        $this
-            // configure an argument
-            ->addArgument('username', InputArgument::REQUIRED, 'The username of the user.')
-            // ...
-        ;
-    }
-
-    // ...
-    public function execute(InputInterface $input, OutputInterface $output): int
+    // The #[Argument] attribute configures $username as a
+    // required input argument and its value is automatically
+    // passed to this parameter
+    public function __invoke(#[Argument('The username of the user.')] string $username, OutputInterface $output): int
     {
         $output->writeln([
             'User Creator',
@@ -397,8 +335,7 @@ Use input options or arguments to pass information to the command::
             '',
         ]);
 
-        // retrieve the argument value using getArgument()
-        $output->writeln('Username: '.$input->getArgument('username'));
+        $output->writeln('Username: '.$username);
 
         return Command::SUCCESS;
     }
@@ -428,23 +365,22 @@ as a service, you can use normal dependency injection. Imagine you have a
 
     // ...
     use App\Service\UserManager;
-    use Symfony\Component\Console\Command\Command;
+    use Symfony\Component\Console\Attribute\Argument;
+    use Symfony\Component\Console\Attribute\AsCommand;
 
-    class CreateUserCommand extends Command
+    #[AsCommand(name: 'app:create-user')]
+    class CreateUserCommand
     {
         public function __construct(
-            private UserManager $userManager,
-        ){
-            parent::__construct();
+            private UserManager $userManager
+        ) {
         }
 
-        // ...
-
-        protected function execute(InputInterface $input, OutputInterface $output): int
+        public function __invoke(#[Argument] string $username, OutputInterface $output): int
         {
             // ...
 
-            $this->userManager->create($input->getArgument('username'));
+            $this->userManager->create($username);
 
             $output->writeln('User successfully generated!');
 
@@ -472,7 +408,7 @@ command:
     Note that it will not be called when the command is run without interaction
     (e.g. when passing the ``--no-interaction`` global option flag).
 
-:method:`Symfony\\Component\\Console\\Command\\Command::execute` *(required)*
+``__invoke()`` (or :method:`Symfony\\Component\\Console\\Command\\Command::execute`) *(required)*
     This method is executed after ``interact()`` and ``initialize()``.
     It contains the logic you want the command to execute and it must
     return an integer which will be used as the command `exit status`_.
diff --git a/console/calling_commands.rst b/console/calling_commands.rst
index dd1f0b12ff9..875ead15d2d 100644
--- a/console/calling_commands.rst
+++ b/console/calling_commands.rst
@@ -14,20 +14,18 @@ arguments and options you want to pass to the command. The command name must be
 the first argument.
 
 Eventually, calling the ``doRun()`` method actually runs the command and returns
-the returned code from the command (return value from command ``execute()``
+the returned code from the command (return value from command ``__invoke()``
 method)::
 
     // ...
-    use Symfony\Component\Console\Command;
+    use Symfony\Component\Console\Attribute\AsCommand;
     use Symfony\Component\Console\Input\ArrayInput;
-    use Symfony\Component\Console\Input\InputInterface;
     use Symfony\Component\Console\Output\OutputInterface;
 
-    class CreateUserCommand extends Command
+    #[AsCommand(name: 'app:create-user')]
+    class CreateUserCommand
     {
-        // ...
-
-        protected function execute(InputInterface $input, OutputInterface $output): int
+        public function __invoke(OutputInterface $output): int
         {
             $greetInput = new ArrayInput([
                 // the command name is passed as first argument
diff --git a/console/commands_as_services.rst b/console/commands_as_services.rst
index 1393879a1df..ed5b99f9cb4 100644
--- a/console/commands_as_services.rst
+++ b/console/commands_as_services.rst
@@ -16,27 +16,16 @@ For example, suppose you want to log something from within your command::
 
     use Psr\Log\LoggerInterface;
     use Symfony\Component\Console\Attribute\AsCommand;
-    use Symfony\Component\Console\Command\Command;
-    use Symfony\Component\Console\Input\InputInterface;
-    use Symfony\Component\Console\Output\OutputInterface;
 
-    #[AsCommand(name: 'app:sunshine')]
-    class SunshineCommand extends Command
+    #[AsCommand(name: 'app:sunshine', description: 'Good morning!')]
+    class SunshineCommand
     {
         public function __construct(
             private LoggerInterface $logger,
         ) {
-            // you *must* call the parent constructor
-            parent::__construct();
-        }
-
-        protected function configure(): void
-        {
-            $this
-                ->setDescription('Good morning!');
         }
 
-        protected function execute(InputInterface $input, OutputInterface $output): int
+        public function __invoke(): int
         {
             $this->logger->info('Waking up the sun');
             // ...
@@ -70,7 +59,7 @@ To make your command lazily loaded, either define its name using the PHP
     // ...
 
     #[AsCommand(name: 'app:sunshine')]
-    class SunshineCommand extends Command
+    class SunshineCommand
     {
         // ...
     }
diff --git a/console/hide_commands.rst b/console/hide_commands.rst
index 44a69d09289..4ab9d3a6dad 100644
--- a/console/hide_commands.rst
+++ b/console/hide_commands.rst
@@ -15,10 +15,9 @@ the ``hidden`` property of the ``AsCommand`` attribute::
     namespace App\Command;
 
     use Symfony\Component\Console\Attribute\AsCommand;
-    use Symfony\Component\Console\Command\Command;
 
     #[AsCommand(name: 'app:legacy', hidden: true)]
-    class LegacyCommand extends Command
+    class LegacyCommand
     {
         // ...
     }
diff --git a/console/input.rst b/console/input.rst
index 98cddd63875..d5b6e4881bb 100644
--- a/console/input.rst
+++ b/console/input.rst
@@ -356,7 +356,7 @@ can also implement value completion for the input in your commands. For
 instance, you may want to complete all usernames from the database in the
 ``name`` argument of your greet command.
 
-To achieve this, use the 5th argument of ``addArgument()``/``addOption``::
+To achieve this, use the 5th argument of ``addArgument()`` or the 6th argument of ``addOption()``::
 
     // ...
     use Symfony\Component\Console\Completion\CompletionInput;
@@ -446,15 +446,21 @@ The Console component adds some predefined options to all commands:
 
 * ``--verbose``: sets the verbosity level (e.g. ``1`` the default, ``2`` and
   ``3``, or you can use respective shortcuts ``-v``, ``-vv`` and ``-vvv``)
-* ``--quiet``: disables output and interaction
-* ``--no-interaction``: disables interaction
-* ``--version``: outputs the version number of the console application
-* ``--help``: displays the command help
+* ``--silent``: disables all output and interaction, including errors
+* ``--quiet|-q``: disables output and interaction, but errors are still displayed
+* ``--no-interaction|-n``: disables interaction
+* ``--version|-V``: outputs the version number of the console application
+* ``--help|-h``: displays the command help
 * ``--ansi|--no-ansi``: whether to force of disable coloring the output
+* ``--profile``: enables the Symfony profiler
+
+.. versionadded:: 7.2
+
+    The ``--silent`` option was introduced in Symfony 7.2.
 
 When using the ``FrameworkBundle``, two more options are predefined:
 
-* ``--env``: sets the Kernel configuration environment (defaults to ``APP_ENV``)
+* ``--env|-e``: sets the Kernel configuration environment (defaults to ``APP_ENV``)
 * ``--no-debug``: disables Kernel debug (defaults to ``APP_DEBUG``)
 
 So your custom commands can use them too out-of-the-box.
diff --git a/console/lockable_trait.rst b/console/lockable_trait.rst
index 0f4a4900e17..2a4fd64ffaf 100644
--- a/console/lockable_trait.rst
+++ b/console/lockable_trait.rst
@@ -13,19 +13,17 @@ that adds two convenient methods to lock and release commands::
     // ...
     use Symfony\Component\Console\Command\Command;
     use Symfony\Component\Console\Command\LockableTrait;
-    use Symfony\Component\Console\Input\InputInterface;
-    use Symfony\Component\Console\Output\OutputInterface;
+    use Symfony\Component\Console\Style\SymfonyStyle;
 
-    class UpdateContentsCommand extends Command
+    #[AsCommand(name: 'contents:update')]
+    class UpdateContentsCommand
     {
         use LockableTrait;
 
-        // ...
-
-        protected function execute(InputInterface $input, OutputInterface $output): int
+        public function __invoke(SymfonyStyle $io): int
         {
             if (!$this->lock()) {
-                $output->writeln('The command is already running in another process.');
+                $io->writeln('The command is already running in another process.');
 
                 return Command::SUCCESS;
             }
@@ -52,7 +50,8 @@ a ``$lockFactory`` property with your own lock factory::
     use Symfony\Component\Console\Command\LockableTrait;
     use Symfony\Component\Lock\LockFactory;
 
-    class UpdateContentsCommand extends Command
+    #[AsCommand(name: 'contents:update')]
+    class UpdateContentsCommand
     {
         use LockableTrait;
 
diff --git a/console/style.rst b/console/style.rst
index 0aaaa3f675e..5357b9e6172 100644
--- a/console/style.rst
+++ b/console/style.rst
@@ -7,18 +7,18 @@ questions to the user involves a lot of repetitive code.
 
 Consider for example the code used to display the title of the following command::
 
-    // src/Command/GreetCommand.php
+    // src/Command/MyCommand.php
     namespace App\Command;
 
+    use Symfony\Component\Console\Attribute\AsCommand;
     use Symfony\Component\Console\Command\Command;
     use Symfony\Component\Console\Input\InputInterface;
     use Symfony\Component\Console\Output\OutputInterface;
 
-    class GreetCommand extends Command
+    #[AsCommand(name: 'app:my-command')]
+    class MyCommand
     {
-        // ...
-
-        protected function execute(InputInterface $input, OutputInterface $output): int
+        public function __invoke(InputInterface $input, OutputInterface $output): int
         {
             $output->writeln([
                 '<info>Lorem Ipsum Dolor Sit Amet</>',
@@ -42,26 +42,22 @@ which allow to create *semantic* commands and forget about their styling.
 Basic Usage
 -----------
 
-In your command, instantiate the :class:`Symfony\\Component\\Console\\Style\\SymfonyStyle`
-class and pass the ``$input`` and ``$output`` variables as its arguments. Then,
-you can start using any of its helpers, such as ``title()``, which displays the
-title of the command::
+In your ``__invoke()`` method, add an argument of type :class:`Symfony\\Component\\Console\\Style\\SymfonyStyle`.
+Then, you can start using any of its helpers, such as ``title()``, which
+displays the title of the command::
 
-    // src/Command/GreetCommand.php
+    // src/Command/MyCommand.php
     namespace App\Command;
 
+    use Symfony\Component\Console\Attribute\AsCommand;
     use Symfony\Component\Console\Command\Command;
-    use Symfony\Component\Console\Input\InputInterface;
-    use Symfony\Component\Console\Output\OutputInterface;
     use Symfony\Component\Console\Style\SymfonyStyle;
 
-    class GreetCommand extends Command
+    #[AsCommand(name: 'app:my-command')]
+    class MyCommand
     {
-        // ...
-
-        protected function execute(InputInterface $input, OutputInterface $output): int
+        public function __invoke(SymfonyStyle $io): int
         {
-            $io = new SymfonyStyle($input, $output);
             $io->title('Lorem Ipsum Dolor Sit Amet');
 
             // ...
@@ -169,6 +165,32 @@ Content Methods
     styled according to the Symfony Style Guide, which allows you to use
     features such as dynamically appending rows.
 
+:method:`Symfony\\Component\\Console\\Style\\SymfonyStyle::tree`
+    It displays the given nested array as a formatted directory/file tree
+    structure in the console output::
+
+        $io->tree([
+            'src' =>  [
+                'Controller' => [
+                    'DefaultController.php',
+                ],
+                'Kernel.php',
+            ],
+            'templates' => [
+                'base.html.twig',
+            ],
+        ]);
+
+.. versionadded:: 7.3
+
+    The ``SymfonyStyle::tree()`` and the ``SymfonyStyle::createTree()`` methods
+    were introduced in Symfony 7.3.
+
+:method:`Symfony\\Component\\Console\\Style\\SymfonyStyle::createTree`
+    Creates an instance of :class:`Symfony\\Component\\Console\\Helper\\TreeHelper`
+    styled according to the Symfony Style Guide, which allows you to use
+    features such as dynamically nesting nodes.
+
 :method:`Symfony\\Component\\Console\\Style\\SymfonyStyle::newLine`
     It displays a blank line in the command output. Although it may seem useful,
     most of the times you won't need it at all. The reason is that every helper
@@ -327,6 +349,12 @@ User Input Methods
 
         $io->choice('Select the queue to analyze', ['queue1', 'queue2', 'queue3'], 'queue1');
 
+    Choice questions display both the choice value and a numeric index, which
+    starts from ``0`` by default. To use custom indices, pass an array with
+    custom numeric keys as the choice values::
+
+        $io->choice('Select the queue to analyze', [5 => 'queue1', 6 => 'queue2', 7 => 'queue3']);
+
     Finally, you can allow users to select multiple choices. To do so, users must
     separate each choice with a comma (e.g. typing ``1, 2`` will select choice 1
     and 2)::
@@ -422,19 +450,17 @@ long they are. This is done to enable clickable URLs in terminals that support t
 
 If you prefer to wrap all contents, including URLs, use this method::
 
-    // src/Command/GreetCommand.php
+    // src/Command/MyCommand.php
     namespace App\Command;
 
     // ...
     use Symfony\Component\Console\Style\SymfonyStyle;
 
-    class GreetCommand extends Command
+    #[AsCommand(name: 'app:my-command')]
+    class MyCommand
     {
-        // ...
-
-        protected function execute(InputInterface $input, OutputInterface $output): int
+        public function __invoke(SymfonyStyle $io): int
         {
-            $io = new SymfonyStyle($input, $output);
             $io->getOutputWrapper()->setAllowCutUrls(true);
 
             // ...
@@ -461,7 +487,7 @@ Then, instantiate this custom class instead of the default ``SymfonyStyle`` in
 your commands. Thanks to the ``StyleInterface`` you won't need to change the code
 of your commands to change their appearance::
 
-    // src/Command/GreetCommand.php
+    // src/Command/MyCommand.php
     namespace App\Console;
 
     use App\Console\CustomStyle;
@@ -469,16 +495,11 @@ of your commands to change their appearance::
     use Symfony\Component\Console\Input\InputInterface;
     use Symfony\Component\Console\Output\OutputInterface;
 
-    class GreetCommand extends Command
+    #[AsCommand(name: 'app:my-command')]
+    class MyCommand
     {
-        // ...
-
-        protected function execute(InputInterface $input, OutputInterface $output): int
+        public function __invoke(InputInterface $input, OutputInterface $output): int
         {
-            // Before
-            $io = new SymfonyStyle($input, $output);
-
-            // After
             $io = new CustomStyle($input, $output);
 
             // ...
diff --git a/console/verbosity.rst b/console/verbosity.rst
index f7a1a1e5e59..3afd085d773 100644
--- a/console/verbosity.rst
+++ b/console/verbosity.rst
@@ -7,7 +7,10 @@ messages, but you can control their verbosity with the ``-q`` and ``-v`` options
 
 .. code-block:: terminal
 
-    # do not output any message (not even the command result messages)
+    # suppress all output, including errors
+    $ php bin/console some-command --silent
+
+    # suppress all output (even the command result messages) but display errors
     $ php bin/console some-command -q
     $ php bin/console some-command --quiet
 
@@ -23,6 +26,10 @@ messages, but you can control their verbosity with the ``-q`` and ``-v`` options
     # display all messages (useful to debug errors)
     $ php bin/console some-command -vvv
 
+.. versionadded:: 7.2
+
+    The ``--silent`` option was introduced in Symfony 7.2.
+
 The verbosity level can also be controlled globally for all commands with the
 ``SHELL_VERBOSITY`` environment variable (the ``-q`` and ``-v`` options still
 have more precedence over the value of ``SHELL_VERBOSITY``):
@@ -30,6 +37,7 @@ have more precedence over the value of ``SHELL_VERBOSITY``):
 =====================  =========================  ===========================================
 Console option         ``SHELL_VERBOSITY`` value  Equivalent PHP constant
 =====================  =========================  ===========================================
+``--silent``           ``-2``                     ``OutputInterface::VERBOSITY_SILENT``
 ``-q`` or ``--quiet``  ``-1``                     ``OutputInterface::VERBOSITY_QUIET``
 (none)                 ``0``                      ``OutputInterface::VERBOSITY_NORMAL``
 ``-v``                 ``1``                      ``OutputInterface::VERBOSITY_VERBOSE``
@@ -41,26 +49,27 @@ It is possible to print a message in a command for only a specific verbosity
 level. For example::
 
     // ...
+    use Symfony\Component\Console\Attribute\Argument;
+    use Symfony\Component\Console\Attribute\AsCommand;
     use Symfony\Component\Console\Command\Command;
     use Symfony\Component\Console\Input\InputInterface;
     use Symfony\Component\Console\Output\OutputInterface;
 
-    class CreateUserCommand extends Command
+    #[AsCommand(name: 'app:create-user')]
+    class CreateUserCommand
     {
-        // ...
-
-        public function execute(InputInterface $input, OutputInterface $output): int
+        public function __invoke(OutputInterface $output, #[Argument] string $username, #[Argument] string $password): int
         {
             $user = new User(...);
 
             $output->writeln([
-                'Username: '.$input->getArgument('username'),
-                'Password: '.$input->getArgument('password'),
+                'Username: '.$username,
+                'Password: '.$password,
             ]);
 
-            // available methods: ->isQuiet(), ->isVerbose(), ->isVeryVerbose(), ->isDebug()
+            // available methods: ->isSilent(), ->isQuiet(), ->isVerbose(), ->isVeryVerbose(), ->isDebug()
             if ($output->isVerbose()) {
-                $output->writeln('User class: '.get_class($user));
+                $output->writeln('User class: '.$user::class);
             }
 
             // alternatively you can pass the verbosity level PHP constant to writeln()
@@ -73,10 +82,19 @@ level. For example::
         }
     }
 
-When the quiet level is used, all output is suppressed as the default
+.. versionadded:: 7.2
+
+    The ``isSilent()`` method was introduced in Symfony 7.2.
+
+When the silent or quiet level are used, all output is suppressed as the default
 :method:`Symfony\\Component\\Console\\Output\\Output::write` method returns
 without actually printing.
 
+.. tip::
+
+    When using the ``silent`` verbosity, errors won't be displayed in the console
+    but they will still be logged through the :doc:`Symfony logger </logging>` integration.
+
 .. tip::
 
     The MonologBridge provides a :class:`Symfony\\Bridge\\Monolog\\Handler\\ConsoleHandler`
diff --git a/contributing/code/bc.rst b/contributing/code/bc.rst
index 497c70fb01d..ad394d2720c 100644
--- a/contributing/code/bc.rst
+++ b/contributing/code/bc.rst
@@ -176,6 +176,13 @@ covered by our backward compatibility promise:
 | Use a public, protected or private method     | Yes                         |
 +-----------------------------------------------+-----------------------------+
 
+Using our Translations
+~~~~~~~~~~~~~~~~~~~~~~
+
+All translations provided by Symfony for security and validation errors are
+intended for internal use only. They may be changed or removed at any time.
+Symfony's Backward Compatibility Promise does not apply to internal translations.
+
 Working on Symfony Code
 -----------------------
 
diff --git a/contributing/code/index.rst b/contributing/code/index.rst
index e537eb3a0c3..b4cf85441b0 100644
--- a/contributing/code/index.rst
+++ b/contributing/code/index.rst
@@ -9,7 +9,6 @@ Contributing Code
     reproducer
     pull_requests
     maintenance
-    core_team
     security
     tests
     bc
diff --git a/contributing/code/pull_requests.rst b/contributing/code/pull_requests.rst
index 7c9ab2579a5..6b40e940dfb 100644
--- a/contributing/code/pull_requests.rst
+++ b/contributing/code/pull_requests.rst
@@ -132,7 +132,7 @@ work:
   branch (you can find them on the `Symfony releases page`_). E.g. if you
   found a bug introduced in ``v5.1.10``, you need to work on ``5.4``.
 
-* ``7.1``, if you are adding a new feature.
+* ``7.2``, if you are adding a new feature.
 
   The only exception is when a new :doc:`major Symfony version </contributing/community/releases>`
   (5.0, 6.0, etc.) comes out every two years. Because of the
@@ -404,7 +404,7 @@ perspective, please join the ``#contribs`` channel on `Symfony Slack`_. If you
 receive feedback you find abusive please contact the
 :doc:`CARE team </contributing/code_of_conduct/care_team>`.
 
-The :doc:`core team </contributing/code/core_team>` is responsible for deciding
+The :doc:`core team </contributing/core_team>` is responsible for deciding
 which PR gets merged, so their feedback is the most relevant. So do not feel
 pressured to refactor your code immediately when someone provides feedback.
 
diff --git a/contributing/code/reproducer.rst b/contributing/code/reproducer.rst
index 3392ca87035..c2208b70b09 100644
--- a/contributing/code/reproducer.rst
+++ b/contributing/code/reproducer.rst
@@ -65,8 +65,8 @@ to a route definition. Then, after creating your project:
    of controllers, actions, etc. as in your original application.
 #. Create a small controller and add your routing definition that shows the bug.
 #. Don't create or modify any other file.
-#. Install the :doc:`local web server </setup/symfony_server>` provided by Symfony
-   and use the ``symfony server:start`` command to browse to the new route and
+#. Install the :doc:`Symfony CLI </setup/symfony_cli>` tool and use the
+   ``symfony server:start`` command to browse to the new route and
    see if the bug appears or not.
 #. If you can see the bug, you're done and you can already share the code with us.
 #. If you can't see the bug, you must keep making small changes. For example, if
diff --git a/contributing/code/standards.rst b/contributing/code/standards.rst
index ced2568c5f7..ebfde7dfab4 100644
--- a/contributing/code/standards.rst
+++ b/contributing/code/standards.rst
@@ -211,7 +211,7 @@ Naming Conventions
 * Use `camelCase`_ for PHP variables, function and method names, arguments
   (e.g. ``$acceptableContentTypes``, ``hasSession()``);
 
-Use `snake_case`_ for configuration parameters, route names and Twig template
+* Use `snake_case`_ for configuration parameters, route names and Twig template
   variables (e.g. ``framework.csrf_protection``, ``http_status_code``);
 
 * Use SCREAMING_SNAKE_CASE for constants (e.g. ``InputArgument::IS_ARRAY``);
@@ -300,7 +300,7 @@ Documentation
 * When adding a new class or when making significant changes to an existing class,
   an ``@author`` tag with personal contact information may be added, or expanded.
   Please note it is possible to have the personal contact information updated or
-  removed per request to the :doc:`core team </contributing/code/core_team>`.
+  removed per request to the :doc:`core team </contributing/core_team>`.
 
 License
 ~~~~~~~
diff --git a/contributing/code/tests.rst b/contributing/code/tests.rst
index 08f6bc5df12..060e3eda02b 100644
--- a/contributing/code/tests.rst
+++ b/contributing/code/tests.rst
@@ -32,7 +32,7 @@ tests, such as Doctrine, Twig and Monolog. To do so,
 
     .. code-block:: terminal
 
-        $ COMPOSER_ROOT_VERSION=7.1.x-dev composer update
+        $ COMPOSER_ROOT_VERSION=7.2.x-dev composer update
 
 .. _running:
 
diff --git a/contributing/code_of_conduct/care_team.rst b/contributing/code_of_conduct/care_team.rst
index e061c0a0afe..1b15850da39 100644
--- a/contributing/code_of_conduct/care_team.rst
+++ b/contributing/code_of_conduct/care_team.rst
@@ -54,7 +54,7 @@ also contact all of them at once by emailing ** care@symfony.com **.
 About the CARE Team
 -------------------
 
-The :doc:`Symfony project leader </contributing/code/core_team>` appoints the CARE
+The :doc:`Symfony project leader </contributing/core_team>` appoints the CARE
 team with candidates they see fit. The CARE team will consist of at least
 3 people. The team should be representing as many demographics as possible,
 ideally from different employers.
diff --git a/contributing/code_of_conduct/code_of_conduct.rst b/contributing/code_of_conduct/code_of_conduct.rst
index 6202fdad424..ce14dd5ad0e 100644
--- a/contributing/code_of_conduct/code_of_conduct.rst
+++ b/contributing/code_of_conduct/code_of_conduct.rst
@@ -34,7 +34,7 @@ Examples of unacceptable behavior include:
   any kind
 * Trolling, insulting or derogatory comments, and personal or political attacks
 * Public or private harassment
-* Publishing others’ private information, such as a physical or email address,
+* Publishing others' private information, such as a physical or email address,
   without their explicit permission
 * Other conduct which could reasonably be considered inappropriate in a
   professional setting
@@ -128,7 +128,7 @@ Attribution
 This Code of Conduct is adapted from the `Contributor Covenant`_, version 2.1,
 available at https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
 
-Community Impact Guidelines were inspired by `Mozilla’s code of conduct enforcement ladder`_.
+Community Impact Guidelines were inspired by `Mozilla's code of conduct enforcement ladder`_.
 
 Related Documents
 -----------------
@@ -141,4 +141,4 @@ Related Documents
     concrete_example_document
 
 .. _Contributor Covenant: https://www.contributor-covenant.org
-.. _Mozilla’s code of conduct enforcement ladder: https://github.com/mozilla/diversity
+.. _Mozilla's code of conduct enforcement ladder: https://github.com/mozilla/diversity
diff --git a/contributing/code_of_conduct/concrete_example_document.rst b/contributing/code_of_conduct/concrete_example_document.rst
index 60ffe2527db..227a41df4a8 100644
--- a/contributing/code_of_conduct/concrete_example_document.rst
+++ b/contributing/code_of_conduct/concrete_example_document.rst
@@ -9,7 +9,7 @@ according to the Symfony code of conduct.
 Concrete Examples
 -----------------
 
-* Unwelcome comments regarding a person’s lifestyle choices and practices,
+* Unwelcome comments regarding a person's lifestyle choices and practices,
   including those related to food, health, parenting, drugs, and employment;
 * Deliberate misgendering or use of `dead names`_ (The birth name
   of a person who has since changed their name, often a transgender person);
diff --git a/contributing/community/releases.rst b/contributing/community/releases.rst
index 8126496bfef..2c5a796e9b5 100644
--- a/contributing/community/releases.rst
+++ b/contributing/community/releases.rst
@@ -113,7 +113,7 @@ PHP Compatibility
 -----------------
 
 The **minimum** PHP version is decided for each **major** Symfony version by consensus
-amongst the :doc:`core team </contributing/code/core_team>` and documented as
+amongst the :doc:`core team </contributing/core_team>` and documented as
 part of the :ref:`technical requirements for running Symfony applications
 <symfony-tech-requirements>`.
 
diff --git a/contributing/community/review-comments.rst b/contributing/community/review-comments.rst
index 5b9bc932205..331352bb5fd 100644
--- a/contributing/community/review-comments.rst
+++ b/contributing/community/review-comments.rst
@@ -28,7 +28,7 @@ constructive, respectful and helpful reviews and replies.
     welcoming place for everyone. **You are free to disagree with
     someone's opinions, but don't be disrespectful.**
 
-It’s important to accept that many programming decisions are opinions.
+It's important to accept that many programming decisions are opinions.
 Discuss trade-offs, which you prefer, and reach a resolution quickly.
 It's not about being right or wrong, but using what works.
 
diff --git a/contributing/code/core_team.rst b/contributing/core_team.rst
similarity index 60%
rename from contributing/code/core_team.rst
rename to contributing/core_team.rst
index 1b1703e4f93..932cc390d60 100644
--- a/contributing/code/core_team.rst
+++ b/contributing/core_team.rst
@@ -13,8 +13,8 @@ This document states the rules that govern the Symfony core team. These rules
 are effective upon publication of this document and all Symfony Core members
 must adhere to said rules and protocol.
 
-Role of a Core Team Member
---------------------------
+Core Team Member Role
+---------------------
 
 In addition to being a regular contributor, core team members are expected to:
 
@@ -22,6 +22,30 @@ In addition to being a regular contributor, core team members are expected to:
 * Help enforce, improve, and implement Symfony :doc:`processes and policies </contributing/index>`;
 * Participate in the Symfony Core Team discussions (on Slack and GitHub).
 
+Core Team Member Responsibilities
+---------------------------------
+
+Core Team members are unpaid volunteers and as such, they are not expected to
+dedicate any specific amount of time on Symfony. They are expected to help the
+project in any way they can. From reviewing pull requests and writing documentation,
+to participating in discussions and helping the community in general. However,
+their involvement is completely voluntary and can be as much or as little as
+they want.
+
+Core Team Communication
+~~~~~~~~~~~~~~~~~~~~~~~
+
+As an open source project, public discussions and documentation is favored
+over private ones. All communication in the Symfony community conforms to
+the :doc:`/contributing/code_of_conduct/code_of_conduct`. Request
+assistance from other Core and CARE team members when getting in situations
+not following the Code of Conduct.
+
+Core Team members are invited in a private Slack channel, for quick
+interactions and private processes (e.g. security issues). Each member
+should feel free to ask for assistance for anything they may encounter.
+Expect no judgement from other team members.
+
 Core Organization
 -----------------
 
@@ -45,6 +69,7 @@ In addition, there are other groups created to manage specific topics:
 * **Security Team**: manages the whole security process (triaging reported vulnerabilities,
   fixing the reported issues, coordinating the release of security fixes, etc.);
 * **Symfony UX Team**: manages the `UX repositories`_;
+* **Symfony CLI Team**: manages the `CLI repositories`_;
 * **Documentation Team**: manages the whole `symfony-docs repository`_.
 
 Active Core Members
@@ -72,7 +97,12 @@ Active Core Members
   * **Oskar Stark** (`OskarStark`_);
   * **Mathieu Santostefano** (`welcomattic`_);
   * **Kevin Bond** (`kbond`_);
-  * **Jérôme Tamarelle** (`gromnan`_).
+  * **Jérôme Tamarelle** (`gromnan`_);
+  * **Berislav Balogović** (`hypemc`_);
+  * **Mathias Arlaud** (`mtarld`_);
+  * **Florent Morselli** (`spomky`_);
+  * **Alexandre Daubois** (`alexandre-daubois`_);
+  * **Christopher Hertel** (`chr-hertel`_).
 
 * **Security Team** (``@symfony/security`` on GitHub):
 
@@ -87,6 +117,11 @@ Active Core Members
   * **Hugo Alliaume** (`kocal`_);
   * **Matheo Daninos** (`webmamba`_).
 
+* **Symfony CLI Team** (``@symfony-cli/core`` on GitHub):
+
+  * **Fabien Potencier** (`fabpot`_);
+  * **Tugdual Saunier** (`tucksaun`_).
+
 * **Documentation Team** (``@symfony/team-symfony-docs`` on GitHub):
 
   * **Fabien Potencier** (`fabpot`_);
@@ -131,6 +166,14 @@ A Symfony Core membership can be revoked for any of the following reasons:
 * Willful negligence or intent to harm the Symfony project;
 * Upon decision of the **Project Leader**.
 
+Core Membership Compensation
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Core Team members work on Symfony on a purely voluntary basis. In return
+for their work for the Symfony project, members can get free access to
+Symfony conferences. Personal vouchers for Symfony conferences are handed out
+on request by the **Project Leader**.
+
 Code Development Rules
 ----------------------
 
@@ -155,7 +198,7 @@ Pull Request Merging Policy
 
 A pull request **can be merged** if:
 
-* It is a :ref:`minor change <core-team_minor-changes>`;
+* It is an :ref:`unsubstantial change <core-team_unsubstantial-changes>`;
 * Enough time was given for peer reviews;
 * It is a bug fix and at least two **Mergers Team** members voted ``+1``
   (only one if the submitter is part of the Mergers team) and no Core
@@ -164,12 +207,20 @@ A pull request **can be merged** if:
   ``+1`` (if the submitter is part of the Mergers team, two *other* members)
   and no Core member voted ``-1`` (via GitHub reviews or as comments).
 
+.. _core-team_unsubstantial-changes:
+
+.. note::
+
+    Unsubstantial changes comprise typos, DocBlock fixes, code standards
+    fixes, comment, exception message tweaks, and minor CSS, JavaScript and
+    HTML modifications.
+
 Pull Request Merging Process
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-All code must be committed to the repository through pull requests, except for
-:ref:`minor change <core-team_minor-changes>` which can be committed directly
-to the repository.
+All code must be committed to the repository through pull requests, except
+for :ref:`unsubstantial change <core-team_unsubstantial-changes>` which can be
+committed directly to the repository.
 
 **Mergers** must always use the command-line ``gh`` tool provided by the
 **Project Leader** to merge pull requests.
@@ -191,6 +242,90 @@ following these rules:
 Getting the right category is important as it is used by automated tools to
 generate the CHANGELOG files when releasing new versions.
 
+.. tip::
+
+    Core team members are part of the ``mergers`` group on the ``symfony``
+    Github organization. This gives them write-access to many repositories,
+    including the main ``symfony/symfony`` mono-repository.
+
+    To avoid unintentional pushes to the main project (which in turn creates
+    new versions on Packagist), Core team members are encouraged to have
+    two clones of the project locally:
+
+    #. A clone for their own contributions, which they use to push to their
+       fork on GitHub. Clear out the push URL for the Symfony repository using
+       ``git remote set-url --push origin dev://null`` (change ``origin``
+       to the Git remote pointing to the Symfony repository);
+    #. A clone for merging, which they use in combination with ``gh`` and
+       allows them to push to the main repository.
+
+Upmerging Version Branches
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+To synchronize changes in all versions, version branches are regularly
+merged from oldest to latest, called "upmerging". This is a manual process.
+There is no strict policy on when this occurs, but usually not more than
+once a day and at least once before monthly releases.
+
+Before starting the upmerge, Git must be configured to provide a merge
+summary by running:
+
+.. code-block:: terminal
+
+    # Run command in the "symfony" repository
+    $ git config merge.stat true
+
+The upmerge should always be done on all maintained versions at the same
+time. Refer to `the releases page`_ to find all actively maintained
+versions (indicated by a green color).
+
+The process follows these steps:
+
+#. Start on the oldest version and make sure it's up to date with the
+   upstream repository;
+#. Check-out the second oldest version, update from upstream and merge the
+   previous version from the local branch;
+#. Continue this process until you reached the latest version;
+#. Push the branches to the repository and monitor the test suite. Failure
+   might indicate hidden/missed merge conflicts.
+
+.. code-block:: terminal
+
+    # 'origin' is refered to as the main upstream project
+    $ git fetch origin
+
+    # update the local branches
+    $ git checkout 6.4
+    $ git reset --hard origin/6.4
+    $ git checkout 7.2
+    $ git reset --hard origin/7.2
+    $ git checkout 7.3
+    $ git reset --hard origin/7.3
+
+    # upmerge 6.4 into 7.2
+    $ git checkout 7.2
+    $ git merge --no-ff 6.4
+    # ... resolve conflicts
+    $ git commit
+
+    # upmerge 7.2 into 7.3
+    $ git checkout 7.3
+    $ git merge --no-ff 7.2
+    # ... resolve conflicts
+    $ git commit
+
+    $ git push origin 7.3 7.2 6.4
+
+.. warning::
+
+    Upmerges must be explicit, i.e. no fast-forward merges.
+
+.. tip::
+
+    Solving merge conflicts can be challenging. You can always ping other
+    Core team members to help you in the process (e.g. members that merged
+    a specific conflicting change).
+
 Release Policy
 ~~~~~~~~~~~~~~
 
@@ -202,15 +337,9 @@ Symfony Core Rules and Protocol Amendments
 The rules described in this document may be amended at any time at the
 discretion of the **Project Leader**.
 
-.. _core-team_minor-changes:
-
-.. note::
-
-    Minor changes comprise typos, DocBlock fixes, code standards
-    violations, and minor CSS, JavaScript and HTML modifications.
-
 .. _`symfony-docs repository`: https://github.com/symfony/symfony-docs
 .. _`UX repositories`: https://github.com/symfony/ux
+.. _`CLI repositories`: https://github.com/symfony-cli
 .. _`fabpot`: https://github.com/fabpot/
 .. _`webmozart`: https://github.com/webmozart/
 .. _`Tobion`: https://github.com/Tobion/
@@ -245,3 +374,10 @@ discretion of the **Project Leader**.
 .. _`smnandre`: https://github.com/smnandre/
 .. _`kocal`: https://github.com/kocal/
 .. _`webmamba`: https://github.com/webmamba/
+.. _`hypemc`: https://github.com/hypemc/
+.. _`mtarld`: https://github.com/mtarld/
+.. _`spomky`: https://github.com/spomky/
+.. _`alexandre-daubois`: https://github.com/alexandre-daubois/
+.. _`tucksaun`: https://github.com/tucksaun/
+.. _`chr-hertel`: https://github.com/chr-hertel/
+.. _`the releases page`: https://symfony.com/releases
diff --git a/contributing/diversity/further_reading.rst b/contributing/diversity/further_reading.rst
index 8bb07c39c97..b5f44047159 100644
--- a/contributing/diversity/further_reading.rst
+++ b/contributing/diversity/further_reading.rst
@@ -9,7 +9,7 @@ Diversity in Open Source
 `Sage Sharp - What makes a good community? <https://sage.thesharps.us/2015/10/06/what-makes-a-good-community>`_
 `Ashe Dryden - The Ethics of Unpaid Labor and the OSS Community <https://www.ashedryden.com/blog/the-ethics-of-unpaid-labor-and-the-oss-community>`_
 `Model View Culture - The Dehumanizing Myth of the Meritocracy <https://modelviewculture.com/pieces/the-dehumanizing-myth-of-the-meritocracy>`_
-`Annalee - How “Good Intent” Undermines Diversity and Inclusion <https://thebias.com/2017/09/26/how-good-intent-undermines-diversity-and-inclusion>`_
+`Annalee - How "Good Intent" Undermines Diversity and Inclusion <https://thebias.com/2017/09/26/how-good-intent-undermines-diversity-and-inclusion>`_
 `Karolina Szczur - Building Inclusive Communities <https://speakerdeck.com/fox/building-inclusive-communities>`_
 
 Code of Conduct
@@ -22,7 +22,7 @@ Code of Conduct
 Inclusive language
 ------------------
 
-`Jenée Desmond-Harris - Why I’m finally convinced it's time to stop saying "you guys" <https://www.vox.com/2015/6/11/8761227/you-guys-sexism-language>`_
+`Jenée Desmond-Harris - Why I'm finally convinced it's time to stop saying "you guys" <https://www.vox.com/2015/6/11/8761227/you-guys-sexism-language>`_
 `inclusive language presentations <https://github.com/hcorona/diversity-inclusion/blob/master/inclusive-language-presentations.md>`_
 
 Other talks and Blog Posts
@@ -30,7 +30,7 @@ Other talks and Blog Posts
 
 `Lena Reinhard – A Talk About Nothing <https://www.youtube.com/watch?v=D3e3V66TH2Y>`_
 `Lena Reinhard - A Talk about Everything <https://www.youtube.com/watch?v=CZx7rYoq1Uw>`_
-`Sage Sharp - SCALE: Improving Diversity with Maslow’s hierarchy <https://sage.thesharps.us/2016/01/24/scale-improving-diversity-with-maslows-hierarchy>`_
+`Sage Sharp - SCALE: Improving Diversity with Maslow's hierarchy <https://sage.thesharps.us/2016/01/24/scale-improving-diversity-with-maslows-hierarchy>`_
 `UCSF - Unconscious Bias <https://diversity.ucsf.edu/resources/unconscious-bias>`_
 `Responding to harassment reports <http://geekfeminism.wikia.com/wiki/Conference_anti-harassment/Responding_to_reports>`_
 `Unconscious bias at work <https://rework.withgoogle.com/guides/unbiasing-raise-awareness/steps/watch-unconscious-bias-at-work>`_
diff --git a/contributing/documentation/format.rst b/contributing/documentation/format.rst
index e81abe92b79..3318df50841 100644
--- a/contributing/documentation/format.rst
+++ b/contributing/documentation/format.rst
@@ -2,19 +2,17 @@ Documentation Format
 ====================
 
 The Symfony documentation uses `reStructuredText`_ as its markup language and
-`Sphinx`_ for generating the documentation in the formats read by the end users,
-such as HTML and PDF.
+a custom tool called `Docs Builder`_ for generating the documentation pages.
 
 reStructuredText
 ----------------
 
 reStructuredText is a plain text markup syntax similar to Markdown, but much
-stricter with its syntax. If you are new to reStructuredText, take some time to
-familiarize with this format by reading the existing `Symfony documentation`_
-source code.
+stricter with its syntax. If you are new to reStructuredText, check out the
+`reStructuredText Primer`_ tutorial and the `reStructuredText Reference`_.
 
-If you want to learn more about this format, check out the `reStructuredText Primer`_
-tutorial and the `reStructuredText Reference`_.
+You can also take some time to familiarize with this format by reading the
+existing `Symfony documentation`_ source.
 
 .. warning::
 
@@ -24,12 +22,11 @@ tutorial and the `reStructuredText Reference`_.
     * Lists start at the beginning of a line (no indentation is allowed);
     * Inline code blocks use double-ticks (````like this````).
 
-Sphinx
-------
+Custom reStructuredText Directives
+----------------------------------
 
-Sphinx_ is a build system that provides tools to create documentation from
-reStructuredText documents. As such, it adds new directives and interpreted text
-roles to the standard reStructuredText markup. Read more about the `Sphinx Markup Constructs`_.
+The Symfony documentation includes several custom directives that extend the
+standard reStructuredText syntax.
 
 Syntax Highlighting
 ~~~~~~~~~~~~~~~~~~~
@@ -45,9 +42,9 @@ change it with the ``code-block`` directive:
 
 .. note::
 
-    Besides all of the major programming languages, the syntax highlighter
-    supports all kinds of markup and configuration languages. Check out the
-    list of `supported languages`_ on the syntax highlighter website.
+    Code highlighting is supported for all programming languages commonly used
+    in Symfony Docs, such as ``yaml``, ``xml``, ``twig``, ``html``, ``js``,
+    ``json``, ``text``, ``bash``, ``diff``, etc.
 
 .. _docs-configuration-blocks:
 
@@ -280,10 +277,8 @@ for Symfony versions that have a lower major version will be removed. For
 example, if Symfony 8.0 were released today, 7.0 to 7.4 ``versionadded`` and
 ``deprecated`` tags would be removed from the new ``8.0`` branch.
 
-.. _reStructuredText: https://docutils.sourceforge.io/rst.html
-.. _Sphinx: https://www.sphinx-doc.org/
+.. _`reStructuredText`: https://docutils.sourceforge.io/rst.html
+.. _`Docs Builder`: https://github.com/symfony-tools/docs-builder
 .. _`Symfony documentation`: https://github.com/symfony/symfony-docs
 .. _`reStructuredText Primer`: https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html
 .. _`reStructuredText Reference`: https://docutils.sourceforge.io/docs/user/rst/quickref.html
-.. _`Sphinx Markup Constructs`: https://www.sphinx-doc.org/en/1.7/markup/index.html
-.. _`supported languages`: https://pygments.org/languages/
diff --git a/contributing/documentation/overview.rst b/contributing/documentation/overview.rst
index 183910e6ac6..7095e4cbc4c 100644
--- a/contributing/documentation/overview.rst
+++ b/contributing/documentation/overview.rst
@@ -113,16 +113,16 @@ memorable name for the new branch (if you are fixing a reported issue, use
 
 .. code-block:: terminal
 
-    $ git checkout -b improve_install_article upstream/5.4
+    $ git checkout -b improve_install_article upstream/6.4
 
 In this example, the name of the branch is ``improve_install_article`` and the
-``upstream/5.4`` value tells Git to create this branch based on the ``5.4``
+``upstream/6.4`` value tells Git to create this branch based on the ``6.4``
 branch of the ``upstream`` remote, which is the original Symfony Docs repository.
 
 Fixes should always be based on the **oldest maintained branch** which contains
-the error. Nowadays this is the ``5.4`` branch. If you are instead documenting a
+the error. Nowadays this is the ``6.4`` branch. If you are instead documenting a
 new feature, switch to the first Symfony version that included it, e.g.
-``upstream/6.2``.
+``upstream/7.2``.
 
 **Step 5.** Now make your changes in the documentation. Add, tweak, reword and
 even remove any content and do your best to comply with the
@@ -156,7 +156,7 @@ changes should be applied:
     :alt: The base branch select option on the GitHub page.
 
 In this example, the **base fork** should be ``symfony/symfony-docs`` and
-the **base** branch should be the ``5.4``, which is the branch that you selected
+the **base** branch should be the ``4.4``, which is the branch that you selected
 to base your changes on. The **head fork** should be your forked copy
 of ``symfony-docs`` and the **compare** branch should be ``improve_install_article``,
 which is the name of the branch you created and where you made your changes.
@@ -209,7 +209,7 @@ contribution to the Symfony docs:
     # create a new branch based on the oldest maintained version
     $ cd projects/symfony-docs/
     $ git fetch upstream
-    $ git checkout -b my_changes upstream/5.4
+    $ git checkout -b my_changes upstream/6.4
 
     # ... do your changes
 
@@ -258,8 +258,8 @@ into multiple branches, corresponding to the different versions of Symfony itsel
 The latest (e.g. ``5.x``) branch holds the documentation for the development branch of
 the code.
 
-Unless you're documenting a feature that was introduced after Symfony 5.4,
-your changes should always be based on the ``5.4`` branch. Documentation managers
+Unless you're documenting a feature that was introduced after Symfony 6.4,
+your changes should always be based on the ``6.4`` branch. Documentation managers
 will use the necessary Git-magic to also apply your changes to all the active
 branches of the documentation.
 
diff --git a/contributing/map.rst.inc b/contributing/map.rst.inc
index 92bc1e2e142..acbb24bb9b0 100644
--- a/contributing/map.rst.inc
+++ b/contributing/map.rst.inc
@@ -1,3 +1,5 @@
+* :doc:`The Core Team </contributing/core_team>`
+
 * **Code of Conduct**
 
   * :doc:`/contributing/code_of_conduct/code_of_conduct`
@@ -12,7 +14,6 @@
   * :doc:`Pull Requests </contributing/code/pull_requests>`
   * :doc:`Reviewing Issues and Pull Requests </contributing/community/reviews>`
   * :doc:`Maintenance </contributing/code/maintenance>`
-  * :doc:`The Core Team </contributing/code/core_team>`
   * :doc:`Security </contributing/code/security>`
   * :doc:`Tests </contributing/code/tests>`
   * :doc:`Backward Compatibility </contributing/code/bc>`
diff --git a/controller.rst b/controller.rst
index c11615d93aa..5b0b77b35b9 100644
--- a/controller.rst
+++ b/controller.rst
@@ -41,7 +41,7 @@ class::
 The controller is the ``number()`` method, which lives inside the
 controller class ``LuckyController``.
 
-This controller is pretty straightforward:
+This controller is quite simple:
 
 * *line 2*: Symfony takes advantage of PHP's namespace functionality to
   namespace the entire controller class.
@@ -363,6 +363,20 @@ attribute, arguments of your controller's action can be automatically fulfilled:
         // ...
     }
 
+The ``MapQueryParameter`` attribute supports the following argument types:
+
+* ``\BackedEnum``
+* ``array``
+* ``bool``
+* ``float``
+* ``int``
+* ``string``
+* Objects that extend :class:`Symfony\\Component\\Uid\\AbstractUid`
+
+.. versionadded:: 7.3
+
+    Support for ``AbstractUid`` objects was introduced in Symfony 7.3.
+
 ``#[MapQueryParameter]`` can take an optional argument called ``filter``. You can use the
 `Validate Filters`_ constants defined in PHP::
 
@@ -443,6 +457,26 @@ HTTP status to return if the validation fails::
 
 The default status code returned if the validation fails is 404.
 
+If you want to map your object to a nested array in your query using a specific key,
+set the ``key`` option in the ``#[MapQueryString]`` attribute::
+
+    use App\Model\SearchDto;
+    use Symfony\Component\HttpFoundation\Response;
+    use Symfony\Component\HttpKernel\Attribute\MapQueryString;
+
+    // ...
+
+    public function dashboard(
+        #[MapQueryString(key: 'search')] SearchDto $searchDto
+    ): Response
+    {
+        // ...
+    }
+
+.. versionadded:: 7.3
+
+    The ``key`` option of ``#[MapQueryString]`` was introduced in Symfony 7.3.
+
 If you need a valid DTO even when the request query string is empty, set a
 default value for your controller arguments::
 
diff --git a/controller/error_pages.rst b/controller/error_pages.rst
index fc36b88779a..06087837437 100644
--- a/controller/error_pages.rst
+++ b/controller/error_pages.rst
@@ -154,7 +154,8 @@ automatically when installing ``symfony/framework-bundle``):
         # config/routes/framework.yaml
         when@dev:
             _errors:
-                resource: '@FrameworkBundle/Resources/config/routing/errors.xml'
+                resource: '@FrameworkBundle/Resources/config/routing/errors.php'
+                type:     php
                 prefix:   /_error
 
     .. code-block:: xml
@@ -167,7 +168,7 @@ automatically when installing ``symfony/framework-bundle``):
                 https://symfony.com/schema/routing/routing-1.0.xsd">
 
             <when env="dev">
-                <import resource="@FrameworkBundle/Resources/config/routing/errors.xml" prefix="/_error"/>
+                <import resource="@FrameworkBundle/Resources/config/routing/errors.php" type="php" prefix="/_error"/>
             </when>
         </routes>
 
@@ -178,7 +179,7 @@ automatically when installing ``symfony/framework-bundle``):
 
         return function (RoutingConfigurator $routes): void {
             if ('dev' === $routes->env()) {
-                $routes->import('@FrameworkBundle/Resources/config/routing/errors.xml')
+                $routes->import('@FrameworkBundle/Resources/config/routing/errors.php', 'php')
                     ->prefix('/_error')
                 ;
             }
@@ -191,6 +192,11 @@ need to replace ``http://localhost/`` by the host used in your local setup):
 * ``http://localhost/_error/{statusCode}`` for HTML
 * ``http://localhost/_error/{statusCode}.{format}`` for any other format
 
+.. versionadded:: 7.3
+
+    The ``errors.php`` file was introduced in Symfony 7.3.
+    Previously, you had to import ``errors.xml``
+
 .. _overriding-non-html-error-output:
 
 Overriding Error output for non-HTML formats
@@ -336,3 +342,50 @@ time and again, you can have just one (or several) listeners deal with them.
     your application (like :class:`Symfony\\Component\\Security\\Core\\Exception\\AccessDeniedException`)
     and takes measures like redirecting the user to the login page, logging them
     out and other things.
+
+Dumping Error Pages as Static HTML Files
+----------------------------------------
+
+.. versionadded:: 7.3
+
+    The feature to dump error pages into static HTML files was introduced in Symfony 7.3.
+
+If an error occurs before reaching your Symfony application, web servers display
+their own default error pages instead of your custom ones. Dumping your application's
+error pages to static HTML ensures users always see your defined pages and improves
+performance by allowing the server to deliver errors instantly without calling
+your application.
+
+Symfony provides the following command to turn your error pages into static HTML files:
+
+.. code-block:: terminal
+
+    # the first argument is the path where the HTML files are stored
+    $ APP_ENV=prod php bin/console error:dump var/cache/prod/error_pages/
+
+    # by default, it generates the pages of all 4xx and 5xx errors, but you can
+    # pass a list of HTTP status codes to only generate those
+    $ APP_ENV=prod php bin/console error:dump var/cache/prod/error_pages/ 401 403 404 500
+
+You must also configure your web server to use these generated pages. For example,
+if you use Nginx:
+
+.. code-block:: nginx
+
+    # /etc/nginx/conf.d/example.com.conf
+    server {
+        # Existing server configuration
+        # ...
+
+        # Serve static error pages
+        error_page 400 /error_pages/400.html;
+        error_page 401 /error_pages/401.html;
+        # ...
+        error_page 510 /error_pages/510.html;
+        error_page 511 /error_pages/511.html;
+
+        location ^~ /error_pages/ {
+            root /path/to/your/symfony/var/cache/error_pages;
+            internal; # prevent direct URL access
+        }
+    }
diff --git a/controller/service.rst b/controller/service.rst
index 88af093ff29..cf83e066a19 100644
--- a/controller/service.rst
+++ b/controller/service.rst
@@ -7,11 +7,65 @@ and your controllers extend the `AbstractController`_ class, they *are* automati
 registered as services. This means you can use dependency injection like any
 other normal service.
 
-If your controllers don't extend the `AbstractController`_ class, you must
-explicitly mark your controller services as ``public``. Alternatively, you can
-apply the ``controller.service_arguments`` tag to your controller services. This
-will make the tagged services ``public`` and will allow you to inject services
-in method parameters:
+If you prefer to not extend the ``AbstractController`` class, you can register
+your controllers as services in several ways:
+
+#. Using the ``#[Route]`` attribute;
+#. Using the ``#[AsController]`` attribute;
+#. Using the ``controller.service_arguments`` service tag.
+
+Using the ``#[Route]`` Attribute
+--------------------------------
+
+When using :ref:`the #[Route] attribute <routing-route-attributes>` to define
+routes on any PHP class, Symfony treats that class as a controller. It registers
+it as a public, non-lazy service and enables service argument injection in all
+its methods.
+
+This is the simplest and recommended way to register controllers as services
+when not extending the base controller class.
+
+.. versionadded:: 7.3
+
+    The feature to register controllers as services when using the ``#[Route]``
+    attribute was introduced in Symfony 7.3.
+
+Using the ``#[AsController]`` Attribute
+---------------------------------------
+
+If you prefer, you can use the ``#[AsController]`` PHP attribute to automatically
+apply the ``controller.service_arguments`` tag to your controller services::
+
+    // src/Controller/HelloController.php
+    namespace App\Controller;
+
+    use Symfony\Component\HttpFoundation\Response;
+    use Symfony\Component\HttpKernel\Attribute\AsController;
+    use Symfony\Component\Routing\Attribute\Route;
+
+    #[AsController]
+    class HelloController
+    {
+        #[Route('/hello', name: 'hello', methods: ['GET'])]
+        public function index(): Response
+        {
+            // ...
+        }
+    }
+
+.. tip::
+
+    When using the ``#[Route]`` attribute, Symfony already registers the controller
+    class as a service, so using the ``#[AsController]`` attribute is redundant.
+
+Using the ``controller.service_arguments`` Service Tag
+------------------------------------------------------
+
+If your controllers don't extend the `AbstractController`_ class and you don't
+use the ``#[AsController]`` or ``#[Route]`` attributes, you must register the
+controllers as public services manually and apply the ``controller.service_arguments``
+:doc:`service tag </service_container/tags>` to enable service injection in
+controller actions:
 
 .. configuration-block::
 
@@ -58,26 +112,6 @@ in method parameters:
             calls:
                 - [setContainer, ['@abstract_controller.locator']]
 
-If you prefer, you can use the ``#[AsController]`` PHP attribute to automatically
-apply the ``controller.service_arguments`` tag to your controller services::
-
-    // src/Controller/HelloController.php
-    namespace App\Controller;
-
-    use Symfony\Component\HttpFoundation\Response;
-    use Symfony\Component\HttpKernel\Attribute\AsController;
-    use Symfony\Component\Routing\Attribute\Route;
-
-    #[AsController]
-    class HelloController
-    {
-        #[Route('/hello', name: 'hello', methods: ['GET'])]
-        public function index(): Response
-        {
-            // ...
-        }
-    }
-
 Registering your controller as a service is the first step, but you also need to
 update your routing config to reference the service properly, so that Symfony
 knows to use it.
diff --git a/controller/upload_file.rst b/controller/upload_file.rst
index b3dc2d6ffd0..793cd26dd65 100644
--- a/controller/upload_file.rst
+++ b/controller/upload_file.rst
@@ -75,14 +75,11 @@ so Symfony doesn't try to get/set its value from the related entity::
                     // unmapped fields can't define their validation using attributes
                     // in the associated entity, so you can use the PHP constraint classes
                     'constraints' => [
-                        new File([
-                            'maxSize' => '1024k',
-                            'mimeTypes' => [
-                                'application/pdf',
-                                'application/x-pdf',
-                            ],
-                            'mimeTypesMessage' => 'Please upload a valid PDF document',
-                        ])
+                        new File(
+                            maxSize: '1024k',
+                            extensions: ['pdf'],
+                            extensionsMessage: 'Please upload a valid PDF document',
+                        )
                     ],
                 ])
                 // ...
diff --git a/controller/value_resolver.rst b/controller/value_resolver.rst
index dbbea7bcc87..835edcfbff9 100644
--- a/controller/value_resolver.rst
+++ b/controller/value_resolver.rst
@@ -281,7 +281,7 @@ this argument) or an array with the resolved value(s). Usually arguments are
 resolved as a single value, but variadic arguments require resolving multiple
 values. That's why you must always return an array, even for single values::
 
-    // src/ValueResolver/IdentifierValueResolver.php
+    // src/ValueResolver/BookingIdValueResolver.php
     namespace App\ValueResolver;
 
     use App\IdentifierInterface;
@@ -333,6 +333,20 @@ but you can set it yourself to change its ``priority`` or ``name`` attributes.
 
 .. configuration-block::
 
+    .. code-block:: php-attributes
+
+        // src/ValueResolver/BookingIdValueResolver.php
+        namespace App\ValueResolver;
+
+        use Symfony\Component\DependencyInjection\Attribute\AsTaggedItem;
+        use Symfony\Component\HttpKernel\Controller\ValueResolverInterface;
+
+         #[AsTaggedItem(index: 'booking_id', priority: 150)]
+        class BookingIdValueResolver implements ValueResolverInterface
+        {
+            // ...
+        }
+
     .. code-block:: yaml
 
         # config/services.yaml
@@ -396,7 +410,7 @@ command to see which argument resolvers are present and in which order they run:
 
 .. code-block:: terminal
 
-    $ php bin/console debug:container debug.argument_resolver.inner --show-arguments
+    $ php bin/console debug:container debug.argument_resolver.inner
 
 You can also configure the name passed to the ``ValueResolver`` attribute to target
 your resolver. Otherwise it will default to the service's id.
@@ -414,7 +428,7 @@ As an alternative, you can add the
 :class:`Symfony\\Component\\HttpKernel\\Attribute\\AsTargetedValueResolver` attribute
 to your resolver and pass your custom name as its first argument::
 
-    // src/ValueResolver/IdentifierValueResolver.php
+    // src/ValueResolver/BookingIdValueResolver.php
     namespace App\ValueResolver;
 
     use Symfony\Component\HttpKernel\Attribute\AsTargetedValueResolver;
diff --git a/create_framework/dependency_injection.rst b/create_framework/dependency_injection.rst
index de3c4e11e4e..aa377a77b5a 100644
--- a/create_framework/dependency_injection.rst
+++ b/create_framework/dependency_injection.rst
@@ -10,7 +10,6 @@ to it::
     namespace Simplex;
 
     use Symfony\Component\EventDispatcher\EventDispatcher;
-    use Symfony\Component\HttpFoundation;
     use Symfony\Component\HttpFoundation\RequestStack;
     use Symfony\Component\HttpKernel;
     use Symfony\Component\Routing;
@@ -199,6 +198,7 @@ Now, here is how you can register a custom listener in the front controller::
 
     // ...
     use Simplex\StringResponseListener;
+    use Symfony\Component\DependencyInjection\Reference;
 
     $container->register('listener.string_response', StringResponseListener::class);
     $container->getDefinition('dispatcher')
@@ -227,16 +227,16 @@ object::
     $container->setParameter('charset', 'UTF-8');
 
 Instead of relying on the convention that the routes are defined by the
-``$routes`` variables, let's use a parameter again::
+``$routes`` variables, let's use a reference::
 
     // ...
     $container->register('matcher', Routing\Matcher\UrlMatcher::class)
-        ->setArguments(['%routes%', new Reference('context')])
+        ->setArguments([new Reference('routes'), new Reference('context')])
     ;
 
 And the related change in the front controller::
 
-    $container->setParameter('routes', include __DIR__.'/../src/app.php');
+    $container->set('routes', $routes);
 
 We have barely scratched the surface of what you can do with the
 container: from class names as parameters, to overriding existing object
diff --git a/create_framework/event_dispatcher.rst b/create_framework/event_dispatcher.rst
index 650e4c7554e..9a3a48942ac 100644
--- a/create_framework/event_dispatcher.rst
+++ b/create_framework/event_dispatcher.rst
@@ -121,7 +121,7 @@ the registration of a listener for the ``response`` event::
         $response = $event->getResponse();
 
         if ($response->isRedirection()
-            || ($response->headers->has('Content-Type') && false === strpos($response->headers->get('Content-Type'), 'html'))
+            || ($response->headers->has('Content-Type') && !str_contains($response->headers->get('Content-Type'), 'html'))
             || 'html' !== $event->getRequest()->getRequestFormat()
         ) {
             return;
@@ -200,7 +200,7 @@ Let's refactor the code a bit by moving the Google listener to its own class::
             $response = $event->getResponse();
 
             if ($response->isRedirection()
-                || ($response->headers->has('Content-Type') && false === strpos($response->headers->get('Content-Type'), 'html'))
+                || ($response->headers->has('Content-Type') && !str_contains($response->headers->get('Content-Type'), 'html'))
                 || 'html' !== $event->getRequest()->getRequestFormat()
             ) {
                 return;
diff --git a/create_framework/front_controller.rst b/create_framework/front_controller.rst
index fded71a7b1c..cc440dd8910 100644
--- a/create_framework/front_controller.rst
+++ b/create_framework/front_controller.rst
@@ -154,7 +154,7 @@ Now, configure your web server root directory to point to ``web/`` and all
 other files will no longer be accessible from the client.
 
 To test your changes in a browser (``http://localhost:4321/hello?name=Fabien``),
-run the :doc:`Symfony Local Web Server </setup/symfony_server>`:
+run the :ref:`Symfony local web server <symfony-cli-server>`:
 
 .. code-block:: terminal
 
diff --git a/create_framework/http_foundation.rst b/create_framework/http_foundation.rst
index 219119164b4..71146b1785c 100644
--- a/create_framework/http_foundation.rst
+++ b/create_framework/http_foundation.rst
@@ -189,7 +189,7 @@ fingertips thanks to a nice and simple API::
     // retrieves a COOKIE value
     $request->cookies->get('PHPSESSID');
 
-    // retrieves a HTTP request header, with normalized, lowercase keys
+    // retrieves an HTTP request header, with normalized, lowercase keys
     $request->headers->get('host');
     $request->headers->get('content-type');
 
diff --git a/create_framework/http_kernel_controller_resolver.rst b/create_framework/http_kernel_controller_resolver.rst
index 1c2857c9ed9..6c7e469da27 100644
--- a/create_framework/http_kernel_controller_resolver.rst
+++ b/create_framework/http_kernel_controller_resolver.rst
@@ -165,15 +165,6 @@ Let's conclude with the new version of our framework::
     use Symfony\Component\HttpKernel;
     use Symfony\Component\Routing;
 
-    function render_template(Request $request): Response
-    {
-        extract($request->attributes->all(), EXTR_SKIP);
-        ob_start();
-        include sprintf(__DIR__.'/../src/pages/%s.php', $_route);
-
-        return new Response(ob_get_clean());
-    }
-
     $request = Request::createFromGlobals();
     $routes = include __DIR__.'/../src/app.php';
 
diff --git a/create_framework/http_kernel_httpkernel_class.rst b/create_framework/http_kernel_httpkernel_class.rst
index ecf9d4c7879..158de638f8a 100644
--- a/create_framework/http_kernel_httpkernel_class.rst
+++ b/create_framework/http_kernel_httpkernel_class.rst
@@ -39,7 +39,6 @@ And the new front controller::
     use Symfony\Component\EventDispatcher\EventDispatcher;
     use Symfony\Component\HttpFoundation\Request;
     use Symfony\Component\HttpFoundation\RequestStack;
-    use Symfony\Component\HttpFoundation\Response;
     use Symfony\Component\HttpKernel;
     use Symfony\Component\Routing;
 
diff --git a/create_framework/introduction.rst b/create_framework/introduction.rst
index 7a1e6b2ad50..420537a8088 100644
--- a/create_framework/introduction.rst
+++ b/create_framework/introduction.rst
@@ -101,7 +101,7 @@ start with the simplest web application we can think of in PHP::
 
     printf('Hello %s', $name);
 
-You can use the :doc:`Symfony Local Web Server </setup/symfony_server>` to test
+You can use the :ref:`Symfony local web server <symfony-cli-server>` to test
 this great application in a browser
 (``http://localhost:8000/index.php?name=Fabien``):
 
diff --git a/create_framework/unit_testing.rst b/create_framework/unit_testing.rst
index 32c97a03846..9c179cd3152 100644
--- a/create_framework/unit_testing.rst
+++ b/create_framework/unit_testing.rst
@@ -12,7 +12,7 @@ using `PHPUnit`_. At first, install PHPUnit as a development dependency:
 
 .. code-block:: terminal
 
-    $ composer require --dev phpunit/phpunit:^9.6
+    $ composer require --dev phpunit/phpunit:^11.0
 
 Then, create a PHPUnit configuration file in ``example.com/phpunit.xml.dist``:
 
@@ -21,16 +21,16 @@ Then, create a PHPUnit configuration file in ``example.com/phpunit.xml.dist``:
     <?xml version="1.0" encoding="UTF-8"?>
     <phpunit
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-        xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.6/phpunit.xsd"
+        xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/11.0/phpunit.xsd"
         backupGlobals="false"
         colors="true"
         bootstrap="vendor/autoload.php"
     >
-        <coverage processUncoveredFiles="true">
+        <source>
             <include>
                 <directory suffix=".php">./src</directory>
             </include>
-        </coverage>
+        </source>
 
         <testsuites>
             <testsuite name="Test Suite">
@@ -103,12 +103,12 @@ We are now ready to write our first test::
             $matcher
                 ->expects($this->once())
                 ->method('match')
-                ->will($this->throwException($exception))
+                ->willThrowException($exception)
             ;
             $matcher
                 ->expects($this->once())
                 ->method('getContext')
-                ->will($this->returnValue($this->createMock(Routing\RequestContext::class)))
+                ->willReturn($this->createMock(Routing\RequestContext::class))
             ;
             $controllerResolver = $this->createMock(ControllerResolverInterface::class);
             $argumentResolver = $this->createMock(ArgumentResolverInterface::class);
@@ -161,16 +161,16 @@ Response::
         $matcher
             ->expects($this->once())
             ->method('match')
-            ->will($this->returnValue([
+            ->willReturn([
                 '_route' => 'is_leap_year/{year}',
                 'year' => '2000',
                 '_controller' => [new LeapYearController(), 'index'],
-            ]))
+            ])
         ;
         $matcher
             ->expects($this->once())
             ->method('getContext')
-            ->will($this->returnValue($this->createMock(Routing\RequestContext::class)))
+            ->willReturn($this->createMock(Routing\RequestContext::class))
         ;
         $controllerResolver = new ControllerResolver();
         $argumentResolver = new ArgumentResolver();
@@ -194,7 +194,7 @@ coverage feature (you need to enable `XDebug`_ first):
 
     $ ./vendor/bin/phpunit --coverage-html=cov/
 
-Open ``example.com/cov/src/Simplex/Framework.php.html`` in a browser and check
+Open ``example.com/cov/Simplex/Framework.php.html`` in a browser and check
 that all the lines for the Framework class are green (it means that they have
 been visited when the tests were executed).
 
@@ -212,6 +212,6 @@ Symfony code.
 Now that we are confident (again) about the code we have written, we can
 safely think about the next batch of features we want to add to our framework.
 
-.. _`PHPUnit`: https://docs.phpunit.de/en/9.6/
-.. _`test doubles`: https://docs.phpunit.de/en/9.6/test-doubles.html
+.. _`PHPUnit`: https://docs.phpunit.de/en/11.0/
+.. _`test doubles`: https://docs.phpunit.de/en/11.0/test-doubles.html
 .. _`XDebug`: https://xdebug.org/
diff --git a/deployment.rst b/deployment.rst
index 864ebc7a963..b9d985920b5 100644
--- a/deployment.rst
+++ b/deployment.rst
@@ -5,7 +5,7 @@ How to Deploy a Symfony Application
 
 Deploying a Symfony application can be a complex and varied task depending on
 the setup and the requirements of your application. This article is not a
-step-by-step guide, but is a general list of the most common requirements and
+step-by-step guide, but rather a general list of the most common requirements and
 ideas for deployment.
 
 .. _symfony2-deployment-basics:
@@ -134,17 +134,18 @@ B) Configure your Environment Variables
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 Most Symfony applications read their configuration from environment variables.
-While developing locally, you'll usually store these in ``.env`` and ``.env.local``
-(for local overrides). On production, you have two options:
+While developing locally, you'll usually store these in :ref:`.env files <configuration-env-var-in-dev>`.
+On production, you have two options:
 
 1. Create "real" environment variables. How you set environment variables, depends
    on your setup: they can be set at the command line, in your Nginx configuration,
    or via other methods provided by your hosting service;
 
-2. Or, create a ``.env.local`` file like your local development.
+2. Or, create a ``.env.prod.local`` file that contains values specific to your
+   production environment.
 
-There is no significant advantage to either of the two options: use whatever is
-most natural in your hosting environment.
+There is no significant advantage to either option: use whichever is most natural
+for your hosting environment.
 
 .. tip::
 
diff --git a/deployment/proxies.rst b/deployment/proxies.rst
index 81fd474951b..4dad6f95fb1 100644
--- a/deployment/proxies.rst
+++ b/deployment/proxies.rst
@@ -22,7 +22,11 @@ Solution: ``setTrustedProxies()``
 ---------------------------------
 
 To fix this, you need to tell Symfony which reverse proxy IP addresses to trust
-and what headers your reverse proxy uses to send information:
+and what headers your reverse proxy uses to send information.
+
+You can do that by setting the ``SYMFONY_TRUSTED_PROXIES`` and ``SYMFONY_TRUSTED_HEADERS``
+environment variables on your machine. Alternatively, you can configure them
+using the following configuration options:
 
 .. configuration-block::
 
@@ -93,6 +97,11 @@ and what headers your reverse proxy uses to send information:
     ``private_ranges`` as a shortcut for private IP address ranges for the
     ``trusted_proxies`` option was introduced in Symfony 7.1.
 
+.. versionadded:: 7.2
+
+    Support for the ``SYMFONY_TRUSTED_PROXIES`` and ``SYMFONY_TRUSTED_HEADERS``
+    environment variables was introduced in Symfony 7.2.
+
 .. danger::
 
     Enabling the ``Request::HEADER_X_FORWARDED_HOST`` option exposes the
@@ -143,9 +152,17 @@ In this case, you'll need to - *very carefully* - trust *all* proxies.
        framework:
            # ...
            # trust *all* requests (the 'REMOTE_ADDR' string is replaced at
-           # run time by $_SERVER['REMOTE_ADDR'])
+           # runtime by $_SERVER['REMOTE_ADDR'])
            trusted_proxies: '127.0.0.1,REMOTE_ADDR'
 
+           # you can also use the 'PRIVATE_SUBNETS' string, which is replaced at
+           # runtime by the IpUtils::PRIVATE_SUBNETS constant
+           # trusted_proxies: '127.0.0.1,PRIVATE_SUBNETS'
+
+.. versionadded:: 7.2
+
+    The support for the ``'PRIVATE_SUBNETS'`` string was introduced in Symfony 7.2.
+
 That's it! It's critical that you prevent traffic from all non-trusted sources.
 If you allow outside traffic, they could "spoof" their true IP address and
 other information.
diff --git a/doctrine.rst b/doctrine.rst
index 171f8a3348a..6a1438322fa 100644
--- a/doctrine.rst
+++ b/doctrine.rst
@@ -646,8 +646,22 @@ automatically! You can simplify the controller to::
         }
     }
 
-That's it! The bundle uses the ``{id}`` from the route to query for the ``Product``
-by the ``id`` column. If it's not found, a 404 page is generated.
+That's it! The attribute uses the ``{id}`` from the route to query for the ``Product``
+by the ``id`` column. If it's not found, a 404 error is thrown.
+
+You can change this behavior by making the controller argument optional. In that
+case, no 404 is thrown automatically and you're free to handle the missing entity
+yourself::
+
+    #[Route('/product/{id}')]
+    public function show(?Product $product): Response
+    {
+        if (null === $product) {
+            // run your own logic to return a custom response
+        }
+
+        // ...
+    }
 
 .. tip::
 
@@ -680,7 +694,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 +708,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 look up
+the corresponding ``Product`` object from the database using the slug.
 
-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
+using the ``MapEntity`` attribute. You can even control the behavior of the
+``EntityValueResolver`` by using the `MapEntity options`_ ::
 
     // src/Controller/ProductController.php
     namespace App\Controller;
@@ -781,6 +798,32 @@ variable. Let's say you want the first or the last comment of a product dependin
     ): Response {
     }
 
+.. _doctrine-entity-value-resolver-resolve-target-entities:
+
+Fetch via Interfaces
+~~~~~~~~~~~~~~~~~~~~
+
+Suppose your ``Product`` class implements an interface called ``ProductInterface``.
+If you want to decouple your controllers from the concrete entity implementation,
+you can reference the entity by its interface instead.
+
+To enable this, first configure the
+:doc:`resolve_target_entities option </doctrine/resolve_target_entity>`.
+Then, your controller can type-hint the interface, and the entity will be
+resolved automatically::
+
+    public function show(
+        #[MapEntity]
+        ProductInterface $product
+    ): Response {
+        // ...
+    }
+
+.. versionadded:: 7.3
+
+    Support for target entity resolution in the ``EntityValueResolver`` was
+    introduced Symfony 7.3
+
 MapEntity Options
 ~~~~~~~~~~~~~~~~~
 
@@ -812,18 +855,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/doctrine/associations.rst b/doctrine/associations.rst
index 8dd9aa7f36b..bb670eeee52 100644
--- a/doctrine/associations.rst
+++ b/doctrine/associations.rst
@@ -447,7 +447,7 @@ by adding JOINs.
         $category = $product->getCategory();
 
         // prints "Proxies\AppEntityCategoryProxy"
-        dump(get_class($category));
+        dump($category::class);
         die();
 
     This proxy object extends the true ``Category`` object, and looks and
@@ -501,7 +501,7 @@ following method to the ``ProductRepository`` class::
         }
     }
 
-This will *still* return an array of ``Product`` objects. But now, when you call
+This will *still* return a ``Product`` object. But now, when you call
 ``$product->getCategory()`` and use that data, no second query is made.
 
 Now, you can use this method in your controller to query for a ``Product``
diff --git a/doctrine/events.rst b/doctrine/events.rst
index dcd97126b7c..accf424083a 100644
--- a/doctrine/events.rst
+++ b/doctrine/events.rst
@@ -3,7 +3,7 @@ Doctrine Events
 
 `Doctrine`_, the set of PHP libraries used by Symfony to work with databases,
 provides a lightweight event system to update entities during the application
-execution. These events, called `lifecycle events`_, allow to perform tasks such
+execution. These events, called `lifecycle events`_, allow performing tasks such
 as *"update the createdAt property automatically right before persisting entities
 of this type"*.
 
@@ -24,7 +24,7 @@ There are different ways to listen to these Doctrine events:
   methods are called for all entities, not only those of a certain type. They are
   ideal to **share event logic between entities**.
 
-The performance of each type of listener depends on how many entities applies to:
+The performance of each type of listener depends on how many entities it applies to:
 lifecycle callbacks are faster than entity listeners, which in turn are faster
 than lifecycle listeners.
 
@@ -304,23 +304,6 @@ listener in the Symfony application by creating a new service for it and
 
 .. configuration-block::
 
-    .. code-block:: php-attributes
-
-        // src/EventListener/SearchIndexer.php
-        namespace App\EventListener;
-
-        use Doctrine\Bundle\DoctrineBundle\Attribute\AsDoctrineListener;
-        use Doctrine\ORM\Event\PostPersistEventArgs;
-
-        #[AsDoctrineListener('postPersist'/*, 500, 'default'*/)]
-        class SearchIndexer
-        {
-            public function postPersist(PostPersistEventArgs $event): void
-            {
-                // ...
-            }
-        }
-
     .. code-block:: yaml
 
         # config/services.yaml
diff --git a/doctrine/resolve_target_entity.rst b/doctrine/resolve_target_entity.rst
index 5ae6475a957..1495f475628 100644
--- a/doctrine/resolve_target_entity.rst
+++ b/doctrine/resolve_target_entity.rst
@@ -1,39 +1,45 @@
-How to Define Relationships with Abstract Classes and Interfaces
-================================================================
+Referencing Entities with Abstract Classes and Interfaces
+=========================================================
 
-One of the goals of bundles is to create discrete bundles of functionality
-that do not have many (if any) dependencies, allowing you to use that
-functionality in other applications without including unnecessary items.
+In applications where functionality is organized in layers or modules with
+minimal concrete dependencies, such as monoliths split into multiple modules,
+it can be challenging to avoid tight coupling between entities.
 
-Doctrine 2.2 includes a new utility called the ``ResolveTargetEntityListener``,
-that functions by intercepting certain calls inside Doctrine and rewriting
-``targetEntity`` parameters in your metadata mapping at runtime. It means that
-in your bundle you are able to use an interface or abstract class in your
-mappings and expect correct mapping to a concrete entity at runtime.
+Doctrine provides a utility called the ``ResolveTargetEntityListener`` to solve
+this issue. It works by intercepting certain calls within Doctrine and rewriting
+``targetEntity`` parameters in your metadata mapping at runtime. This allows you
+to reference an interface or abstract class in your mappings and have it resolved
+to a concrete entity at runtime.
 
-This functionality allows you to define relationships between different entities
-without making them hard dependencies.
+This makes it possible to define relationships between entities without
+creating hard dependencies. This feature also works with the ``EntityValueResolver``
+:ref:`as explained in the main Doctrine article <doctrine-entity-value-resolver-resolve-target-entities>`.
+
+.. versionadded:: 7.3
+
+    Support for target entity resolution in the ``EntityValueResolver`` was
+    introduced Symfony 7.3
 
 Background
 ----------
 
-Suppose you have an InvoiceBundle which provides invoicing functionality
-and a CustomerBundle that contains customer management tools. You want
-to keep these separated, because they can be used in other systems without
-each other, but for your application you want to use them together.
+Suppose you have an application with two modules: an Invoice module that
+provides invoicing functionality, and a Customer module that handles customer
+management. You want to keep these modules decoupled, so that neither is aware
+of the other's implementation details.
 
-In this case, you have an ``Invoice`` entity with a relationship to a
-non-existent object, an ``InvoiceSubjectInterface``. The goal is to get
-the ``ResolveTargetEntityListener`` to replace any mention of the interface
-with a real object that implements that interface.
+In this case, your ``Invoice`` entity has a relationship to the interface
+``InvoiceSubjectInterface``. Since interfaces are not valid Doctrine entities,
+the goal is to use the ``ResolveTargetEntityListener`` to replace all
+references to this interface with a concrete class that implements it.
 
 Set up
 ------
 
-This article uses the following two basic entities (which are incomplete for
-brevity) to explain how to set up and use the ``ResolveTargetEntityListener``.
+This article uses two basic (incomplete) entities to demonstrate how to set up
+and use the ``ResolveTargetEntityListener``.
 
-A Customer entity::
+A ``Customer`` entity::
 
     // src/Entity/Customer.php
     namespace App\Entity;
@@ -50,7 +56,7 @@ A Customer entity::
         // are already implemented in the BaseCustomer
     }
 
-An Invoice entity::
+An ``Invoice`` entity::
 
     // src/Entity/Invoice.php
     namespace App\Entity;
@@ -58,9 +64,6 @@ An Invoice entity::
     use App\Model\InvoiceSubjectInterface;
     use Doctrine\ORM\Mapping as ORM;
 
-    /**
-     * Represents an Invoice.
-     */
     #[ORM\Entity]
     #[ORM\Table(name: 'invoice')]
     class Invoice
@@ -69,7 +72,7 @@ An Invoice entity::
         protected InvoiceSubjectInterface $subject;
     }
 
-An InvoiceSubjectInterface::
+The interface representing the subject used in the invoice::
 
     // src/Model/InvoiceSubjectInterface.php
     namespace App\Model;
@@ -89,8 +92,8 @@ An InvoiceSubjectInterface::
         public function getName(): string;
     }
 
-Next, you need to configure the listener, which tells the DoctrineBundle
-about the replacement:
+Now configure the ``resolve_target_entities`` option to tell Doctrine
+how to replace the interface with the concrete class:
 
 .. configuration-block::
 
@@ -140,7 +143,6 @@ about the replacement:
 Final Thoughts
 --------------
 
-With the ``ResolveTargetEntityListener``, you are able to decouple your
-bundles, keeping them usable by themselves, but still being able to
-define relationships between different objects. By using this method,
-your bundles will end up being easier to maintain independently.
+Using ``ResolveTargetEntityListener`` allows you to decouple your modules
+while still defining relationships between their entities. This makes your
+codebase more modular and easier to maintain over time.
diff --git a/event_dispatcher.rst b/event_dispatcher.rst
index 27885af267b..ffa9e67aa0d 100644
--- a/event_dispatcher.rst
+++ b/event_dispatcher.rst
@@ -149,7 +149,7 @@ Defining Event Listeners with PHP Attributes
 
 An alternative way to define an event listener is to use the
 :class:`Symfony\\Component\\EventDispatcher\\Attribute\\AsEventListener`
-PHP attribute. This allows to configure the listener inside its class, without
+PHP attribute. This allows you to configure the listener inside its class, without
 having to add any configuration in external files::
 
     namespace App\EventListener;
@@ -246,14 +246,14 @@ methods could be called before or after the methods defined in other listeners
 and subscribers. To learn more about event subscribers, read :doc:`/components/event_dispatcher`.
 
 The following example shows an event subscriber that defines several methods which
-listen to the same ``kernel.exception`` event::
+listen to the same :ref:`kernel.exception event <component-http-kernel-kernel-exception>`
+via its ``ExceptionEvent`` class::
 
     // src/EventSubscriber/ExceptionSubscriber.php
     namespace App\EventSubscriber;
 
     use Symfony\Component\EventDispatcher\EventSubscriberInterface;
     use Symfony\Component\HttpKernel\Event\ExceptionEvent;
-    use Symfony\Component\HttpKernel\KernelEvents;
 
     class ExceptionSubscriber implements EventSubscriberInterface
     {
@@ -261,7 +261,7 @@ listen to the same ``kernel.exception`` event::
         {
             // return the subscribed events, their methods and priorities
             return [
-                KernelEvents::EXCEPTION => [
+                ExceptionEvent::class => [
                     ['processException', 10],
                     ['logException', 0],
                     ['notifyException', -10],
@@ -509,7 +509,7 @@ A ``kernel.controller`` (aka ``KernelEvents::CONTROLLER``) listener gets notifie
 on *every* request, right before the controller is executed. So, first, you need
 some way to identify if the controller that matches the request needs token validation.
 
-A clean and easy way is to create an empty interface and make the controllers
+A clean and simple way is to create an empty interface and make the controllers
 implement it::
 
     namespace App\Controller;
@@ -541,7 +541,7 @@ Creating an Event Subscriber
 
 Next, you'll need to create an event subscriber, which will hold the logic
 that you want to be executed before your controllers. If you're not familiar with
-event subscribers, you can learn more about them at :doc:`/event_dispatcher`::
+event subscribers, you can learn more about :ref:`how to use them <events-subscriber>`::
 
     // src/EventSubscriber/TokenSubscriber.php
     namespace App\EventSubscriber;
diff --git a/form/button_based_validation.rst b/form/button_based_validation.rst
deleted file mode 100644
index 47f2673b079..00000000000
--- a/form/button_based_validation.rst
+++ /dev/null
@@ -1,36 +0,0 @@
-How to Choose Validation Groups Based on the Clicked Button
-===========================================================
-
-When your form contains multiple submit buttons, you can change the validation
-group depending on which button is used to submit the form. For example,
-consider a form in a wizard that lets you advance to the next step or go back
-to the previous step. Also assume that when returning to the previous step,
-the data of the form should be saved, but not validated.
-
-First, we need to add the two buttons to the form::
-
-    $form = $this->createFormBuilder($task)
-        // ...
-        ->add('nextStep', SubmitType::class)
-        ->add('previousStep', SubmitType::class)
-        ->getForm();
-
-Then, we configure the button for returning to the previous step to run
-specific validation groups. In this example, we want it to suppress validation,
-so we set its ``validation_groups`` option to false::
-
-    $form = $this->createFormBuilder($task)
-        // ...
-        ->add('previousStep', SubmitType::class, [
-            'validation_groups' => false,
-        ])
-        ->getForm();
-
-Now the form will skip your validation constraints. It will still validate
-basic integrity constraints, such as checking whether an uploaded file was too
-large or whether you tried to submit text in a number field.
-
-.. seealso::
-
-    To see how to use a service to resolve ``validation_groups`` dynamically
-    read the :doc:`/form/validation_group_service_resolver` article.
diff --git a/form/data_based_validation.rst b/form/data_based_validation.rst
deleted file mode 100644
index b01bea10b16..00000000000
--- a/form/data_based_validation.rst
+++ /dev/null
@@ -1,72 +0,0 @@
-How to Choose Validation Groups Based on the Submitted Data
-===========================================================
-
-If you need some advanced logic to determine the validation groups (e.g.
-based on submitted data), you can set the ``validation_groups`` option
-to an array callback::
-
-    use App\Entity\Client;
-    use Symfony\Component\OptionsResolver\OptionsResolver;
-
-    // ...
-    public function configureOptions(OptionsResolver $resolver): void
-    {
-        $resolver->setDefaults([
-            'validation_groups' => [
-                Client::class,
-                'determineValidationGroups',
-            ],
-        ]);
-    }
-
-This will call the static method ``determineValidationGroups()`` on the
-``Client`` class after the form is submitted, but before validation is
-invoked. The Form object is passed as an argument to that method (see next
-example).  You can also define whole logic inline by using a ``Closure``::
-
-    use App\Entity\Client;
-    use Symfony\Component\Form\FormInterface;
-    use Symfony\Component\OptionsResolver\OptionsResolver;
-
-    // ...
-    public function configureOptions(OptionsResolver $resolver): void
-    {
-        $resolver->setDefaults([
-            'validation_groups' => function (FormInterface $form): array {
-                $data = $form->getData();
-
-                if (Client::TYPE_PERSON == $data->getType()) {
-                    return ['person'];
-                }
-
-                return ['company'];
-            },
-        ]);
-    }
-
-Using the ``validation_groups`` option overrides the default validation
-group which is being used. If you want to validate the default constraints
-of the entity as well you have to adjust the option as follows::
-
-    use App\Entity\Client;
-    use Symfony\Component\Form\FormInterface;
-    use Symfony\Component\OptionsResolver\OptionsResolver;
-
-    // ...
-    public function configureOptions(OptionsResolver $resolver): void
-    {
-        $resolver->setDefaults([
-            'validation_groups' => function (FormInterface $form): array {
-                $data = $form->getData();
-
-                if (Client::TYPE_PERSON == $data->getType()) {
-                    return ['Default', 'person'];
-                }
-
-                return ['Default', 'company'];
-            },
-        ]);
-    }
-
-You can find more information about how the validation groups and the default constraints
-work in the article about :doc:`validation groups </validation/groups>`.
diff --git a/form/dynamic_form_modification.rst b/form/dynamic_form_modification.rst
index 09be80ebb5a..a1f32c7c16c 100644
--- a/form/dynamic_form_modification.rst
+++ b/form/dynamic_form_modification.rst
@@ -138,8 +138,8 @@ For better reusability or if there is some heavy logic in your event listener,
 you can also move the logic for creating the ``name`` field to an
 :ref:`event subscriber <event_dispatcher-using-event-subscribers>`::
 
-    // src/Form/EventListener/AddNameFieldSubscriber.php
-    namespace App\Form\EventListener;
+    // src/Form/EventSubscriber/AddNameFieldSubscriber.php
+    namespace App\Form\EventSubscriber;
 
     use Symfony\Component\EventDispatcher\EventSubscriberInterface;
     use Symfony\Component\Form\Extension\Core\Type\TextType;
@@ -172,7 +172,7 @@ Great! Now use that in your form class::
     namespace App\Form\Type;
 
     // ...
-    use App\Form\EventListener\AddNameFieldSubscriber;
+    use App\Form\EventSubscriber\AddNameFieldSubscriber;
 
     class ProductType extends AbstractType
     {
diff --git a/form/embedded.rst b/form/embedded.rst
index dd163235f03..9e20164c3a4 100644
--- a/form/embedded.rst
+++ b/form/embedded.rst
@@ -87,6 +87,7 @@ inside the task form itself. To accomplish this, add a ``category`` field
 to the ``TaskType`` object whose type is an instance of the new ``CategoryType``
 class::
 
+    // src/Form/TaskType.php
     use App\Form\CategoryType;
     use Symfony\Component\Form\FormBuilderInterface;
 
diff --git a/form/form_collections.rst b/form/form_collections.rst
index 2a0ba99657f..3c8a2050690 100644
--- a/form/form_collections.rst
+++ b/form/form_collections.rst
@@ -699,13 +699,12 @@ the relationship between the removed ``Tag`` and ``Task`` object.
 
     The Symfony community has created some JavaScript packages that provide the
     functionality needed to add, edit and delete elements of the collection.
-    Check out the `@a2lix/symfony-collection`_ package for modern browsers and
-    the `symfony-collection`_ package based on jQuery for the rest of browsers.
+    Check out the `@a2lix/symfony-collection`_ or search on GitHub for other
+    recent packages.
 
 .. _`Owning Side and Inverse Side`: https://www.doctrine-project.org/projects/doctrine-orm/en/current/reference/unitofwork-associations.html
 .. _`JSFiddle`: https://jsfiddle.net/ey8ozh6n/
 .. _`@a2lix/symfony-collection`: https://github.com/a2lix/symfony-collection
-.. _`symfony-collection`: https://github.com/ninsuo/symfony-collection
 .. _`ArrayCollection`: https://www.doctrine-project.org/projects/doctrine-collections/en/1.6/index.html
 .. _`Symfony UX Demo of Form Collections`: https://ux.symfony.com/live-component/demos/form-collection-type
 .. _`Stimulus`: https://symfony.com/doc/current/frontend/encore/simple-example.html#stimulus-symfony-ux
diff --git a/form/form_customization.rst b/form/form_customization.rst
index 1c23601a883..dc09aefe77d 100644
--- a/form/form_customization.rst
+++ b/form/form_customization.rst
@@ -103,6 +103,7 @@ That's why Symfony provides other Twig form helpers that render the value of
 each form field part without adding any HTML around it:
 
 * ``field_name()``
+* ``field_id()``
 * ``field_value()``
 * ``field_label()``
 * ``field_help()``
@@ -116,6 +117,7 @@ fields, so you no longer have to deal with form themes:
 
     <input
         name="{{ field_name(form.username) }}"
+        id="{{ field_id(form.username) }}"
         value="{{ field_value(form.username) }}"
         placeholder="{{ field_label(form.username) }}"
         class="form-control"
@@ -129,6 +131,10 @@ fields, so you no longer have to deal with form themes:
         {% endfor %}
     </select>
 
+.. versionadded:: 7.3
+
+    The ``field_id()`` helper was introduced in Symfony 7.3.
+
 Form Rendering Variables
 ------------------------
 
diff --git a/form/inherit_data_option.rst b/form/inherit_data_option.rst
index 2caa0afcdbe..8067e932c5a 100644
--- a/form/inherit_data_option.rst
+++ b/form/inherit_data_option.rst
@@ -29,7 +29,7 @@ entities, a ``Company`` and a ``Customer``::
         private string $firstName;
         private string $lastName;
 
-        private string  $address;
+        private string $address;
         private string $zipcode;
         private string $city;
         private string $country;
diff --git a/form/validation_group_service_resolver.rst b/form/validation_group_service_resolver.rst
deleted file mode 100644
index 82a6f65d6ec..00000000000
--- a/form/validation_group_service_resolver.rst
+++ /dev/null
@@ -1,58 +0,0 @@
-How to Dynamically Configure Form Validation Groups
-===================================================
-
-Sometimes you need advanced logic to determine the validation groups. If they
-can't be determined by a callback, you can use a service. Create a service
-that implements ``__invoke()`` which accepts a ``FormInterface`` as a
-parameter::
-
-    // src/Validation/ValidationGroupResolver.php
-    namespace App\Validation;
-
-    use Symfony\Component\Form\FormInterface;
-
-    class ValidationGroupResolver
-    {
-        public function __construct(
-            private object $service1,
-            private object $service2,
-        ) {
-        }
-
-        public function __invoke(FormInterface $form): array
-        {
-            $groups = [];
-
-            // ... determine which groups to apply and return an array
-
-            return $groups;
-        }
-    }
-
-Then in your form, inject the resolver and set it as the ``validation_groups``::
-
-    // src/Form/MyClassType.php;
-    namespace App\Form;
-
-    use App\Validation\ValidationGroupResolver;
-    use Symfony\Component\Form\AbstractType;
-    use Symfony\Component\OptionsResolver\OptionsResolver;
-
-    class MyClassType extends AbstractType
-    {
-        public function __construct(
-            private ValidationGroupResolver $groupResolver,
-        ) {
-        }
-
-        // ...
-        public function configureOptions(OptionsResolver $resolver): void
-        {
-            $resolver->setDefaults([
-                'validation_groups' => $this->groupResolver,
-            ]);
-        }
-    }
-
-This will result in the form validator invoking your group resolver to set the
-validation groups returned when validating.
diff --git a/form/validation_groups.rst b/form/validation_groups.rst
index 4addc1ba1a7..3157ef7bc5b 100644
--- a/form/validation_groups.rst
+++ b/form/validation_groups.rst
@@ -1,39 +1,163 @@
-How to Define the Validation Groups to Use
-==========================================
+Configuring Validation Groups in Forms
+======================================
 
-Validation Groups
------------------
+If the object handled in your form uses :doc:`validation groups </validation/groups>`,
+you need to specify which validation group(s) the form should apply.
 
-If your object takes advantage of :doc:`validation groups </validation/groups>`,
-you'll need to specify which validation group(s) your form should use. Pass
-this as an option when :ref:`creating forms in controllers <creating-forms-in-controllers>`::
+To define them when :ref:`creating forms in classes <creating-forms-in-classes>`,
+use the ``configureOptions()`` method::
+
+    use Symfony\Component\OptionsResolver\OptionsResolver;
+
+    public function configureOptions(OptionsResolver $resolver): void
+    {
+        $resolver->setDefaults([
+            // ...
+            'validation_groups' => ['registration'],
+        ]);
+    }
+
+When :ref:`creating forms in controllers <creating-forms-in-controllers>`, pass
+it as a form option::
 
     $form = $this->createFormBuilder($user, [
         'validation_groups' => ['registration'],
     ])->add(/* ... */);
 
-When :ref:`creating forms in classes <creating-forms-in-classes>`, add the
-following to the ``configureOptions()`` method::
+In both cases, *only* the ``registration`` group will be used to validate the
+object. To apply the ``registration`` group *and* all constraints not in any
+other group, add the special ``Default`` group::
+
+    [
+        // ...
+        'validation_groups' => ['Default', 'registration'],
+    ]
 
+.. note::
+
+    You can use any name for your validation groups. Symfony recommends using
+    "lower snake case" (e.g. ``foo_bar``), while automatically generated
+    groups use "UpperCamelCase" (e.g. ``Default``, ``SomeClassName``).
+
+Choosing Validation Groups Based on the Clicked Button
+------------------------------------------------------
+
+When your form has :doc:`multiple submit buttons </form/multiple_buttons>`, you
+can change the validation group based on the clicked button. For example, in a
+multi-step form like the following, you might want to skip validation when
+returning to a previous step::
+
+    $form = $this->createFormBuilder($task)
+        // ...
+        ->add('nextStep', SubmitType::class)
+        ->add('previousStep', SubmitType::class)
+        ->getForm();
+
+To do so, configure the validation groups of the ``previousStep`` button to
+``false``, which is a special value that skips validation::
+
+    $form = $this->createFormBuilder($task)
+        // ...
+        ->add('previousStep', SubmitType::class, [
+            'validation_groups' => false,
+        ])
+        ->getForm();
+
+Now the form will skip your validation constraints when that button is clicked.
+It will still validate basic integrity constraints, such as checking whether an
+uploaded file was too large or whether you tried to submit text in a number field.
+
+Choosing Validation Groups Based on Submitted Data
+--------------------------------------------------
+
+To determine validation groups dynamically based on submitted data, use a
+callback. This is called after the form is submitted, but before validation is
+invoked. The callback receives the form object as its first argument::
+
+    use App\Entity\Client;
+    use Symfony\Component\Form\FormInterface;
     use Symfony\Component\OptionsResolver\OptionsResolver;
 
     public function configureOptions(OptionsResolver $resolver): void
     {
         $resolver->setDefaults([
-            // ...
-            'validation_groups' => ['registration'],
+            'validation_groups' => function (FormInterface $form): array {
+                $data = $form->getData();
+
+                if (Client::TYPE_PERSON === $data->getType()) {
+                    return ['Default', 'person'];
+                }
+
+                return ['Default', 'company'];
+            },
         ]);
     }
 
-In both of these cases, *only* the ``registration`` validation group will
-be used to validate the underlying object. To apply the ``registration``
-group *and* all constraints that are not in a group, use::
+.. note::
+
+    Adding ``Default`` to the list of validation groups is common but not mandatory.
+    See the main :doc:`article about validation groups </validation/groups>` to
+    learn more about validation groups and the default constraints.
 
-    'validation_groups' => ['Default', 'registration']
+You can also pass a static class method callback::
 
-.. note::
+    'validation_groups' => [Client::class, 'determineValidationGroups']
+
+Choosing Validation Groups via a Service
+----------------------------------------
+
+If validation group logic requires services or can't fit in a closure, use a
+dedicated validation group resolver service. The class of this service must
+be invokable and receives the form object as its first argument::
+
+    // src/Validation/ValidationGroupResolver.php
+    namespace App\Validation;
+
+    use Symfony\Component\Form\FormInterface;
+
+    class ValidationGroupResolver
+    {
+        public function __construct(
+            private object $service1,
+            private object $service2,
+        ) {
+        }
+
+        public function __invoke(FormInterface $form): array
+        {
+            $groups = [];
+
+            // ... determine which groups to return
+
+            return $groups;
+        }
+    }
+
+Then use the service in your form type::
+
+    namespace App\Form;
+
+    use App\Validation\ValidationGroupResolver;
+    use Symfony\Component\Form\AbstractType;
+    use Symfony\Component\OptionsResolver\OptionsResolver;
+
+    class MyClassType extends AbstractType
+    {
+        public function __construct(
+            private ValidationGroupResolver $groupResolver,
+        ) {
+        }
+
+        public function configureOptions(OptionsResolver $resolver): void
+        {
+            $resolver->setDefaults([
+                'validation_groups' => $this->groupResolver,
+            ]);
+        }
+    }
+
+Learn More
+----------
 
-    You can choose any name for your validation groups, but Symfony recommends
-    using "lower snake case" names (e.g. ``foo_bar``) in contrast with the
-    automatic validation groups created by Symfony, which use "upper camel case"
-    (e.g. ``Default``, ``SomeClassName``).
+For more information about how validation groups work, see
+:doc:`/validation/groups`.
diff --git a/form/without_class.rst b/form/without_class.rst
index 8b0af7cf23f..c31ff346170 100644
--- a/form/without_class.rst
+++ b/form/without_class.rst
@@ -96,12 +96,12 @@ but here's a short example::
     {
         $builder
             ->add('firstName', TextType::class, [
-                'constraints' => new Length(['min' => 3]),
+                'constraints' => new Length(min: 3),
             ])
             ->add('lastName', TextType::class, [
                 'constraints' => [
                     new NotBlank(),
-                    new Length(['min' => 3]),
+                    new Length(min: 3),
                 ],
             ])
         ;
@@ -153,10 +153,10 @@ This can be done by setting the ``constraints`` option in the
         $resolver->setDefaults([
             'data_class' => null,
             'constraints' => new Collection([
-                'firstName' => new Length(['min' => 3]),
+                'firstName' => new Length(min: 3),
                 'lastName' => [
                     new NotBlank(),
-                    new Length(['min' => 3]),
+                    new Length(min: 3),
                 ],
             ]),
         ]);
@@ -177,3 +177,41 @@ in your controller::
         ->add('firstName', TextType::class)
         ->add('lastName', TextType::class)
         ->getForm();
+
+Conditional Constraints
+~~~~~~~~~~~~~~~~~~~~~~~
+
+It's possible to define field constraints that depend on the value of other
+fields (e.g. a field must not be blank when another field has a certain value).
+To achieve this, use the ``expression`` option of the
+:doc:`When constraint </reference/constraints/When>` to reference the other field::
+
+    $builder
+        ->add('how_did_you_hear', ChoiceType::class, [
+            'required' => true,
+            'label' => 'How did you hear about us?',
+            'choices' => [
+                'Search engine' => 'search_engine',
+                'Friends' => 'friends',
+                'Other' => 'other',
+            ],
+            'expanded' => true,
+            'constraints' => [
+                new Assert\NotBlank(),
+            ]
+        ])
+
+        // this field is only required if the value of the 'how_did_you_hear' field is 'other'
+        ->add('other_text', TextType::class, [
+            'required' => false,
+            'label' => 'Please specify',
+            'constraints' => [
+                new Assert\When(
+                    expression: 'this.getParent().get("how_did_you_hear").getData() == "other"',
+                    constraints: [
+                        new Assert\NotBlank(),
+                    ],
+                )
+            ],
+        ])
+    ;
diff --git a/forms.rst b/forms.rst
index 008c60a66c6..83065d7524b 100644
--- a/forms.rst
+++ b/forms.rst
@@ -995,8 +995,6 @@ Validation:
     :maxdepth: 1
 
     /form/validation_groups
-    /form/validation_group_service_resolver
-    /form/button_based_validation
     /form/disabling_validation
 
 Misc.:
diff --git a/frontend.rst b/frontend.rst
index c28e6fcf222..404c9ade3a3 100644
--- a/frontend.rst
+++ b/frontend.rst
@@ -35,7 +35,7 @@ Supports Sass/Tailwind            :ref:`yes <asset-mapper-tailwind>`  yes
 Supports React, Vue, Svelte?      yes :ref:`[1] <ux-note-1>`          yes
 Supports TypeScript               :ref:`yes <asset-mapper-ts>`        yes
 Removes comments from JavaScript  no :ref:`[2] <ux-note-2>`           yes
-Removes comments from CSS         no :ref:`[2] <ux-note-2>`           no
+Removes comments from CSS         no :ref:`[2] <ux-note-2>`           yes :ref:`[4] <ux-note-4>`
 Versioned assets                  always                              optional
 Can update 3rd party packages     yes                                 no :ref:`[3] <ux-note-3>`
 ================================  ==================================  ==========
@@ -56,6 +56,11 @@ be executed by a browser.
 
 **[3]** If you use ``npm``, there are update checkers available (e.g. ``npm-check``).
 
+.. _ux-note-4:
+
+**[4]** CSS comments can be removed using `CssMinimizerPlugin`_, which is included
+in Webpack Encore and configurable via ``Encore.configureCssMinimizerPlugin()``.
+
 .. _frontend-asset-mapper:
 
 AssetMapper (Recommended)
@@ -153,3 +158,4 @@ Other Front-End Articles
 .. _`SensioLabs Minify Bundle`: https://github.com/sensiolabs/minify-bundle
 .. _`AssetMapper screencast series`: https://symfonycasts.com/screencast/asset-mapper
 .. _`API Platform screencast series`: https://symfonycasts.com/screencast/api-platform
+.. _`CssMinimizerPlugin`: https://webpack.js.org/plugins/css-minimizer-webpack-plugin
diff --git a/frontend/asset_mapper.rst b/frontend/asset_mapper.rst
index 57e0aa53d43..912f645bf6a 100644
--- a/frontend/asset_mapper.rst
+++ b/frontend/asset_mapper.rst
@@ -12,7 +12,7 @@ The component has two main features:
 * :ref:`Mapping & Versioning Assets <mapping-assets>`: All files inside of ``assets/``
   are made available publicly and **versioned**. You can reference the file
   ``assets/images/product.jpg`` in a Twig template with ``{{ asset('images/product.jpg') }}``.
-  The final URL will include a version hash, like ``/assets/images/product-3c16d9220694c0e56d8648f25e6035e9.jpg``.
+  The final URL will include a version hash, like ``/assets/images/product-3c16d92m.jpg``.
 
 * :ref:`Importmaps <importmaps-javascript>`: A native browser feature that makes it easier
   to use the JavaScript ``import`` statement (e.g. ``import { Modal } from 'bootstrap'``)
@@ -70,7 +70,7 @@ The path - ``images/duck.png`` - is relative to your mapped directory (``assets/
 This is known as the **logical path** to your asset.
 
 If you look at the HTML in your page, the URL will be something
-like: ``/assets/images/duck-3c16d9220694c0e56d8648f25e6035e9.png``. If you change
+like: ``/assets/images/duck-3c16d92m.png``. If you change
 the file, the version part of the URL will also change automatically.
 
 .. _asset-mapper-compile-assets:
@@ -78,7 +78,7 @@ the file, the version part of the URL will also change automatically.
 Serving Assets in dev vs prod
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-In the ``dev`` environment, the URL ``/assets/images/duck-3c16d9220694c0e56d8648f25e6035e9.png``
+In the ``dev`` environment, the URL ``/assets/images/duck-3c16d92m.png``
 is handled and returned by your Symfony app.
 
 For the ``prod`` environment, before deploy, you should run:
@@ -139,6 +139,28 @@ This will show you all the mapped paths and the assets inside of each:
 The "Logical Path" is the path to use when referencing the asset, like
 from a template.
 
+The ``debug:asset-map`` command provides several options to filter results:
+
+.. code-block:: terminal
+
+    # provide an asset name or dir to only show results that match it
+    $ php bin/console debug:asset-map bootstrap.js
+    $ php bin/console debug:asset-map style/
+
+    # provide an extension to only show that file type
+    $ php bin/console debug:asset-map --ext=css
+
+    # you can also only show assets in vendor/ dir or exclude any results from it
+    $ php bin/console debug:asset-map --vendor
+    $ php bin/console debug:asset-map --no-vendor
+
+    # you can also combine all filters (e.g. find bold web fonts in your own asset dirs)
+    $ php bin/console debug:asset-map bold --no-vendor --ext=woff2
+
+.. versionadded:: 7.2
+
+    The options to filter ``debug:asset-map`` results were introduced in Symfony 7.2.
+
 .. _importmaps-javascript:
 
 Importmaps & Writing JavaScript
@@ -193,6 +215,15 @@ to add any `npm package`_:
 
     $ php bin/console importmap:require bootstrap
 
+.. tip::
+
+    Add the ``--dry-run`` option to simulate package installation without actually
+    making any changes (e.g. ``php bin/console importmap:require bootstrap --dry-run``)
+
+    .. versionadded:: 7.3
+
+        The ``--dry-run`` option was introduced in Symfony 7.3.
+
 This adds the ``bootstrap`` package to your ``importmap.php`` file::
 
     // importmap.php
@@ -221,6 +252,26 @@ This adds the ``bootstrap`` package to your ``importmap.php`` file::
     `package.json configuration file`_. Try to contact the package maintainer to
     ask them to fix those issues.
 
+.. tip::
+
+    If you see a network error like *Connection was reset for "https://cdn.jsdelivr.net/npm/..."*,
+    it may be caused by a proxy or firewall restriction. In that case, you can
+    temporarily configure a proxy to connect to the ``jsDelivr`` CDN:
+
+    .. code-block:: yaml
+
+        # config/packages/framework.yaml
+        framework:
+            # ...
+            http_client:
+                default_options:
+                    proxy: '185.250.180.238:8080'
+                    # if you use CURL, add extra options:
+                    extra:
+                        curl:
+                            # 61 is value of constant CURLOPT_HTTPPROXYTUNNEL
+                            '61': true
+
 Now you can import the ``bootstrap`` package like usual:
 
 .. code-block:: javascript
@@ -250,6 +301,33 @@ You can update your third-party packages to their current versions by running:
     $ php bin/console importmap:update bootstrap lodash
     $ php bin/console importmap:outdated bootstrap lodash
 
+Removing JavaScript Packages
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If you need to remove a JavaScript package that was previously added to your
+``importmap.php`` file, use the ``importmap:remove`` command. For example, to
+remove the ``lodash`` package:
+
+.. code-block:: terminal
+
+    $ php bin/console importmap:remove lodash
+
+This updates your ``importmap.php`` file and removes the specified package
+(along with any dependencies that were added with it).
+
+After running this command, it's recommended to also run the following to ensure
+that your ``assets/vendor/`` directory is in sync with the updated import map:
+
+.. code-block:: terminal
+
+    $ php bin/console importmap:install
+
+.. tip::
+
+    Removing a package from the import map does not automatically remove any
+    references to it in your JavaScript files. Make sure to update your code and
+    remove any ``import`` statements that reference the removed package.
+
 How does the importmap Work?
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -261,9 +339,9 @@ outputs an `importmap`_:
 
     <script type="importmap">{
         "imports": {
-            "app": "/assets/app-4e986c1a2318dd050b1d47db8d856278.js",
-            "/assets/duck.js": "/assets/duck-1b7a64b3b3d31219c262cf72521a5267.js",
-            "bootstrap": "/assets/vendor/bootstrap/bootstrap.index-f0935445d9c6022100863214b519a1f2.js"
+            "app": "/assets/app-4e986c1a.js",
+            "/assets/duck.js": "/assets/duck-1b7a64b3.js",
+            "bootstrap": "/assets/vendor/bootstrap/bootstrap.index-f093544d.js"
         }
     }</script>
 
@@ -320,8 +398,8 @@ The ``importmap()`` function also outputs a set of "preloads":
 
 .. code-block:: html
 
-    <link rel="modulepreload" href="/assets/app-4e986c1a2318dd050b1d47db8d856278.js">
-    <link rel="modulepreload" href="/assets/duck-1b7a64b3b3d31219c262cf72521a5267.js">
+    <link rel="modulepreload" href="/assets/app-4e986c1a.js">
+    <link rel="modulepreload" href="/assets/duck-1b7a64b3.js">
 
 This is a performance optimization and you can learn more about below
 in :ref:`Performance: Add Preloading <performance-preloading>`.
@@ -472,9 +550,9 @@ for ``duck.png``:
 
 .. code-block:: css
 
-    /* public/assets/styles/app-3c16d9220694c0e56d8648f25e6035e9.css */
+    /* public/assets/styles/app-3c16d92m.css */
     .quack {
-        background-image: url('../images/duck-3c16d9220694c0e56d8648f25e6035e9.png');
+        background-image: url('../images/duck-3c16d92m.png');
     }
 
 .. _asset-mapper-tailwind:
@@ -551,7 +629,7 @@ Sometimes a JavaScript file you're importing (e.g. ``import './duck.js'``),
 or a CSS/image file you're referencing won't be found, and you'll see a 404
 error in your browser's console. You'll also notice that the 404 URL is missing
 the version hash in the filename (e.g. a 404 to ``/assets/duck.js`` instead of
-a path like ``/assets/duck.1b7a64b3b3d31219c262cf72521a5267.js``).
+a path like ``/assets/duck-1b7a64b3.js``).
 
 This is usually because the path is wrong. If you're referencing the file
 directly in a Twig template:
@@ -634,7 +712,9 @@ which will automatically do most of these things for you:
 - **Compress your assets**: Your web server should compress (e.g. using gzip)
   your assets (JavaScript, CSS, images) before sending them to the browser. This
   is automatically enabled in Caddy and can be activated in Nginx and Apache.
-  In Cloudflare, assets are compressed by default.
+  In Cloudflare, assets are compressed by default. AssetMapper also supports
+  :ref:`precompressing your web assets <performance-precompressing>` to further
+  improve performance.
 
 - **Set long-lived cache expiry**: Your web server should set a long-lived
   ``Cache-Control`` HTTP header on your assets. Because the AssetMapper component includes a version
@@ -682,6 +762,85 @@ even though it hasn't yet seen the ``import`` statement for them.
 Additionally, if the :doc:`WebLink Component </web_link>` is available in your application,
 Symfony will add a ``Link`` header in the response to preload the CSS files.
 
+.. _performance-precompressing:
+
+Pre-Compressing Assets
+----------------------
+
+.. versionadded:: 7.3
+
+    Support for pre-compressing assets was introduced in Symfony 7.3.
+
+Although most web servers (Caddy, Nginx, Apache, FrankenPHP) and services like Cloudflare
+provide asset compression features, AssetMapper also allows you to compress all
+your assets before serving them.
+
+This improves performance because you can compress assets using the highest (and
+slowest) compression ratios beforehand and provide those compressed assets to the
+server, which then returns them to the client without wasting CPU resources on
+compression.
+
+AssetMapper supports  `Brotli`_, `Zstandard`_ and  `gzip`_ compression formats.
+Before using any of them, the machine that pre-compresses assets must have
+installed the following PHP extensions or CLI commands:
+
+* Brotli: ``brotli`` CLI command; `brotli PHP extension`_;
+* Zstandard: ``zstd`` CLI command; `zstd PHP extension`_;
+* gzip: ``zopfli`` (better) or ``gzip`` CLI command; `zlib PHP extension`_.
+
+Then, update your AssetMapper configuration to define which compression to use
+and which file extensions should be compressed:
+
+.. code-block:: yaml
+
+    # config/packages/asset_mapper.yaml
+    framework:
+        asset_mapper:
+            # ...
+
+            precompress:
+                # possible values: 'brotli', 'zstandard', 'gzip'
+                format: 'zstandard'
+
+                # you can also pass multiple values to generate files in several formats
+                # format: ['brotli', 'zstandard']
+
+                # if you don't define the following option, AssetMapper will compress all
+                # the extensions considered safe (css, js, json, svg, xml, ttf, otf, wasm, etc.)
+                extensions: ['css', 'js', 'json', 'svg', 'xml']
+
+Now, when running the ``asset-map:compile`` command, all matching files will be
+compressed in the configured format and at the highest compression level. The
+compressed files are created with the same name as the original but with the
+``.br``, ``.zst``, or ``.gz`` extension appended.
+
+Then, you need to configure your web server to serve the precompressed assets
+instead of the original ones:
+
+.. configuration-block::
+
+    .. code-block:: caddy
+
+        file_server {
+            precompressed br zstd gzip
+        }
+
+    .. code-block:: nginx
+
+        gzip_static on;
+
+        # Requires https://github.com/google/ngx_brotli
+        brotli_static on;
+
+        # Requires https://github.com/tokers/zstd-nginx-module
+        zstd_static on;
+
+.. tip::
+
+    AssetMapper provides an ``assets:compress`` CLI command and a service called
+    ``asset_mapper.compressor`` that you can use anywhere in your application to
+    compress any kind of files (e.g. files uploaded by users to your application).
+
 Frequently Asked Questions
 --------------------------
 
@@ -826,7 +985,7 @@ be versioned! It will output something like:
 
 .. code-block:: html+twig
 
-    <link rel="stylesheet" href="/assets/bundles/babdevpagerfanta/css/pagerfanta-ea64fc9c55f8394e696554f8aeb81a8e.css">
+    <link rel="stylesheet" href="/assets/bundles/babdevpagerfanta/css/pagerfanta-ea64fc9c.css">
 
 Overriding 3rd-Party Assets
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -1043,14 +1202,19 @@ both ``app`` and ``checkout``:
         {{ importmap(['app', 'checkout']) }}
     {% endblock %}
 
-By passing both ``app`` and ``checkout``, the ``importmap()`` function will
-output the ``importmap`` and also add a ``<script type="module">`` tag that
-loads the ``app.js`` file *and* the ``checkout.js`` file. It's important
-to *not* call ``parent()`` in the ``importmap`` block. Each page can only
-have *one* importmap, so ``importmap()`` must be called exactly once.
+The ``importmap()`` function always includes the full import map to ensure all
+module definitions are available on the page. It also adds a ``<script type="module">``
+tag to load the specific JavaScript entry files you pass to it (in the example
+above, the ``app.js`` file *and* the ``checkout.js`` file).
+
+.. warning::
+
+    Do not call ``parent()`` inside the ``{% block importmap %}`` Twig block. Each
+    page can include only one import map, so ``importmap()`` must be called exactly once.
 
-If, for some reason, you want to execute *only* ``checkout.js``
-and *not* ``app.js``, pass only ``checkout`` to ``importmap()``.
+If you want to execute *only* ``checkout.js`` (and not ``app.js``), call
+``{{ importmap('checkout') }}``. In this case, the full import map will still be
+included in the page, but only the ``checkout.js`` file will actually be loaded.
 
 Using a Content Security Policy (CSP)
 -------------------------------------
@@ -1173,3 +1337,9 @@ command as part of your CI to be warned anytime a new vulnerability is found.
 .. _strict-dynamic: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src#strict-dynamic
 .. _kocal/biome-js-bundle: https://github.com/Kocal/BiomeJsBundle
 .. _`SensioLabs Minify Bundle`: https://github.com/sensiolabs/minify-bundle
+.. _`Brotli`: https://en.wikipedia.org/wiki/Brotli
+.. _`Zstandard`: https://en.wikipedia.org/wiki/Zstd
+.. _`gzip`: https://en.wikipedia.org/wiki/Gzip
+.. _`brotli PHP extension`: https://pecl.php.net/package/brotli
+.. _`zstd PHP extension`: https://pecl.php.net/package/zstd
+.. _`zlib PHP extension`: https://www.php.net/manual/en/book.zlib.php
diff --git a/frontend/encore/advanced-config.rst b/frontend/encore/advanced-config.rst
index bbd4fc44c93..b7a02883e08 100644
--- a/frontend/encore/advanced-config.rst
+++ b/frontend/encore/advanced-config.rst
@@ -198,6 +198,34 @@ Now you can inject your service into your class::
         $this->render($emailTwo);
     }
 
+Configuring the CSS Loader
+--------------------------
+
+Encore provides the ``configureCssLoader()`` method to customize how ``css-loader``
+processes your CSS assets. One common use case is to prevent Webpack from resolving
+certain URLs.
+
+For instance, if your application serves user-uploaded assets from a specific
+directory, you'll want Webpack to ignore these paths since they may not exist
+during the build process:
+
+.. code-block:: javascript
+
+    // Configuring the CSS Loader in Webpack Encore
+    // Prevent Webpack from resolving certain URLs in CSS files
+    Encore.configureCssLoader((options) => {
+        options.url = {
+            filter: (url) => {
+                // Ignore URLs beginning with /uploads/
+                if (url.startsWith('/uploads/')) {
+                    return false;
+                }
+
+                return true; // Process other URLs as usual
+            },
+        };
+    });
+
 Generating a Webpack Configuration Object without using the Command-Line Interface
 ----------------------------------------------------------------------------------
 
diff --git a/frontend/encore/dev-server.rst b/frontend/encore/dev-server.rst
index 01501178caf..a3adb04685a 100644
--- a/frontend/encore/dev-server.rst
+++ b/frontend/encore/dev-server.rst
@@ -53,9 +53,9 @@ method in your ``webpack.config.js`` file:
 Enabling HTTPS using the Symfony Web Server
 -------------------------------------------
 
-If you're using the :doc:`Symfony web server </setup/symfony_server>` locally with HTTPS,
-you'll need to also tell the dev-server to use HTTPS. To do this, you can reuse the Symfony web
-server SSL certificate:
+If you're using the :ref:`Symfony local web server <symfony-cli-server>` locally
+with HTTPS, you'll need to also tell the dev-server to use HTTPS. To do this,
+you can reuse the Symfony web server SSL certificate:
 
 .. code-block:: diff
 
diff --git a/frontend/encore/virtual-machine.rst b/frontend/encore/virtual-machine.rst
index d18026d3633..34587173b93 100644
--- a/frontend/encore/virtual-machine.rst
+++ b/frontend/encore/virtual-machine.rst
@@ -107,7 +107,7 @@ the dev-server. To fix this, set the ``allowedHosts`` option:
         // ...
 
         .configureDevServerOptions(options => {
-            options.allowedHosts = all;
+            options.allowedHosts = 'all';
         })
 
 .. warning::
diff --git a/html_sanitizer.rst b/html_sanitizer.rst
index f2400103284..38d7664ccf7 100644
--- a/html_sanitizer.rst
+++ b/html_sanitizer.rst
@@ -314,6 +314,8 @@ attributes from the `W3C Standard Proposal`_ are allowed.
                             img: 'src'
                             # allow the <h1> element with all safe attributes
                             h1: '*'
+                            # allow the <div> element with no attributes
+                            div: []
 
     .. code-block:: xml
 
@@ -343,9 +345,12 @@ attributes from the `W3C Standard Proposal`_ are allowed.
                         </framework:allow-element>
 
                         <!-- allow the <h1> element with all safe attributes -->
-                        <framework:allow-element name="img">
+                        <framework:allow-element name="h1">
                             <framework:attribute>*</framework:attribute>
                         </framework:allow-element>
+
+                        <!-- allow the <div> element with no attributes -->
+                        <framework:allow-element name="div"/>
                     </framework:sanitizer>
                 </framework:html-sanitizer>
             </framework:config>
@@ -367,6 +372,9 @@ attributes from the `W3C Standard Proposal`_ are allowed.
 
                     // allow the <h1> element with all safe attributes
                     ->allowElement('h1', '*')
+
+                    // allow the <div> element with no attributes
+                    ->allowElement('div', [])
             ;
         };
 
@@ -385,6 +393,9 @@ attributes from the `W3C Standard Proposal`_ are allowed.
 
                 // allow the <h1> element with all safe attributes
                 ->allowElement('h1')
+
+                // allow the <div> element with no attributes
+                ->allowElement('div', [])
         );
 
 Block and Drop Elements
@@ -804,16 +815,16 @@ URLs of ``<a>`` elements:
             (new HtmlSanitizerConfig())
                 // if `true`, all URLs using the `http://` scheme will be converted to
                 // use the `https://` scheme instead. `http` still needs to be
-                // allowed in `allowedLinkSchemes`
+                // allowed in `allowLinkSchemes`
                 ->forceHttpsUrls()
 
                 // specifies the allowed URL schemes. If the URL has a different scheme, the
                 // attribute will be dropped
-                ->allowedLinkSchemes(['http', 'https', 'mailto'])
+                ->allowLinkSchemes(['http', 'https', 'mailto'])
 
                 // specifies the allowed hosts, the attribute will be dropped if the
                 // URL contains a different host which is not a subdomain of the allowed host
-                ->allowedLinkHosts(['symfony.com']) // Also allows any subdomain (i.e. www.symfony.com)
+                ->allowLinkHosts(['symfony.com']) // Also allows any subdomain (i.e. www.symfony.com)
 
                 // whether to allow relative links (i.e. URLs without scheme and host)
                 ->allowRelativeLinks()
@@ -923,16 +934,16 @@ the HTML sanitizer: ``src``, ``href``, ``lowsrc``, ``background`` and ``ping``.
             (new HtmlSanitizerConfig())
                 // if `true`, all URLs using the `http://` scheme will be converted to
                 // use the `https://` scheme instead. `http` still needs to be
-                // allowed in `allowedMediaSchemes`
+                // allowed in `allowMediaSchemes`
                 ->forceHttpsUrls()
 
                 // specifies the allowed URL schemes. If the URL has a different scheme, the
                 // attribute will be dropped
-                ->allowedMediaSchemes(['http', 'https', 'mailto'])
+                ->allowMediaSchemes(['http', 'https', 'mailto'])
 
                 // specifies the allowed hosts, the attribute will be dropped if the URL
                 // contains a different host which is not a subdomain of the allowed host
-                ->allowedMediaHosts(['symfony.com']) // Also allows any subdomain (i.e. www.symfony.com)
+                ->allowMediaHosts(['symfony.com']) // Also allows any subdomain (i.e. www.symfony.com)
 
                 // whether to allow relative URLs (i.e. URLs without scheme and host)
                 ->allowRelativeMedias()
diff --git a/http_cache.rst b/http_cache.rst
index b0bca286281..1797219c649 100644
--- a/http_cache.rst
+++ b/http_cache.rst
@@ -19,7 +19,7 @@ The Symfony cache system is different because it relies on the simplicity
 and power of the HTTP cache as defined in `RFC 7234 - Caching`_. Instead of
 reinventing a caching methodology, Symfony embraces the standard that defines
 basic communication on the Web. Once you understand the fundamental HTTP
-validation and expiration caching models, you'll be ready to master the Symfony
+validation and expiration caching models, you'll be ready to understand the Symfony
 cache system.
 
 Since caching with HTTP isn't unique to Symfony, many articles already exist
diff --git a/http_cache/varnish.rst b/http_cache/varnish.rst
index a9bb668c100..6fcb7fd766b 100644
--- a/http_cache/varnish.rst
+++ b/http_cache/varnish.rst
@@ -62,7 +62,7 @@ If you know for sure that the backend never uses sessions or basic
 authentication, have Varnish remove the corresponding header from requests to
 prevent clients from bypassing the cache. In practice, you will need sessions
 at least for some parts of the site, e.g. when using forms with
-:doc:`CSRF Protection </security/csrf>`. In this situation, make sure to
+:doc:`stateful CSRF Protection </security/csrf>`. In this situation, make sure to
 :ref:`only start a session when actually needed <session-avoid-start>`
 and clear the session when it is no longer needed. Alternatively, you can look
 into :ref:`caching pages that contain CSRF protected forms <caching-pages-that-contain-csrf-protected-forms>`.
diff --git a/http_client.rst b/http_client.rst
index 30379f9a3b3..a1c8f09bc1d 100644
--- a/http_client.rst
+++ b/http_client.rst
@@ -175,8 +175,9 @@ Some options are described in this guide:
 Check out the full :ref:`http_client config reference <reference-http-client>`
 to learn about all the options.
 
-The HTTP client also has one configuration option called
-``max_host_connections``, this option can not be overridden by a request:
+The HTTP client also has a configuration option called
+:ref:`max_host_connections <reference-http-client-max-host-connections>`.
+This option cannot be overridden per request:
 
 .. configuration-block::
 
@@ -342,6 +343,12 @@ autoconfigure the HTTP client based on the requested URL:
 You can define several scopes, so that each set of options is added only if a
 requested URL matches one of the regular expressions set by the ``scope`` option.
 
+.. note::
+
+    The options passed to the ``request()`` method are merged with the default
+    options defined in the scoped client. The options passed to ``request()``
+    take precedence and override or extend the default ones.
+
 If you use scoped clients in the Symfony framework, you must use any of the
 methods defined by Symfony to :ref:`choose a specific service <services-wire-specific-service>`.
 Each client has a unique service named after its configuration.
@@ -666,6 +673,15 @@ of the opened file, but you can configure both with the PHP streaming configurat
         $formData->getParts(); // Returns two instances of TextPart both
                                // with the name "array_field"
 
+The ``Content-Type`` of each form's part is detected automatically. However,
+you can override it by passing a ``DataPart``::
+
+    use Symfony\Component\Mime\Part\DataPart;
+
+    $formData = new FormDataPart([
+        ['json_data' => new DataPart(json_encode($json), null, 'application/json')]
+    ]);
+
 By default, HttpClient streams the body contents when uploading them. This might
 not work with all servers, resulting in HTTP status code 411 ("Length Required")
 because there is no ``Content-Length`` header. The solution is to turn the body
@@ -891,7 +907,7 @@ in your requests::
         'extra' => ['trace_content' => false],
     ]);
 
-This setting won’t affect other clients.
+This setting won't affect other clients.
 
 Using URI Templates
 ~~~~~~~~~~~~~~~~~~~
@@ -1302,65 +1318,75 @@ remaining to do.
 Concurrent Requests
 -------------------
 
-Thanks to responses being lazy, requests are always managed concurrently.
-On a fast enough network, the following code makes 379 requests in less than
-half a second when cURL is used::
+Symfony's HTTP client makes asynchronous HTTP requests by default. This means
+you don't need to configure anything special to send multiple requests in parallel
+and process them efficiently.
+
+Here's a practical example that fetches metadata about several Symfony
+components from the Packagist API in parallel::
 
+    $packages = ['console', 'http-kernel', '...', 'routing', 'yaml'];
     $responses = [];
-    for ($i = 0; $i < 379; ++$i) {
-        $uri = "https://http2.akamai.com/demo/tile-$i.png";
-        $responses[] = $client->request('GET', $uri);
+    foreach ($packages as $package) {
+        $uri = sprintf('https://repo.packagist.org/p2/symfony/%s.json', $package);
+        // send all requests concurrently (they won't block until response content is read)
+        $responses[$package] = $client->request('GET', $uri);
     }
 
-    foreach ($responses as $response) {
-        $content = $response->getContent();
-        // ...
+    $results = [];
+    // iterate through the responses and read their content
+    foreach ($responses as $package => $response) {
+        // process response data somehow ...
+        $results[$package] = $response->toArray();
     }
 
-As you can read in the first "for" loop, requests are issued but are not consumed
-yet. That's the trick when concurrency is desired: requests should be sent
-first and be read later on. This will allow the client to monitor all pending
-requests while your code waits for a specific one, as done in each iteration of
-the above "foreach" loop.
+As you can see, the requests are sent in the first loop, but their responses
+aren't consumed until the second one. This is the key to achieving parallel and
+concurrent execution: dispatch all requests first, and read them later.
+This allows the client to handle all pending responses efficiently while your
+code waits only when necessary.
 
 .. note::
 
-    The maximum number of concurrent requests that you can perform depends on
-    the resources of your machine (e.g. your operating system may limit the
-    number of simultaneous reads of the file that stores the certificates
-    file). Make your requests in batches to avoid these issues.
+    The maximum number of concurrent requests depends on your system's resources
+    (e.g. the operating system might limit the number of simultaneous connections
+    or access to certificate files). To avoid hitting these limits, consider
+    processing requests in batches.
+
+    There is, however, a maximum amount of concurrent connections that can be open
+    per host (``6`` by default). See :ref:`max_host_connections <reference-http-client-max-host-connections>`.
 
 Multiplexing Responses
 ~~~~~~~~~~~~~~~~~~~~~~
 
-If you look again at the snippet above, responses are read in requests' order.
-But maybe the 2nd response came back before the 1st? Fully asynchronous operations
-require being able to deal with the responses in whatever order they come back.
+In the previous example, responses are read in the same order as the requests
+were sent. However, it's possible that, for instance, the second response arrives
+before the first. To handle such cases efficiently, you need fully asynchronous
+processing, which allows responses to be handled in whatever order they arrive.
 
-In order to do so, the
-:method:`Symfony\\Contracts\\HttpClient\\HttpClientInterface::stream`
-accepts a list of responses to monitor. As mentioned
+To achieve this, the
+:method:`Symfony\\Contracts\\HttpClient\\HttpClientInterface::stream` method
+can be used to monitor a list of responses. As mentioned
 :ref:`previously <http-client-streaming-responses>`, this method yields response
-chunks as they arrive from the network. By replacing the "foreach" in the
-snippet with this one, the code becomes fully async::
+chunks as soon as they arrive over the network. Replacing the standard ``foreach``
+loop with the following version enables true asynchronous behavior::
 
     foreach ($client->stream($responses) as $response => $chunk) {
         if ($chunk->isFirst()) {
-            // headers of $response just arrived
-            // $response->getHeaders() is now a non-blocking call
+            // the $response headers just arrived
+            // $response->getHeaders() is now non-blocking
         } elseif ($chunk->isLast()) {
-            // the full content of $response just completed
-            // $response->getContent() is now a non-blocking call
+            // the full $response body has been received
+            // $response->getContent() is now non-blocking
         } else {
-            // $chunk->getContent() will return a piece
-            // of the response body that just arrived
+            // $chunk->getContent() returns a piece of the body that just arrived
         }
     }
 
 .. tip::
 
-    Use the ``user_data`` option combined with ``$response->getInfo('user_data')``
-    to track the identity of the responses in your foreach loops.
+    Use the ``user_data`` option along with ``$response->getInfo('user_data')``
+    to identify each response during streaming.
 
 Dealing with Network Timeouts
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/lock.rst b/lock.rst
index 82ec48db572..3b9f1692df4 100644
--- a/lock.rst
+++ b/lock.rst
@@ -1,7 +1,7 @@
 Dealing with Concurrency with Locks
 ===================================
 
-When a program runs concurrently, some part of code which modify shared
+When a program runs concurrently, some parts of code that modify shared
 resources should not be accessed by multiple processes at the same time.
 Symfony's :doc:`Lock component </components/lock>` provides a locking mechanism to ensure
 that only one process is running the critical section of code at any point of
@@ -62,6 +62,8 @@ this behavior by using the ``lock`` key like:
             lock: 'oci:host=127.0.0.1;dbname=app'
             lock: 'mongodb://127.0.0.1/app?collection=lock'
             lock: '%env(LOCK_DSN)%'
+            # using an existing service
+            lock: 'snc_redis.default'
 
             # named locks
             lock:
@@ -119,6 +121,9 @@ this behavior by using the ``lock`` key like:
 
                     <framework:resource>%env(LOCK_DSN)%</framework:resource>
 
+                    <!-- using an existing service -->
+                    <framework:resource>snc_redis.default</framework:resource>
+
                     <!-- named locks -->
                     <framework:resource name="invoice">semaphore</framework:resource>
                     <framework:resource name="invoice">redis://r2.docker</framework:resource>
@@ -131,6 +136,7 @@ this behavior by using the ``lock`` key like:
 
         // config/packages/lock.php
         use Symfony\Config\FrameworkConfig;
+        use function Symfony\Component\DependencyInjection\Loader\Configurator\env;
 
         return static function (FrameworkConfig $framework): void {
             $framework->lock()
@@ -152,6 +158,8 @@ this behavior by using the ``lock`` key like:
                 ->resource('default', ['oci:host=127.0.0.1;dbname=app'])
                 ->resource('default', ['mongodb://127.0.0.1/app?collection=lock'])
                 ->resource('default', [env('LOCK_DSN')])
+                // using an existing service
+                ->resource('default', ['snc_redis.default'])
 
                 // named locks
                 ->resource('invoice', ['semaphore', 'redis://r2.docker'])
@@ -159,6 +167,10 @@ this behavior by using the ``lock`` key like:
             ;
         };
 
+.. versionadded:: 7.2
+
+    The option to use an existing service as the lock/semaphore was introduced in Symfony 7.2.
+
 Locking a Resource
 ------------------
 
diff --git a/logging/monolog_console.rst b/logging/monolog_console.rst
index 67bf0f5acae..4d007abe854 100644
--- a/logging/monolog_console.rst
+++ b/logging/monolog_console.rst
@@ -10,10 +10,9 @@ When a lot of logging has to happen, it's cumbersome to print information
 depending on the verbosity settings (``-v``, ``-vv``, ``-vvv``) because the
 calls need to be wrapped in conditions. For example::
 
-    use Symfony\Component\Console\Input\InputInterface;
     use Symfony\Component\Console\Output\OutputInterface;
 
-    protected function execute(InputInterface $input, OutputInterface $output): int
+    public function __invoke(OutputInterface $output): int
     {
         if ($output->isDebug()) {
             $output->writeln('Some info');
@@ -34,23 +33,22 @@ the current log level and the console verbosity.
 
 The example above could then be rewritten as::
 
-    // src/Command/YourCommand.php
+    // src/Command/MyCommand.php
     namespace App\Command;
 
     use Psr\Log\LoggerInterface;
+    use Symfony\Component\Console\Attribute\AsCommand;
     use Symfony\Component\Console\Command\Command;
-    use Symfony\Component\Console\Input\InputInterface;
-    use Symfony\Component\Console\Output\OutputInterface;
 
-    class YourCommand extends Command
+    #[AsCommand(name: 'app:my-command')]
+    class MyCommand
     {
         public function __construct(
             private LoggerInterface $logger,
         ) {
-            parent::__construct();
         }
 
-        protected function execute(InputInterface $input, OutputInterface $output): int
+        public function __invoke(): int
         {
             $this->logger->debug('Some info');
             $this->logger->notice('Some more info');
diff --git a/logging/monolog_email.rst b/logging/monolog_email.rst
index 99a416913c8..d52629e0797 100644
--- a/logging/monolog_email.rst
+++ b/logging/monolog_email.rst
@@ -180,7 +180,7 @@ You can adjust the time period using the ``time`` option:
             // ...
 
             $monolog->handler('deduplicated')
-                ->type('deduplicated')
+                ->type('deduplication')
                 // the time in seconds during which duplicate entries are discarded (default: 60)
                 ->time(10)
                 ->handler('symfony_mailer')
@@ -304,7 +304,7 @@ get logged on the server as well as the emails being sent:
             ;
 
             $monolog->handler('deduplicated')
-                ->type('deduplicated')
+                ->type('deduplication')
                 ->handler('symfony_mailer')
             ;
 
diff --git a/mailer.rst b/mailer.rst
index 06feb1e5c29..bdc50757cdd 100644
--- a/mailer.rst
+++ b/mailer.rst
@@ -54,8 +54,8 @@ over SMTP by configuring the DSN in your ``.env`` file (the ``user``,
     .. code-block:: php
 
         // config/packages/mailer.php
-        use function Symfony\Component\DependencyInjection\Loader\Configurator\env;
         use Symfony\Config\FrameworkConfig;
+        use function Symfony\Component\DependencyInjection\Loader\Configurator\env;
 
         return static function (FrameworkConfig $framework): void {
             $framework->mailer()->dsn(env('MAILER_DSN'));
@@ -100,25 +100,38 @@ via a third-party provider:
 ===================== =============================================== ===============
 Service               Install with                                    Webhook support
 ===================== =============================================== ===============
+`AhaSend`_            ``composer require symfony/aha-send-mailer``    yes
 `Amazon SES`_         ``composer require symfony/amazon-mailer``
 `Azure`_              ``composer require symfony/azure-mailer``
 `Brevo`_              ``composer require symfony/brevo-mailer``       yes
 `Infobip`_            ``composer require symfony/infobip-mailer``
 `Mailgun`_            ``composer require symfony/mailgun-mailer``     yes
 `Mailjet`_            ``composer require symfony/mailjet-mailer``     yes
+`Mailomat`_           ``composer require symfony/mailomat-mailer``    yes
 `MailPace`_           ``composer require symfony/mail-pace-mailer``
 `MailerSend`_         ``composer require symfony/mailer-send-mailer`` yes
-`Mandrill`_           ``composer require symfony/mailchimp-mailer``
+`Mailtrap`_           ``composer require symfony/mailtrap-mailer``    yes
+`Mandrill`_           ``composer require symfony/mailchimp-mailer``   yes
+`Postal`_             ``composer require symfony/postal-mailer``
 `Postmark`_           ``composer require symfony/postmark-mailer``    yes
 `Resend`_             ``composer require symfony/resend-mailer``      yes
 `Scaleway`_           ``composer require symfony/scaleway-mailer``
 `SendGrid`_           ``composer require symfony/sendgrid-mailer``    yes
+`Sweego`_             ``composer require symfony/sweego-mailer``      yes
 ===================== =============================================== ===============
 
 .. versionadded:: 7.1
 
     The Azure and Resend integrations were introduced in Symfony 7.1.
 
+.. versionadded:: 7.2
+
+    The Mailomat, Mailtrap, Postal and Sweego integrations were introduced in Symfony 7.2.
+
+.. versionadded:: 7.3
+
+    The AhaSend integration was introduced in Symfony 7.3.
+
 .. note::
 
     As a convenience, Symfony also provides support for Gmail (``composer
@@ -144,7 +157,7 @@ You'll now have a new line in your ``.env`` file that you can uncomment:
 
 The ``MAILER_DSN`` isn't a *real* address: it's a convenient format that
 offloads most of the configuration work to mailer. The ``sendgrid`` scheme
-activates the SendGrid provider that you just installed, which knows all about
+activates the SendGrid provider that you installed, which knows all about
 how to deliver messages via SendGrid. The *only* part you need to change is the
 ``KEY`` placeholder.
 
@@ -167,6 +180,10 @@ party provider:
 +------------------------+---------------------------------------------------------+
 | Provider               | Formats                                                 |
 +========================+=========================================================+
+| `AhaSend`_             | - API ``ahasend+api://KEY@default``                     |
+|                        | - HTTP n/a                                              |
+|                        | - SMTP ``ahasend+smtp://USERNAME:PASSWORD@default``     |
++------------------------+---------------------------------------------------------+
 | `Amazon SES`_          | - SMTP ``ses+smtp://USERNAME:PASSWORD@default``         |
 |                        | - HTTP ``ses+https://ACCESS_KEY:SECRET_KEY@default``    |
 |                        | - API ``ses+api://ACCESS_KEY:SECRET_KEY@default``       |
@@ -201,10 +218,22 @@ party provider:
 |                        | - HTTP n/a                                              |
 |                        | - API ``mailjet+api://ACCESS_KEY:SECRET_KEY@default``   |
 +------------------------+---------------------------------------------------------+
+| `Mailomat`_            | - SMTP ``mailomat+smtp://USERNAME:PASSWORD@default``    |
+|                        | - HTTP n/a                                              |
+|                        | - API ``mailomat+api://KEY@default``                    |
++------------------------+---------------------------------------------------------+
 | `MailPace`_            | - SMTP ``mailpace+api://API_TOKEN@default``             |
 |                        | - HTTP n/a                                              |
 |                        | - API ``mailpace+api://API_TOKEN@default``              |
 +------------------------+---------------------------------------------------------+
+| `Mailtrap`_            | - SMTP ``mailtrap+smtp://PASSWORD@default``             |
+|                        | - HTTP n/a                                              |
+|                        | - API ``mailtrap+api://API_TOKEN@default``              |
++------------------------+---------------------------------------------------------+
+| `Postal`_              | - SMTP n/a                                              |
+|                        | - HTTP n/a                                              |
+|                        | - API ``postal+api://API_KEY@BASE_URL``                 |
++------------------------+---------------------------------------------------------+
 | `Postmark`_            | - SMTP ``postmark+smtp://ID@default``                   |
 |                        | - HTTP n/a                                              |
 |                        | - API ``postmark+api://KEY@default``                    |
@@ -221,6 +250,10 @@ party provider:
 |                        | - HTTP n/a                                              |
 |                        | - API ``sendgrid+api://KEY@default``                    |
 +------------------------+---------------------------------------------------------+
+| `Sweego`_              | - SMTP ``sweego+smtp://LOGIN:PASSWORD@HOST:PORT``       |
+|                        | - HTTP n/a                                              |
+|                        | - API ``sweego+api://API_KEY@default``                  |
++------------------------+---------------------------------------------------------+
 
 .. warning::
 
@@ -235,12 +268,6 @@ party provider:
     you need to add the ``ping_threshold`` parameter to your ``MAILER_DSN`` with
     a value lower than ``10``: ``ses+smtp://USERNAME:PASSWORD@default?ping_threshold=9``
 
-.. warning::
-
-    If you send custom headers when using the `Amazon SES`_ transport (to receive
-    them later via a webhook), make sure to use the ``ses+https`` provider because
-    it's the only one that supports them.
-
 .. note::
 
     When using SMTP, the default timeout for sending a message before throwing an
@@ -307,6 +334,17 @@ The failover-transport starts using the first transport and if it fails, it
 will retry the same delivery with the next transports until one of them succeeds
 (or until all of them fail).
 
+By default, delivery is retried 60 seconds after a failed attempt. You can adjust
+the retry period by setting the ``retry_period`` option in the DSN:
+
+.. code-block:: env
+
+    MAILER_DSN="failover(postmark+api://ID@default sendgrid+smtp://KEY@default)?retry_period=15"
+
+.. versionadded:: 7.3
+
+    The ``retry_period`` option was introduced in Symfony 7.3.
+
 Load Balancing
 ~~~~~~~~~~~~~~
 
@@ -327,6 +365,17 @@ As with the failover transport, round-robin retries deliveries until
 a transport succeeds (or all fail). In contrast to the failover transport,
 it *spreads* the load across all its transports.
 
+By default, delivery is retried 60 seconds after a failed attempt. You can adjust
+the retry period by setting the ``retry_period`` option in the DSN:
+
+.. code-block:: env
+
+    MAILER_DSN="roundrobin(postmark+api://ID@default sendgrid+smtp://KEY@default)?retry_period=15"
+
+.. versionadded:: 7.3
+
+    The ``retry_period`` option was introduced in Symfony 7.3.
+
 TLS Peer Verification
 ~~~~~~~~~~~~~~~~~~~~~
 
@@ -371,6 +420,49 @@ setting the ``auto_tls`` option to ``false`` in the DSN::
 
     This setting only works when the ``smtp://`` protocol is used.
 
+Ensure TLS
+~~~~~~~~~~
+
+You may want to ensure that TLS is used (either directly or via ``STARTTLS``)
+when sending mail over SMTP, regardless of other options or SMTP server support.
+To require TLS, call ``setRequireTls(true)`` on the ``EsmtpTransport`` instance,
+or set the ``require_tls`` option to ``true`` in the DSN::
+
+    $dsn = 'smtp://user:pass@10.0.0.25?require_tls=true';
+
+When TLS is required, a :class:`Symfony\\Component\\Mailer\\Exception\\TransportException`
+is thrown if a TLS connection cannot be established during the initial communication
+with the SMTP server.
+
+.. note::
+
+    This setting only applies when using the ``smtp://`` protocol.
+
+.. versionadded:: 7.3
+
+    The ``require_tls`` option was introduced in Symfony 7.3.
+
+Binding to IPv4 or IPv6
+~~~~~~~~~~~~~~~~~~~~~~~
+
+.. versionadded:: 7.3
+
+    The option to bind to IPv4, or IPv6, or a specific IP address was introduced in Symfony 7.3.
+
+By default, the underlying ``SocketStream`` will bind to IPv4 or IPv6 based on the
+available interfaces. You can enforce binding to a specific protocol or IP address
+by using the ``source_ip`` option. To bind to IPv4, use::
+
+    $dsn = 'smtp://smtp.example.com?source_ip=0.0.0.0';
+
+As per RFC2732, IPv6 addresses must be enclosed in square brackets. To bind to IPv6, use::
+
+    $dsn = 'smtp://smtp.example.com?source_ip=[::]';
+
+.. note::
+
+    This option only works when using the ``smtp://`` protocol.
+
 Overriding default SMTP authenticators
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -518,6 +610,10 @@ both strings or address objects::
         // email address as a simple string
         ->from('fabien@example.com')
 
+        // non-ASCII characters are supported both in the local part and the domain;
+        // if the SMTP server doesn't support this feature, you'll see an exception
+        ->from('jânë.dœ@ëxãmplę.com')
+
         // email address as an object
         ->from(new Address('fabien@example.com'))
 
@@ -538,6 +634,11 @@ both strings or address objects::
     :ref:`configure emails globally <mailer-configure-email-globally>` to set the
     same ``From`` email to all messages.
 
+.. versionadded:: 7.2
+
+    Support for non-ASCII email addresses (e.g. ``jânë.dœ@ëxãmplę.com``)
+    was introduced in Symfony 7.2.
+
 .. note::
 
     The local part of the address (what goes before the ``@``) can include UTF-8
@@ -576,8 +677,8 @@ header, etc.) but most of the times you'll set text headers::
 
     $email = (new Email())
         ->getHeaders()
-            // this non-standard header tells compliant autoresponders ("email holiday mode") to not
-            // reply to this message because it's an automated email
+            // this non-standard header tells compliant autoresponders ("email holiday mode")
+            // to not reply to this message because it's an automated email
             ->addTextHeader('X-Auto-Response-Suppress', 'OOF, DR, RN, NRN, AutoReply')
 
             // use an array if you want to add a header with multiple values
@@ -791,29 +892,61 @@ Catch that exception to recover from the error or to display some message::
         // error message or try to resend the message
     }
 
+.. _mailer-debugging-emails:
+
 Debugging Emails
 ----------------
 
-The :class:`Symfony\\Component\\Mailer\\SentMessage` object returned by the
-``send()`` method of the :class:`Symfony\\Component\\Mailer\\Transport\\TransportInterface`
-provides access to the original message (``getOriginalMessage()``) and to some
-debug information (``getDebug()``) such as the HTTP calls done by the HTTP
-transports, which is useful to debug errors.
+The ``send()`` method of the mailer service injected when using ``MailerInterface``
+doesn't return anything, so you can't access the sent email information. This is because
+it sends email messages **asynchronously** when the :doc:`Messenger component </messenger>`
+is used in the application.
 
-.. note::
+To access information about the sent email, update your code to replace the
+:class:`Symfony\\Component\\Mailer\\MailerInterface` with
+:class:`Symfony\\Component\\Mailer\\Transport\\TransportInterface`:
+
+.. code-block:: diff
+
+    -use Symfony\Component\Mailer\MailerInterface;
+    +use Symfony\Component\Mailer\Transport\TransportInterface;
+     // ...
+
+     class MailerController extends AbstractController
+     {
+         #[Route('/email')]
+    -    public function sendEmail(MailerInterface $mailer): Response
+    +    public function sendEmail(TransportInterface $mailer): Response
+         {
+             $email = (new Email())
+                 // ...
+
+             $sentEmail = $mailer->send($email);
+
+             // ...
+         }
+     }
+
+The ``send()`` method of ``TransportInterface`` returns an object of type
+:class:`Symfony\\Component\\Mailer\\SentMessage`. This is because it always sends
+the emails **synchronously**, even if your application uses the Messenger component.
 
-    If your code used :class:`Symfony\\Component\\Mailer\\MailerInterface`, you
-    need to replace it by :class:`Symfony\\Component\\Mailer\\Transport\\TransportInterface`
-    to have the ``SentMessage`` object returned.
+The ``SentMessage`` object provides access to the original message
+(``getOriginalMessage()``) and to some debug information (``getDebug()``) such
+as the HTTP calls done by the HTTP transports, which is useful to debug errors.
+
+You can also access the :class:`Symfony\\Component\\Mailer\\SentMessage` object
+by listening to the :ref:`SentMessageEvent <mailer-sent-message-event>`, and retrieve
+``getDebug()`` by listening to the :ref:`FailedMessageEvent <mailer-failed-message-event>`.
 
 .. note::
 
     Some mailer providers change the ``Message-Id`` when sending the email. The
-    ``getMessageId()`` method from ``SentMessage`` always returns the definitive
-    ID of the message (being the original random ID generated by Symfony or the
-    new ID generated by the mailer provider).
+    ``getMessageId()`` method from ``SentMessage`` always returns the final ID
+    of the message - whether it's the original random ID generated by Symfony or
+    a new one generated by the provider.
 
-The exceptions related to mailer transports (those which implement
+Exceptions related to mailer transports (those implementing
 :class:`Symfony\\Component\\Mailer\\Exception\\TransportException`) also provide
 this debug information via the ``getDebug()`` method.
 
@@ -982,6 +1115,18 @@ the email contents:
     <h1>Welcome {{ email.toName }}!</h1>
     {# ... #}
 
+By default this will create an attachment using the file path as file name:
+``Content-Disposition: inline; name="cid..."; filename="@images/logo.png"``.
+This behavior can be overridden by passing a custom file name as the third argument:
+
+.. code-block:: html+twig
+
+    <img src="{{ email.image('@images/logo.png', 'image/png', 'logo-acme.png') }}" alt="ACME Logo">
+
+.. versionadded:: 7.3
+
+    The third argument of ``email.image()`` was introduced in Symfony 7.3.
+
 .. _mailer-inline-css:
 
 Inlining CSS Styles
@@ -1279,6 +1424,81 @@ key but not a certificate::
         ->toArray()
     );
 
+Signing Messages Globally
+.........................
+
+Instead of creating a signer instance for each email, you can configure a global
+signer that automatically applies to all outgoing messages. This approach
+minimizes repetition and centralizes your configuration for DKIM and S/MIME signing.
+
+.. configuration-block::
+
+    .. code-block:: yaml
+
+        # config/packages/mailer.yaml
+        framework:
+            mailer:
+                dkim_signer:
+                    key: 'file://%kernel.project_dir%/var/certificates/dkim.pem'
+                    domain: 'symfony.com'
+                    select: 's1'
+                smime_signer:
+                    key: '%kernel.project_dir%/var/certificates/smime.key'
+                    certificate: '%kernel.project_dir%/var/certificates/smime.crt'
+                    passphrase: ''
+
+    .. code-block:: xml
+
+        <!-- config/packages/mailer.xml -->
+        <?xml version="1.0" encoding="UTF-8" ?>
+        <container xmlns="http://symfony.com/schema/dic/services"
+            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+            xmlns:framework="http://symfony.com/schema/dic/symfony"
+            xsi:schemaLocation="http://symfony.com/schema/dic/services
+                https://symfony.com/schema/dic/services/services-1.0.xsd
+                http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
+
+            <!-- ... -->
+            <framework:config>
+                <framework:mailer>
+                    <framework:dkim-signer>
+                        <framework:key>file://%kernel.project_dir%/var/certificates/dkim.pem</framework:key>
+                        <framework:domain>symfony.com</framework:domain>
+                        <framework:select>s1</framework:select>
+                    </framework:dkim-signer>
+                    <framework:smime-signer>
+                        <framework:key>%kernel.project_dir%/var/certificates/smime.pem</framework:key>
+                        <framework:certificate>%kernel.project_dir%/var/certificates/smime.crt</framework:certificate>
+                        <framework:passphrase></framework:passphrase>
+                    </framework:smime-signer>
+                </framework:mailer>
+            </framework:config>
+        </container>
+
+    .. code-block:: php
+
+        // config/packages/mailer.php
+        use Symfony\Config\FrameworkConfig;
+
+        return static function (FrameworkConfig $framework): void {
+            $mailer = $framework->mailer();
+            $mailer->dsn('%env(MAILER_DSN)%');
+            $mailer->dkimSigner()
+                    ->key('file://%kernel.project_dir%/var/certificates/dkim.pem')
+                    ->domain('symfony.com')
+                    ->select('s1');
+
+            $mailer->smimeSigner()
+                    ->key('%kernel.project_dir%/var/certificates/smime.key')
+                    ->certificate('%kernel.project_dir%/var/certificates/smime.crt')
+                    ->passphrase('')
+            ;
+        };
+
+.. versionadded:: 7.3
+
+    Global message signing was introduced in Symfony 7.3.
+
 Encrypting Messages
 ~~~~~~~~~~~~~~~~~~~
 
@@ -1320,6 +1540,86 @@ and it will select the appropriate certificate depending on the ``To`` option::
     $firstEncryptedEmail = $encrypter->encrypt($firstEmail);
     $secondEncryptedEmail = $encrypter->encrypt($secondEmail);
 
+Encrypting Messages Globally
+............................
+
+Instead of creating a new encrypter for each email, you can configure a global S/MIME
+encrypter that automatically applies to all outgoing messages:
+
+.. configuration-block::
+
+    .. code-block:: yaml
+
+        # config/packages/mailer.yaml
+        framework:
+            mailer:
+                smime_encrypter:
+                    repository: App\Security\LocalFileCertificateRepository
+
+    .. code-block:: xml
+
+        <!-- config/packages/mailer.xml -->
+        <?xml version="1.0" encoding="UTF-8" ?>
+        <container xmlns="http://symfony.com/schema/dic/services"
+            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+            xmlns:framework="http://symfony.com/schema/dic/symfony"
+            xsi:schemaLocation="http://symfony.com/schema/dic/services
+                https://symfony.com/schema/dic/services/services-1.0.xsd
+                http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
+
+            <!-- ... -->
+            <framework:config>
+                <framework:mailer>
+                    <framework:smime-encrypter>
+                        <framework:repository>App\Security\LocalFileCertificateRepository</framework:repository>
+                    </framework:smime-encrypter>
+                </framework:mailer>
+            </framework:config>
+        </container>
+
+    .. code-block:: php
+
+        // config/packages/mailer.php
+        use App\Security\LocalFileCertificateRepository;
+        use Symfony\Config\FrameworkConfig;
+
+        return static function (FrameworkConfig $framework): void {
+            $mailer = $framework->mailer();
+            $mailer->smimeEncrypter()
+                    ->repository(LocalFileCertificateRepository::class)
+            ;
+        };
+
+The ``repository`` option is the ID of a service that implements
+:class:`Symfony\\Component\\Mailer\\EventListener\\SmimeCertificateRepositoryInterface`.
+This interface requires only one method: ``findCertificatePathFor()``, which must
+return the file path to the certificate associated with the given email address::
+
+    namespace App\Security;
+
+    use Symfony\Component\DependencyInjection\Attribute\Autowire;
+    use Symfony\Component\Mailer\EventListener\SmimeCertificateRepositoryInterface;
+
+    class LocalFileCertificateRepository implements SmimeCertificateRepositoryInterface
+    {
+        public function __construct(
+            #[Autowire(param: 'kernel.project_dir')]
+            private readonly string $projectDir
+        ){}
+
+        public function findCertificatePathFor(string $email): ?string
+        {
+            $hash = hash('sha256', strtolower(trim($email)));
+            $path = sprintf('%s/storage/%s.crt', $this->projectDir, $hash);
+
+            return file_exists($path) ? $path : null;
+        }
+    }
+
+.. versionadded:: 7.3
+
+    Global message encryption configuration was introduced in Symfony 7.3.
+
 .. _multiple-email-transports:
 
 Multiple Email Transports
@@ -1363,8 +1663,8 @@ This can be configured by replacing the ``dsn`` configuration entry with a
     .. code-block:: php
 
         // config/packages/mailer.php
-        use function Symfony\Component\DependencyInjection\Loader\Configurator\env;
         use Symfony\Config\FrameworkConfig;
+        use function Symfony\Component\DependencyInjection\Loader\Configurator\env;
 
         return static function (FrameworkConfig $framework): void {
             $framework->mailer()
@@ -1562,6 +1862,7 @@ The following transports currently support tags and metadata:
 
 * Brevo
 * Mailgun
+* Mailtrap
 * Mandrill
 * Postmark
 * Sendgrid
@@ -1667,15 +1968,17 @@ and their priorities:
 
     $ php bin/console debug:event-dispatcher "Symfony\Component\Mailer\Event\MessageEvent"
 
+.. _mailer-sent-message-event:
+
 SentMessageEvent
 ~~~~~~~~~~~~~~~~
 
 **Event Class**: :class:`Symfony\\Component\\Mailer\\Event\\SentMessageEvent`
 
 ``SentMessageEvent`` allows you to act on the :class:`Symfony\\Component\\\Mailer\\\SentMessage`
-class to access the original message (``getOriginalMessage()``) and some debugging
-information (``getDebug()``) such as the HTTP calls made by the HTTP transports,
-which is useful for debugging errors::
+class to access the original message (``getOriginalMessage()``) and some
+:ref:`debugging information <mailer-debugging-emails>` (``getDebug()``) such as
+the HTTP calls made by the HTTP transports, which is useful for debugging errors::
 
     use Symfony\Component\EventDispatcher\EventSubscriberInterface;
     use Symfony\Component\Mailer\Event\SentMessageEvent;
@@ -1684,7 +1987,7 @@ which is useful for debugging errors::
     {
         $message = $event->getMessage();
 
-        // do something with the message
+        // do something with the message (e.g. get its id)
     }
 
 Execute this command to find out which listeners are registered for this event
@@ -1694,20 +1997,28 @@ and their priorities:
 
     $ php bin/console debug:event-dispatcher "Symfony\Component\Mailer\Event\SentMessageEvent"
 
+.. _mailer-failed-message-event:
+
 FailedMessageEvent
 ~~~~~~~~~~~~~~~~~~
 
 **Event Class**: :class:`Symfony\\Component\\Mailer\\Event\\FailedMessageEvent`
 
-``FailedMessageEvent`` allows acting on the initial message in case of a failure::
+``FailedMessageEvent`` allows acting on the initial message in case of a failure
+and some :ref:`debugging information <mailer-debugging-emails>` (``getDebug()``)
+such as the HTTP calls made by the HTTP transports, which is useful for debugging errors::
 
     use Symfony\Component\EventDispatcher\EventSubscriberInterface;
     use Symfony\Component\Mailer\Event\FailedMessageEvent;
+    use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
 
     public function onMessage(FailedMessageEvent $event): void
     {
         // e.g you can get more information on this error when sending an email
-        $event->getError();
+        $error = $event->getError();
+        if ($error instanceof TransportExceptionInterface) {
+            $error->getDebug();
+        }
 
         // do something with the message
     }
@@ -1729,8 +2040,8 @@ Enabling an Email Catcher
 
 When developing locally, it is recommended to use an email catcher. If you have
 enabled Docker support via Symfony recipes, an email catcher is automatically
-configured. In addition, if you are using the :doc:`Symfony local web server
-</setup/symfony_server>`, the mailer DSN is automatically exposed via the
+configured. In addition, if you are using the :doc:`Symfony CLI </setup/symfony_cli>`
+tool, the mailer DSN is automatically exposed via the
 :ref:`symfony binary Docker integration <symfony-server-docker>`.
 
 Sending Test Emails
@@ -1850,9 +2161,9 @@ a specific address, instead of the *real* address:
             ;
         };
 
-Use the ``allowed_recipients`` option to specify exceptions to the behavior defined
-in the ``recipients`` option; allowing emails directed to these specific recipients
-to maintain their original destination:
+Use the ``allowed_recipients`` option to define specific addresses that should
+still receive their original emails. These messages will also be sent to the
+address(es) defined in ``recipients``, as with all other emails:
 
 .. configuration-block::
 
@@ -1911,9 +2222,9 @@ to maintain their original destination:
             ;
         };
 
-With this configuration, all emails will be sent to ``youremail@example.com``,
-except for those sent to ``internal@example.com``, ``internal-monitoring@example.fr``,
-etc., which will receive emails as usual.
+With this configuration, all emails will be sent to ``youremail@example.com``.
+Additionally, emails sent to ``internal@example.com``, ``internal-monitoring@example.fr``,
+etc., will also be delivered to those addresses.
 
 .. versionadded:: 7.1
 
@@ -1957,6 +2268,7 @@ the :class:`Symfony\\Bundle\\FrameworkBundle\\Test\\MailerAssertionsTrait`::
    following the redirection and the message will be lost from the mailer event
    handler.
 
+.. _`AhaSend`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Mailer/Bridge/AhaSend/README.md
 .. _`Amazon SES`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Mailer/Bridge/Amazon/README.md
 .. _`Azure`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Mailer/Bridge/Azure/README.md
 .. _`App Password`: https://support.google.com/accounts/answer/185833
@@ -1975,12 +2287,16 @@ the :class:`Symfony\\Bundle\\FrameworkBundle\\Test\\MailerAssertionsTrait`::
 .. _`Mailgun`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Mailer/Bridge/Mailgun/README.md
 .. _`Mailjet`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Mailer/Bridge/Mailjet/README.md
 .. _`Markdown syntax`: https://commonmark.org/
+.. _`Mailomat`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Mailer/Bridge/Mailomat/README.md
 .. _`MailPace`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Mailer/Bridge/MailPace/README.md
 .. _`OpenSSL PHP extension`: https://www.php.net/manual/en/book.openssl.php
 .. _`PEM encoded`: https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail
+.. _`Postal`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Mailer/Bridge/Postal/README.md
 .. _`Postmark`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Mailer/Bridge/Postmark/README.md
+.. _`Mailtrap`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Mailer/Bridge/Mailtrap/README.md
 .. _`Resend`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Mailer/Bridge/Resend/README.md
 .. _`RFC 3986`: https://www.ietf.org/rfc/rfc3986.txt
 .. _`S/MIME`: https://en.wikipedia.org/wiki/S/MIME
 .. _`Scaleway`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Mailer/Bridge/Scaleway/README.md
 .. _`SendGrid`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Mailer/Bridge/Sendgrid/README.md
+.. _`Sweego`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Mailer/Bridge/Sweego/README.md
diff --git a/mercure.rst b/mercure.rst
index 42ad9798d3c..459a4f18297 100644
--- a/mercure.rst
+++ b/mercure.rst
@@ -11,7 +11,7 @@ notifying the user when :doc:`an asynchronous job </messenger>` has been
 completed or creating chat applications are among the typical use cases
 requiring "push" capabilities.
 
-Symfony provides a straightforward component, built on top of
+Symfony provides a simple component, built on top of
 `the Mercure protocol`_, specifically designed for this class of use cases.
 
 Mercure is an open protocol designed from the ground up to publish updates from
@@ -72,8 +72,9 @@ Thanks to :doc:`the Docker integration of Symfony </setup/docker>`,
 :ref:`Flex <symfony-flex>` proposes to install a Mercure hub for development.
 Run ``docker-compose up`` to start the hub if you have chosen this option.
 
-If you use the :doc:`Symfony Local Web Server </setup/symfony_server>`,
-you must start it with the ``--no-tls`` option.
+If you use the :ref:`Symfony local web server <symfony-cli-server>`,
+you must start it with the ``--no-tls`` option to prevent mixed content and
+invalid TLS certificate issues:
 
 .. code-block:: terminal
 
@@ -282,6 +283,11 @@ URL in a dedicated HTML element:
     {{ mercure('https://example.com/books/1')|json_encode(constant('JSON_UNESCAPED_SLASHES') b-or constant('JSON_HEX_TAG'))|raw }}
     </script>
 
+    <!-- with Stimulus -->
+    <div {{ stimulus_controller('my-controller', {
+        mercureUrl: mercure('https://example.com/books/1'),
+    }) }}>
+
 Then retrieve it from your JS file:
 
 .. code-block:: javascript
@@ -290,6 +296,9 @@ Then retrieve it from your JS file:
     const eventSource = new EventSource(url);
     // ...
 
+    // with Stimulus
+    this.eventSource = new EventSource(this.mercureUrlValue);
+
 Mercure also allows subscribing to several topics,
 and to use URI Templates or the special value ``*`` (matched by all topics)
 as patterns:
@@ -704,6 +713,9 @@ enable it::
     :alt: The Mercure panel of the Symfony Profiler, showing information like time, memory, topics and data of each message sent by Mercure.
     :class: with-browser
 
+The Mercure hub itself provides a debug tool that can be enabled and it's
+available on ``/.well-known/mercure/ui/``
+
 Async dispatching
 -----------------
 
diff --git a/messenger.rst b/messenger.rst
index 97829327d03..9ffb4164426 100644
--- a/messenger.rst
+++ b/messenger.rst
@@ -3,7 +3,7 @@ Messenger: Sync & Queued Message Handling
 
 Messenger provides a message bus with the ability to send messages and then
 handle them immediately in your application or send them through transports
-(e.g. queues) to be handled later. To learn more deeply about it, read the
+(e.g. queues) to be handled later. To learn more about it, read the
 :doc:`Messenger component docs </components/messenger>`.
 
 Installation
@@ -203,8 +203,23 @@ Routing Messages to a Transport
 Now that you have a transport configured, instead of handling a message immediately,
 you can configure them to be sent to a transport:
 
+.. _messenger-message-attribute:
+
 .. configuration-block::
 
+    .. code-block:: php-attributes
+
+        // src/Message/SmsNotification.php
+        namespace App\Message;
+
+        use Symfony\Component\Messenger\Attribute\AsMessage;
+
+        #[AsMessage('async')]
+        class SmsNotification
+        {
+            // ...
+        }
+
     .. code-block:: yaml
 
         # config/packages/messenger.yaml
@@ -251,15 +266,26 @@ you can configure them to be sent to a transport:
             ;
         };
 
+.. versionadded:: 7.2
+
+    The ``#[AsMessage]`` attribute was introduced in Symfony 7.2.
+
 Thanks to this, the ``App\Message\SmsNotification`` will be sent to the ``async``
 transport and its handler(s) will *not* be called immediately. Any messages not
 matched under ``routing`` will still be handled immediately, i.e. synchronously.
 
 .. note::
 
-    You may use a partial PHP namespace like ``'App\Message\*'`` to match all
-    the messages within the matching namespace. The only requirement is that the
-    ``'*'`` wildcard has to be placed at the end of the namespace.
+    If you configure routing with both YAML/XML/PHP configuration files and
+    PHP attributes, the configuration always takes precedence over the class
+    attribute. This behavior allows you to override routing on a per-environment basis.
+
+.. note::
+
+    When configuring the routing in separate YAML/XML/PHP files, you can use a partial
+    PHP namespace like ``'App\Message\*'`` to match all the messages within the
+    matching namespace. The only requirement is that the ``'*'`` wildcard has to
+    be placed at the end of the namespace.
 
     You may use ``'*'`` as the message class. This will act as a default routing
     rule for any message not matched under ``routing``. This is useful to ensure
@@ -275,6 +301,27 @@ to multiple transports:
 
 .. configuration-block::
 
+    .. code-block:: php-attributes
+
+        // src/Message/SmsNotification.php
+        namespace App\Message;
+
+        use Symfony\Component\Messenger\Attribute\AsMessage;
+
+        #[AsMessage(['async', 'audit'])]
+        class SmsNotification
+        {
+            // ...
+        }
+
+        // if you prefer, you can also apply multiple attributes to the message class
+        #[AsMessage('async')]
+        #[AsMessage('audit')]
+        class SmsNotification
+        {
+            // ...
+        }
+
     .. code-block:: yaml
 
         # config/packages/messenger.yaml
@@ -496,6 +543,24 @@ command with the ``--all`` option:
 
     The ``--all`` option was introduced in Symfony 7.1.
 
+Messages that take a long time to process may be redelivered prematurely because
+some transports assume that an unacknowledged message is lost. To prevent this
+issue, use the ``--keepalive`` command option to specify an interval (in seconds;
+default value = ``5``) at which the message is marked as "in progress". This prevents
+the message from being redelivered until the worker completes processing it:
+
+.. code-block:: terminal
+
+    $ php bin/console messenger:consume --keepalive
+
+.. note::
+
+    This option is only available for the following transports: Beanstalkd, AmazonSQS, Doctrine and Redis.
+
+.. versionadded:: 7.2
+
+    The ``--keepalive`` option was introduced in Symfony 7.2.
+
 .. tip::
 
     In a development environment and if you're using the Symfony CLI tool,
@@ -688,6 +753,14 @@ of some or all transports:
     # shows stats only for some transports
     $ php bin/console messenger:stats my_transport_name other_transport_name
 
+    # you can also output the stats in JSON format
+    $ php bin/console messenger:stats --format=json
+    $ php bin/console messenger:stats my_transport_name other_transport_name --format=json
+
+.. versionadded:: 7.2
+
+    The ``format`` option was introduced in Symfony 7.2.
+
 .. note::
 
     In order for this command to work, the configured transport's receiver must implement
@@ -776,7 +849,56 @@ message before terminating.
 
 However, you might prefer to use different POSIX signals for graceful shutdown.
 You can override default ones by setting the ``framework.messenger.stop_worker_on_signals``
-configuration option.
+configuration option:
+
+.. configuration-block::
+
+    .. code-block:: yaml
+
+        # config/packages/messenger.yaml
+        framework:
+            messenger:
+                stop_worker_on_signals:
+                    - SIGTERM
+                    - SIGINT
+                    - SIGUSR1
+
+    .. code-block:: xml
+
+        <!-- config/packages/messenger.xml -->
+        <?xml version="1.0" encoding="UTF-8" ?>
+        <container xmlns="http://symfony.com/schema/dic/services"
+                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                   xmlns:framework="http://symfony.com/schema/dic/symfony"
+                   xsi:schemaLocation="http://symfony.com/schema/dic/services
+                https://symfony.com/schema/dic/services/services-1.0.xsd
+                http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
+
+            <framework:config>
+                <framework:messenger>
+                    <!-- ... -->
+                    <framework:stop-worker-on-signal>SIGTERM</framework:stop-worker-on-signal>
+                    <framework:stop-worker-on-signal>SIGINT</framework:stop-worker-on-signal>
+                    <framework:stop-worker-on-signal>SIGUSR1</framework:stop-worker-on-signal>
+                </framework:messenger>
+            </framework:config>
+        </container>
+
+    .. code-block:: php
+
+        // config/packages/messenger.php
+        use Symfony\Config\FrameworkConfig;
+
+        return static function (FrameworkConfig $framework): void {
+            $framework->messenger()
+                ->stopWorkerOnSignals(['SIGTERM', 'SIGINT', 'SIGUSR1']);
+        };
+
+.. versionadded:: 7.3
+
+    Support for signals plain names in configuration was introduced in Symfony 7.3.
+    Previously, you had to use the numeric values of signals as defined by the
+    ``pcntl`` extension's `predefined constants`_.
 
 In some cases the ``SIGTERM`` signal is sent by Supervisor itself (e.g. stopping
 a Docker container having Supervisor as its entrypoint). In these cases you
@@ -808,6 +930,8 @@ directory. For example, you can create a new ``messenger-worker.service`` file.
 
     [Service]
     ExecStart=php /path/to/your/app/bin/console messenger:consume async --time-limit=3600
+    # for Redis, set a custom consumer name for each instance
+    Environment="MESSENGER_CONSUMER_NAME=symfony-%n-%i"
     Restart=always
     RestartSec=30
 
@@ -1079,6 +1203,15 @@ and must be retried. If you throw
 :class:`Symfony\\Component\\Messenger\\Exception\\RecoverableMessageHandlingException`,
 the message will always be retried infinitely and ``max_retries`` setting will be ignored.
 
+You can define a custom retry delay (e.g., to use the value from the ``Retry-After``
+header in an HTTP response) by setting the ``retryDelay`` argument in the
+constructor of the ``RecoverableMessageHandlingException``.
+
+.. versionadded:: 7.2
+
+    The ``retryDelay`` argument and the ``getRetryDelay()`` method were introduced
+    in Symfony 7.2.
+
 .. _messenger-failure-transport:
 
 Saving & Retrying Failed Messages
@@ -1155,8 +1288,8 @@ to retry them:
     # see the 10 first messages
     $ php bin/console messenger:failed:show --max=10
 
-    # see only MyClass messages
-    $ php bin/console messenger:failed:show --class-filter='MyClass'
+    # see only App\Message\MyMessage messages
+    $ php bin/console messenger:failed:show --class-filter='App\Message\MyMessage'
 
     # see the number of messages by message class
     $ php bin/console messenger:failed:show --stats
@@ -1164,7 +1297,7 @@ to retry them:
     # see details about a specific failure
     $ php bin/console messenger:failed:show 20 -vv
 
-    # view and retry messages one-by-one
+    # for each message, this command asks whether to retry, skip, or delete
     $ php bin/console messenger:failed:retry -vv
 
     # retry specific messages
@@ -1179,10 +1312,23 @@ to retry them:
     # remove all messages in the failure transport
     $ php bin/console messenger:failed:remove --all
 
+    # remove only App\Message\MyMessage messages
+    $ php bin/console messenger:failed:remove --class-filter='App\Message\MyMessage'
+
 If the message fails again, it will be re-sent back to the failure transport
 due to the normal :ref:`retry rules <messenger-retries-failures>`. Once the max
 retry has been hit, the message will be discarded permanently.
 
+.. versionadded:: 7.2
+
+    The option to skip a message in the ``messenger:failed:retry`` command was
+    introduced in Symfony 7.2
+
+.. versionadded:: 7.3
+
+    The option to filter by a message class in the ``messenger:failed:remove`` command was
+    introduced in Symfony 7.3
+
 Multiple Failed Transports
 ~~~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -1358,7 +1504,7 @@ RabbitMQ. Install it by running:
 
     $ composer require symfony/amqp-messenger
 
-The AMQP transport DSN may looks like this:
+The AMQP transport DSN may look like this:
 
 .. code-block:: env
 
@@ -1499,11 +1645,15 @@ The transport has a number of options:
     Exchange flags
 
 ``exchange[name]``
-    Name of the exchange
+    Name of the exchange. Use an empty string to use the default exchange.
 
 ``exchange[type]`` (default: ``fanout``)
     Type of exchange
 
+.. versionadded:: 7.3
+
+    Empty string support for ``exchange[name]`` was introduced in Symfony 7.3.
+
 You can also configure AMQP-specific settings on your message by adding
 :class:`Symfony\\Component\\Messenger\\Bridge\\Amqp\\Transport\\AmqpStamp` to
 your Envelope::
@@ -1545,7 +1695,7 @@ Install it by running:
 
     $ composer require symfony/doctrine-messenger
 
-The Doctrine transport DSN may looks like this:
+The Doctrine transport DSN may look like this:
 
 .. code-block:: env
 
@@ -1583,7 +1733,7 @@ The transport has a number of options:
 
     .. note::
 
-        Set ``redeliver_timeout`` to a greater value than your slowest message
+        Set ``redeliver_timeout`` to a greater value than your longest message
         duration. Otherwise, some messages will start a second time while the
         first one is still being handled.
 
@@ -1607,6 +1757,13 @@ in the table.
     The length of time to wait for a response when calling
     ``PDO::pgsqlGetNotify``, in milliseconds.
 
+The Doctrine transport supports the ``--keepalive`` option by periodically updating
+the ``delivered_at`` timestamp to prevent the message from being redelivered.
+
+.. versionadded:: 7.3
+
+    Keepalive support was introduced in Symfony 7.3.
+
 Beanstalkd Transport
 ~~~~~~~~~~~~~~~~~~~~
 
@@ -1629,8 +1786,13 @@ The Beanstalkd transport DSN may looks like this:
 
 The transport has a number of options:
 
-``tube_name`` (default: ``default``)
-    Name of the queue
+``bury_on_reject`` (default: ``false``)
+    When set to ``true``, rejected messages are placed into a "buried" state
+    in Beanstalkd instead of being deleted.
+
+    .. versionadded:: 7.3
+
+        The ``bury_on_reject`` option was introduced in Symfony 7.3.
 
 ``timeout`` (default: ``0``)
     Message reservation timeout - in seconds. 0 will cause the server to
@@ -1640,6 +1802,33 @@ The transport has a number of options:
     The message time to run before it is put back in the ready queue - in
     seconds.
 
+``tube_name`` (default: ``default``)
+    Name of the queue
+
+The Beanstalkd transport supports the ``--keepalive`` option by using Beanstalkd's
+``touch`` command to periodically reset the job's ``ttr``.
+
+.. versionadded:: 7.2
+
+    Keepalive support was introduced in Symfony 7.2.
+
+The Beanstalkd transport lets you set the priority of the messages being dispatched.
+Use the :class:`Symfony\\Component\\Messenger\\Bridge\\Beanstalkd\\Transport\\BeanstalkdPriorityStamp`
+and pass a number to specify the priority (default = ``1024``; lower numbers mean higher priority)::
+
+    use App\Message\SomeMessage;
+    use Symfony\Component\Messenger\Stamp\BeanstalkdPriorityStamp;
+
+    $this->bus->dispatch(new SomeMessage('some data'), [
+        // 0 = highest priority
+        // 2**32 - 1 = lowest priority
+        new BeanstalkdPriorityStamp(0),
+    ]);
+
+.. versionadded:: 7.3
+
+    ``BeanstalkdPriorityStamp`` support was introduced in Symfony 7.3.
+
 .. _messenger-redis-transport:
 
 Redis Transport
@@ -1680,7 +1869,20 @@ under the transport in ``messenger.yaml``:
     The Redis consumer group name
 
 ``consumer`` (default: ``consumer``)
-    Consumer name used in Redis
+    Consumer name used in Redis. Allows setting an explicit consumer name identifier.
+    Recommended in environments with multiple workers to prevent duplicate message
+    processing. Typically set via an environment variable:
+
+    .. code-block:: yaml
+
+        # config/packages/messenger.yaml
+        framework:
+            messenger:
+                transports:
+                    redis:
+                        dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
+                        options:
+                            consumer: '%env(MESSENGER_CONSUMER_NAME)%'
 
 ``auto_setup`` (default: ``true``)
     Whether to create the Redis group automatically
@@ -1773,6 +1975,13 @@ under the transport in ``messenger.yaml``:
     in your case) to avoid memory leaks. Otherwise, all messages will remain
     forever in Redis.
 
+The Redis transport supports the ``--keepalive`` option by using Redis's ``XCLAIM``
+command to periodically reset the message's idle time to zero.
+
+.. versionadded:: 7.3
+
+    Keepalive support was introduced in Symfony 7.3.
+
 In Memory Transport
 ~~~~~~~~~~~~~~~~~~~
 
@@ -1915,6 +2124,12 @@ The transport has a number of options:
 ``queue_name`` (default: ``messages``)
     Name of the queue
 
+``queue_attributes``
+    Attributes of a queue as per `SQS CreateQueue API`_. Array of strings indexed by keys of ``AsyncAws\Sqs\Enum\QueueAttributeName``.
+
+``queue_tags``
+    Cost allocation tags of a queue as per `SQS CreateQueue API`_. Array of strings indexed by strings.
+
 ``region`` (default: ``eu-west-1``)
     Name of the AWS region
 
@@ -1930,6 +2145,10 @@ The transport has a number of options:
 ``wait_time`` (default: ``20``)
     `Long polling`_ duration in seconds
 
+.. versionadded:: 7.3
+
+    The ``queue_attributes`` and ``queue_tags`` options were introduced in Symfony 7.3.
+
 .. note::
 
     The ``wait_time`` parameter defines the maximum duration Amazon SQS should
@@ -1962,6 +2181,13 @@ The transport has a number of options:
     FIFO queues don't support setting a delay per message, a value of ``delay: 0``
     is required in the retry strategy settings.
 
+The SQS transport supports the ``--keepalive`` option by using the ``ChangeMessageVisibility``
+action to periodically update the ``VisibilityTimeout`` of the message.
+
+.. versionadded:: 7.2
+
+    Keepalive support was introduced in Symfony 7.2.
+
 Serializing Messages
 ~~~~~~~~~~~~~~~~~~~~
 
@@ -2045,6 +2271,22 @@ on a case-by-case basis via the :class:`Symfony\\Component\\Messenger\\Stamp\\Se
     provides that control. See `SymfonyCasts' message serializer tutorial`_ for
     details.
 
+Closing Connections
+~~~~~~~~~~~~~~~~~~~
+
+When using a transport that requires a connection, you can close it by calling the
+:method:`Symfony\\Component\\Messenger\\Transport\\CloseableTransportInterface::close`
+method to free up resources in long-running processes.
+
+This interface is implemented by the following transports: AmazonSqs, Amqp, and Redis.
+If you need to close a Doctrine connection, you can do so
+:ref:`using middleware <middleware-for-doctrine>`.
+
+.. versionadded:: 7.3
+
+    The ``CloseableTransportInterface`` and its ``close()`` method were introduced
+    in Symfony 7.3.
+
 Running Commands And External Processes
 ---------------------------------------
 
@@ -2100,8 +2342,9 @@ will take care of creating a new process with the parameters you passed::
 
     class CleanUpService
     {
-        public function __construct(private readonly MessageBusInterface $bus)
-        {
+        public function __construct(
+            private readonly MessageBusInterface $bus,
+        ) {
         }
 
         public function cleanUp(): void
@@ -2112,6 +2355,34 @@ will take care of creating a new process with the parameters you passed::
         }
     }
 
+If you want to use shell features such as redirections or pipes, use the static
+:method:`Symfony\\Component\\Process\\Messenger\\RunProcessMessage::fromShellCommandline` factory method::
+
+    use Symfony\Component\Messenger\MessageBusInterface;
+    use Symfony\Component\Process\Messenger\RunProcessMessage;
+
+    class CleanUpService
+    {
+        public function __construct(
+            private readonly MessageBusInterface $bus,
+        ) {
+        }
+
+        public function cleanUp(): void
+        {
+            $this->bus->dispatch(RunProcessMessage::fromShellCommandline('echo "Hello World" > var/log/hello.txt'));
+
+            // ...
+        }
+    }
+
+For more information, read the documentation about
+:ref:`using features from the OS shell <process-using-features-from-the-os-shell>`.
+
+.. versionadded:: 7.3
+
+    The ``RunProcessMessage::fromShellCommandline()`` method was introduced in Symfony 7.3.
+
 Once handled, the handler will return a
 :class:`Symfony\\Component\\Process\\Messenger\\RunProcessContext` which
 contains many useful information such as the exit code or the output of the
@@ -2258,6 +2529,15 @@ wherever you need a query bus behavior instead of the ``MessageBusInterface``::
         }
     }
 
+You can also add new stamps when handling a message; they will be appended
+to the existing ones::
+
+    $this->handle(new SomeMessage($data), [new SomeStamp(), new AnotherStamp()]);
+
+.. versionadded:: 7.3
+
+    The ``$stamps`` parameter of the ``handle()`` method was introduced in Symfony 7.3.
+
 Customizing Handlers
 --------------------
 
@@ -2352,7 +2632,8 @@ Possible options to configure with tags are:
     Name of the method that will process the message.
 
 ``priority``
-    Priority of the handler when multiple handlers can process the same message.
+    Defines the order in which the handler is executed when multiple handlers
+    can process the same message; those with higher priority run first.
 
 .. _handler-subscriber-options:
 
@@ -2478,7 +2759,7 @@ using the ``DispatchAfterCurrentBusMiddleware`` and adding a
     {
         public function __construct(
             private MailerInterface $mailer,
-            EntityManagerInterface $em,
+            private EntityManagerInterface $em,
         ) {
         }
 
@@ -2830,6 +3111,11 @@ and a different instance will be created per bus.
             $bus->middleware()->id('App\Middleware\AnotherMiddleware');
         };
 
+.. tip::
+
+    If you have installed the MakerBundle, you can use the ``make:messenger-middleware``
+    command to bootstrap the creation of your own messenger middleware.
+
 .. _middleware-doctrine:
 
 Middleware for Doctrine
@@ -3478,3 +3764,5 @@ Learn more
 .. _`high connection churn`: https://www.rabbitmq.com/connections.html#high-connection-churn
 .. _`article about CQRS`: https://martinfowler.com/bliki/CQRS.html
 .. _`SSL context options`: https://php.net/context.ssl
+.. _`predefined constants`: https://www.php.net/pcntl.constants
+.. _`SQS CreateQueue API`: https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_CreateQueue.html
diff --git a/notifier.rst b/notifier.rst
index b74f4eb02e0..49a1c2d533b 100644
--- a/notifier.rst
+++ b/notifier.rst
@@ -33,7 +33,14 @@ The notifier component supports the following channels:
   services like Slack and Telegram;
 * :ref:`Email channel <notifier-email-channel>` integrates the :doc:`Symfony Mailer </mailer>`;
 * Browser channel uses :ref:`flash messages <flash-messages>`.
-* :ref:`Push channel <notifier-push-channel>` sends notifications to phones and browsers via push notifications.
+* :ref:`Push channel <notifier-push-channel>` sends notifications to phones and
+  browsers via push notifications.
+* :ref:`Desktop channel <notifier-desktop-channel>` displays desktop notifications
+  on the same host machine.
+
+.. versionadded:: 7.2
+
+    The ``Desktop`` channel was introduced in Symfony 7.2.
 
 .. _notifier-sms-channel:
 
@@ -61,6 +68,7 @@ Service
 `AllMySms`_         **Install**: ``composer require symfony/all-my-sms-notifier`` \
                     **DSN**: ``allmysms://LOGIN:APIKEY@default?from=FROM`` \
                     **Webhook support**: No
+                    **Extra properties in SentMessage**: ``nbSms``, ``balance``, ``cost``
 `AmazonSns`_        **Install**: ``composer require symfony/amazon-sns-notifier`` \
                     **DSN**: ``sns://ACCESS_KEY:SECRET_KEY@default?region=REGION`` \
                     **Webhook support**: No
@@ -69,7 +77,7 @@ Service
                     **Webhook support**: No
 `Brevo`_            **Install**: ``composer require symfony/brevo-notifier`` \
                     **DSN**: ``brevo://API_KEY@default?sender=SENDER`` \
-                    **Webhook support**: No
+                    **Webhook support**: Yes
 `Clickatell`_       **Install**: ``composer require symfony/clickatell-notifier`` \
                     **DSN**: ``clickatell://ACCESS_TOKEN@default?from=FROM`` \
                     **Webhook support**: No
@@ -132,9 +140,13 @@ Service
 `OvhCloud`_         **Install**: ``composer require symfony/ovh-cloud-notifier`` \
                     **DSN**: ``ovhcloud://APPLICATION_KEY:APPLICATION_SECRET@default?consumer_key=CONSUMER_KEY&service_name=SERVICE_NAME`` \
                     **Webhook support**: No
+                    **Extra properties in SentMessage**:: ``totalCreditsRemoved``
 `Plivo`_            **Install**: ``composer require symfony/plivo-notifier`` \
                     **DSN**: ``plivo://AUTH_ID:AUTH_TOKEN@default?from=FROM`` \
                     **Webhook support**: No
+`Primotexto`_       **Install**: ``composer require symfony/primotexto-notifier`` \
+                    **DSN**: ``primotexto://API_KEY@default?from=FROM`` \
+                    **Webhook support**: No
 `Redlink`_          **Install**: ``composer require symfony/redlink-notifier`` \
                     **DSN**: ``redlink://API_KEY:APP_KEY@default?from=SENDER_NAME&version=API_VERSION`` \
                     **Webhook support**: No
@@ -156,6 +168,9 @@ Service
 `Sinch`_            **Install**: ``composer require symfony/sinch-notifier`` \
                     **DSN**: ``sinch://ACCOUNT_ID:AUTH_TOKEN@default?from=FROM`` \
                     **Webhook support**: No
+`Sipgate`_          **Install**: ``composer require symfony/sipgate-notifier`` \
+                    **DSN**: ``sipgate://TOKEN_ID:TOKEN@default?senderId=SENDER_ID`` \
+                    **Webhook support**: No
 `SmsSluzba`_        **Install**: ``composer require symfony/sms-sluzba-notifier`` \
                     **DSN**: ``sms-sluzba://USERNAME:PASSWORD@default`` \
                     **Webhook support**: No
@@ -164,7 +179,7 @@ Service
                     **Webhook support**: No
 `Smsbox`_           **Install**: ``composer require symfony/smsbox-notifier`` \
                     **DSN**: ``smsbox://APIKEY@default?mode=MODE&strategy=STRATEGY&sender=SENDER`` \
-                    **Webhook support**: No
+                    **Webhook support**: Yes
 `SmsBiuras`_        **Install**: ``composer require symfony/sms-biuras-notifier`` \
                     **DSN**: ``smsbiuras://UID:API_KEY@default?from=FROM&test_mode=0`` \
                     **Webhook support**: No
@@ -180,6 +195,9 @@ Service
 `SpotHit`_          **Install**: ``composer require symfony/spot-hit-notifier`` \
                     **DSN**: ``spothit://TOKEN@default?from=FROM`` \
                     **Webhook support**: No
+`Sweego`_           **Install**: ``composer require symfony/sweego-notifier`` \
+                    **DSN**: ``sweego://API_KEY@default?region=REGION&campaign_type=CAMPAIGN_TYPE`` \
+                    **Webhook support**: Yes
 `Telnyx`_           **Install**: ``composer require symfony/telnyx-notifier`` \
                     **DSN**: ``telnyx://API_KEY@default?from=FROM&messaging_profile_id=MESSAGING_PROFILE_ID`` \
                     **Webhook support**: No
@@ -216,6 +234,16 @@ Service
     The ``Smsbox``, ``SmsSluzba``, ``SMSense``, ``LOX24`` and ``Unifonic``
     integrations were introduced in Symfony 7.1.
 
+.. versionadded:: 7.2
+
+    The ``Primotexto``, ``Sipgate`` and ``Sweego`` integrations were introduced in Symfony 7.2.
+
+.. versionadded:: 7.3
+
+    Webhook support for the ``Brevo`` integration was introduced in Symfony 7.3.
+    The extra properties in ``SentMessage`` for ``AllMySms`` and ``OvhCloud``
+    providers were introduced in Symfony 7.3 too.
+
 .. deprecated:: 7.1
 
     The `Sms77`_ integration is deprecated since
@@ -330,35 +358,71 @@ The chat channel is used to send chat messages to users by using
 :class:`Symfony\\Component\\Notifier\\Chatter` classes. Symfony provides
 integration with these chat services:
 
-=======================================  ====================================  =============================================================================
-Service                                  Package                               DSN
-=======================================  ====================================  =============================================================================
-`AmazonSns`_                             ``symfony/amazon-sns-notifier``       ``sns://ACCESS_KEY:SECRET_KEY@default?region=REGION``
-`Bluesky`_                               ``symfony/bluesky-notifier``          ``bluesky://USERNAME:PASSWORD@default``
-`Chatwork`_                              ``symfony/chatwork-notifier``         ``chatwork://API_TOKEN@default?room_id=ID``
-`Discord`_                               ``symfony/discord-notifier``          ``discord://TOKEN@default?webhook_id=ID``
-`FakeChat`_                              ``symfony/fake-chat-notifier``        ``fakechat+email://default?to=TO&from=FROM`` or ``fakechat+logger://default``
-`Firebase`_                              ``symfony/firebase-notifier``         ``firebase://USERNAME:PASSWORD@default``
-`Gitter`_                                ``symfony/gitter-notifier``           ``gitter://TOKEN@default?room_id=ROOM_ID``
-`GoogleChat`_                            ``symfony/google-chat-notifier``      ``googlechat://ACCESS_KEY:ACCESS_TOKEN@default/SPACE?thread_key=THREAD_KEY``
-`LINE Notify`_                           ``symfony/line-notify-notifier``      ``linenotify://TOKEN@default``
-`LinkedIn`_                              ``symfony/linked-in-notifier``        ``linkedin://TOKEN:USER_ID@default``
-`Mastodon`_                              ``symfony/mastodon-notifier``         ``mastodon://ACCESS_TOKEN@HOST``
-`Mattermost`_                            ``symfony/mattermost-notifier``       ``mattermost://ACCESS_TOKEN@HOST/PATH?channel=CHANNEL``
-`Mercure`_                               ``symfony/mercure-notifier``          ``mercure://HUB_ID?topic=TOPIC``
-`MicrosoftTeams`_                        ``symfony/microsoft-teams-notifier``  ``microsoftteams://default/PATH``
-`RocketChat`_                            ``symfony/rocket-chat-notifier``      ``rocketchat://TOKEN@ENDPOINT?channel=CHANNEL``
-`Slack`_                                 ``symfony/slack-notifier``            ``slack://TOKEN@default?channel=CHANNEL``
-`Telegram`_                              ``symfony/telegram-notifier``         ``telegram://TOKEN@default?channel=CHAT_ID``
-`Twitter`_                               ``symfony/twitter-notifier``          ``twitter://API_KEY:API_SECRET:ACCESS_TOKEN:ACCESS_SECRET@default``
-`Zendesk`_                               ``symfony/zendesk-notifier``          ``zendesk://EMAIL:TOKEN@SUBDOMAIN``
-`Zulip`_                                 ``symfony/zulip-notifier``            ``zulip://EMAIL:TOKEN@HOST?channel=CHANNEL``
-======================================   ====================================  =============================================================================
+======================================   =====================================================================================
+Service
+======================================   =====================================================================================
+`AmazonSns`_                             **Install**: ``composer require symfony/amazon-sns-notifier`` \
+                                         **DSN**: ``sns://ACCESS_KEY:SECRET_KEY@default?region=REGION``
+`Bluesky`_                               **Install**: ``composer require symfony/bluesky-notifier`` \
+                                         **DSN**: ``bluesky://USERNAME:PASSWORD@default``
+                                         **Extra properties in SentMessage**: ``cid``
+`Chatwork`_                              **Install**: ``composer require symfony/chatwork-notifier`` \
+                                         **DSN**: ``chatwork://API_TOKEN@default?room_id=ID``
+`Discord`_                               **Install**: ``composer require symfony/discord-notifier`` \
+                                         **DSN**: ``discord://TOKEN@default?webhook_id=ID``
+`FakeChat`_                              **Install**: ``composer require symfony/fake-chat-notifier`` \
+                                         **DSN**: ``fakechat+email://default?to=TO&from=FROM`` or ``fakechat+logger://default``
+`Firebase`_                              **Install**: ``composer require symfony/firebase-notifier`` \
+                                         **DSN**: ``firebase://USERNAME:PASSWORD@default``
+`GoogleChat`_                            **Install**: ``composer require symfony/google-chat-notifier`` \
+                                         **DSN**: ``googlechat://ACCESS_KEY:ACCESS_TOKEN@default/SPACE?thread_key=THREAD_KEY``
+`LINE Bot`_                              **Install**: ``composer require symfony/line-bot-notifier`` \
+                                         **DSN**: ``linebot://TOKEN@default?receiver=RECEIVER``
+`LINE Notify`_                           **Install**: ``composer require symfony/line-notify-notifier`` \
+                                         **DSN**: ``linenotify://TOKEN@default``
+`LinkedIn`_                              **Install**: ``composer require symfony/linked-in-notifier`` \
+                                         **DSN**: ``linkedin://TOKEN:USER_ID@default``
+`Mastodon`_                              **Install**: ``composer require symfony/mastodon-notifier`` \
+                                         **DSN**: ``mastodon://ACCESS_TOKEN@HOST``
+`Matrix`_                                **Install**: ``composer require symfony/matrix-notifier`` \
+                                         **DSN**: ``matrix://HOST:PORT/?accessToken=ACCESSTOKEN&ssl=SSL``
+`Mattermost`_                            **Install**: ``composer require symfony/mattermost-notifier`` \
+                                         **DSN**: ``mattermost://ACCESS_TOKEN@HOST/PATH?channel=CHANNEL``
+`Mercure`_                               **Install**: ``composer require symfony/mercure-notifier`` \
+                                         **DSN**: ``mercure://HUB_ID?topic=TOPIC``
+`MicrosoftTeams`_                        **Install**: ``composer require symfony/microsoft-teams-notifier`` \
+                                         **DSN**: ``microsoftteams://default/PATH``
+`RocketChat`_                            **Install**: ``composer require symfony/rocket-chat-notifier`` \
+                                         **DSN**: ``rocketchat://TOKEN@ENDPOINT?channel=CHANNEL``
+`Slack`_                                 **Install**: ``composer require symfony/slack-notifier`` \
+                                         **DSN**: ``slack://TOKEN@default?channel=CHANNEL``
+`Telegram`_                              **Install**: ``composer require symfony/telegram-notifier`` \
+                                         **DSN**: ``telegram://TOKEN@default?channel=CHAT_ID``
+`Twitter`_                               **Install**: ``composer require symfony/twitter-notifier`` \
+                                         **DSN**: ``twitter://API_KEY:API_SECRET:ACCESS_TOKEN:ACCESS_SECRET@default``
+`Zendesk`_                               **Install**: ``composer require symfony/zendesk-notifier`` \
+                                         **DSN**: ``zendesk://EMAIL:TOKEN@SUBDOMAIN``
+`Zulip`_                                 **Install**: ``composer require symfony/zulip-notifier`` \
+                                         **DSN**: ``zulip://EMAIL:TOKEN@HOST?channel=CHANNEL``
+======================================   =====================================================================================
 
 .. versionadded:: 7.1
 
     The ``Bluesky`` integration was introduced in Symfony 7.1.
 
+.. versionadded:: 7.2
+
+    The ``LINE Bot`` integration was introduced in Symfony 7.2.
+
+.. deprecated:: 7.2
+
+    The ``Gitter`` integration was removed in Symfony 7.2 because that service
+    no longer provides an API.
+
+.. versionadded:: 7.3
+
+    The ``Matrix`` integration was introduced in Symfony 7.3.
+
 .. warning::
 
     By default, if you have the :doc:`Messenger component </messenger>` installed,
@@ -540,18 +604,26 @@ The push channel is used to send notifications to users by using
 :class:`Symfony\\Component\\Notifier\\Texter` classes. Symfony provides
 integration with these push services:
 
-===============  ====================================  ==============================================================================
-Service          Package                               DSN
-===============  ====================================  ==============================================================================
-`Engagespot`_    ``symfony/engagespot-notifier``       ``engagespot://API_KEY@default?campaign_name=CAMPAIGN_NAME``
-`Expo`_          ``symfony/expo-notifier``             ``expo://Token@default``
-`Novu`_          ``symfony/novu-notifier``             ``novu://API_KEY@default``
-`Ntfy`_          ``symfony/ntfy-notifier``             ``ntfy://default/TOPIC``
-`OneSignal`_     ``symfony/one-signal-notifier``       ``onesignal://APP_ID:API_KEY@default?defaultRecipientId=DEFAULT_RECIPIENT_ID``
-`PagerDuty`_     ``symfony/pager-duty-notifier``       ``pagerduty://TOKEN@SUBDOMAIN``
-`Pushover`_      ``symfony/pushover-notifier``         ``pushover://USER_KEY:APP_TOKEN@default``
-`Pushy`_         ``symfony/pushy-notifier``            ``pushy://API_KEY@default``
-===============  ====================================  ==============================================================================
+===============  =======================================================================================
+Service
+===============  =======================================================================================
+`Engagespot`_    **Install**: ``composer require symfony/engagespot-notifier`` \
+                 **DSN**: ``engagespot://API_KEY@default?campaign_name=CAMPAIGN_NAME``
+`Expo`_          **Install**: ``composer require symfony/expo-notifier`` \
+                 **DSN**: ``expo://TOKEN@default``
+`Novu`_          **Install**: ``composer require symfony/novu-notifier`` \
+                 **DSN**: ``novu://API_KEY@default``
+`Ntfy`_          **Install**: ``composer require symfony/ntfy-notifier`` \
+                 **DSN**: ``ntfy://default/TOPIC``
+`OneSignal`_     **Install**: ``composer require symfony/one-signal-notifier`` \
+                 **DSN**: ``onesignal://APP_ID:API_KEY@default?defaultRecipientId=DEFAULT_RECIPIENT_ID``
+`PagerDuty`_     **Install**: ``composer require symfony/pager-duty-notifier`` \
+                 **DSN**: ``pagerduty://TOKEN@SUBDOMAIN``
+`Pushover`_      **Install**: ``composer require symfony/pushover-notifier`` \
+                 **DSN**: ``pushover://USER_KEY:APP_TOKEN@default``
+`Pushy`_         **Install**: ``composer require symfony/pushy-notifier`` \
+                 **DSN**: ``pushy://API_KEY@default``
+===============  =======================================================================================
 
 To enable a texter, add the correct DSN in your ``.env`` file and
 configure the ``texter_transports``:
@@ -607,6 +679,124 @@ configure the ``texter_transports``:
             ;
         };
 
+.. _notifier-desktop-channel:
+
+Desktop Channel
+~~~~~~~~~~~~~~~
+
+The desktop channel is used to display local desktop notifications on the same
+host machine using :class:`Symfony\\Component\\Notifier\\Texter` classes. Currently,
+Symfony is integrated with the following providers:
+
+===============  ================================================  ==============================================================================
+Provider         Install                                           DSN
+===============  ================================================  ==============================================================================
+`JoliNotif`_     ``composer require symfony/joli-notif-notifier``  ``jolinotif://default``
+===============  ================================================  ==============================================================================
+
+.. versionadded:: 7.2
+
+    The JoliNotif bridge was introduced in Symfony 7.2.
+
+If you are using :ref:`Symfony Flex <symfony-flex>`, installing that package will
+also create the necessary environment variable and configuration. Otherwise, you'll
+need to add the following manually:
+
+1) Add the correct DSN in your ``.env`` file:
+
+.. code-block:: bash
+
+    # .env
+    JOLINOTIF=jolinotif://default
+
+2) Update the Notifier configuration to add a new texter transport:
+
+.. configuration-block::
+
+    .. code-block:: yaml
+
+        # config/packages/notifier.yaml
+        framework:
+            notifier:
+                texter_transports:
+                    jolinotif: '%env(JOLINOTIF)%'
+
+    .. code-block:: xml
+
+        <!-- config/packages/notifier.xml -->
+        <?xml version="1.0" encoding="UTF-8" ?>
+        <container xmlns="http://symfony.com/schema/dic/services"
+            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+            xmlns:framework="http://symfony.com/schema/dic/symfony"
+            xsi:schemaLocation="http://symfony.com/schema/dic/services
+                https://symfony.com/schema/dic/services/services-1.0.xsd
+                http://symfony.com/schema/dic/symfony
+                https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
+
+            <framework:config>
+                <framework:notifier>
+                    <framework:texter-transport name="jolinotif">
+                        %env(JOLINOTIF)%
+                    </framework:texter-transport>
+                </framework:notifier>
+            </framework:config>
+        </container>
+
+    .. code-block:: php
+
+        // config/packages/notifier.php
+        use Symfony\Config\FrameworkConfig;
+
+        return static function (FrameworkConfig $framework): void {
+            $framework->notifier()
+                ->texterTransport('jolinotif', env('JOLINOTIF'))
+            ;
+        };
+
+Now you can send notifications to your desktop as follows::
+
+    // src/Notifier/SomeService.php
+    use Symfony\Component\Notifier\Message\DesktopMessage;
+    use Symfony\Component\Notifier\TexterInterface;
+    // ...
+
+    class SomeService
+    {
+        public function __construct(
+            private TexterInterface $texter,
+        ) {
+        }
+
+        public function notifyNewSubscriber(User $user): void
+        {
+            $message = new DesktopMessage(
+                'New subscription! 🎉',
+                sprintf('%s is a new subscriber', $user->getFullName())
+            );
+
+            $this->texter->send($message);
+        }
+    }
+
+These notifications can be customized further, and depending on your operating system,
+they may support features like custom sounds, icons, and more::
+
+    use Symfony\Component\Notifier\Bridge\JoliNotif\JoliNotifOptions;
+    // ...
+
+    $options = (new JoliNotifOptions())
+        ->setIconPath('/path/to/icons/error.png')
+        ->setExtraOption('sound', 'sosumi')
+        ->setExtraOption('url', 'https://example.com');
+
+    $message = new DesktopMessage('Production is down', <<<CONTENT
+        ❌ Server prod-1 down
+        ❌ Server prod-2 down
+        ✅ Network is up
+        CONTENT, $options);
+
+    $texter->send($message);
+
 Configure to use Failover or Round-Robin Transports
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -910,9 +1100,10 @@ and its ``asChatMessage()`` method::
 
 The
 :class:`Symfony\\Component\\Notifier\\Notification\\SmsNotificationInterface`,
-:class:`Symfony\\Component\\Notifier\\Notification\\EmailNotificationInterface`
-and
+:class:`Symfony\\Component\\Notifier\\Notification\\EmailNotificationInterface`,
 :class:`Symfony\\Component\\Notifier\\Notification\\PushNotificationInterface`
+and
+:class:`Symfony\\Component\\Notifier\\Notification\\DesktopNotificationInterface`
 also exists to modify messages sent to those channels.
 
 Customize Browser Notifications (Flash Messages)
@@ -1093,19 +1284,21 @@ is dispatched. Listeners receive a
 .. _`Firebase`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/Firebase/README.md
 .. _`FreeMobile`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/FreeMobile/README.md
 .. _`GatewayApi`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/GatewayApi/README.md
-.. _`Gitter`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/Gitter/README.md
-.. _`GoIP`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/GoIp/README.md
+.. _`GoIP`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/GoIP/README.md
 .. _`GoogleChat`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/GoogleChat/README.md
 .. _`Infobip`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/Infobip/README.md
 .. _`Iqsms`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/Iqsms/README.md
 .. _`iSendPro`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/Isendpro/README.md
+.. _`JoliNotif`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/JoliNotif/README.md
 .. _`KazInfoTeh`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/KazInfoTeh/README.md
+.. _`LINE Bot`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/LineBot/README.md
 .. _`LINE Notify`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/LineNotify/README.md
 .. _`LightSms`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/LightSms/README.md
 .. _`LinkedIn`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/LinkedIn/README.md
 .. _`LOX24`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/Lox24/README.md
 .. _`Mailjet`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/Mailjet/README.md
 .. _`Mastodon`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/Mastodon/README.md
+.. _`Matrix`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/Matrix/README.md
 .. _`Mattermost`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/Mattermost/README.md
 .. _`Mercure`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/Mercure/README.md
 .. _`MessageBird`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/MessageBird/README.md
@@ -1121,6 +1314,7 @@ is dispatched. Listeners receive a
 .. _`OvhCloud`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/OvhCloud/README.md
 .. _`PagerDuty`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/PagerDuty/README.md
 .. _`Plivo`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/Plivo/README.md
+.. _`Primotexto`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/Primotexto/README.md
 .. _`Pushover`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/Pushover/README.md
 .. _`Pushy`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/Pushy/README.md
 .. _`Redlink`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/Redlink/README.md
@@ -1133,6 +1327,7 @@ is dispatched. Listeners receive a
 .. _`Seven.io`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/Sevenio/README.md
 .. _`SimpleTextin`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/SimpleTextin/README.md
 .. _`Sinch`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/Sinch/README.md
+.. _`Sipgate`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/Sipgate/README.md
 .. _`Slack`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/Slack/README.md
 .. _`Sms77`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/Sms77/README.md
 .. _`SmsBiuras`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/SmsBiuras/README.md
@@ -1142,6 +1337,7 @@ is dispatched. Listeners receive a
 .. _`SMSense`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/SMSense/README.md
 .. _`SmsSluzba`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/SmsSluzba/README.md
 .. _`SpotHit`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/SpotHit/README.md
+.. _`Sweego`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/Sweego/README.md
 .. _`Telegram`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/Telegram/README.md
 .. _`Telnyx`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/Telnyx/README.md
 .. _`TurboSms`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/TurboSms/README.md
diff --git a/object_mapper.rst b/object_mapper.rst
new file mode 100644
index 00000000000..625466ffefc
--- /dev/null
+++ b/object_mapper.rst
@@ -0,0 +1,738 @@
+Object Mapper
+=============
+
+.. versionadded:: 7.3
+
+    The ObjectMapper component was introduced in Symfony 7.3 as an
+    :doc:`experimental feature </contributing/code/experimental>`.
+
+This component transforms one object into another, simplifying tasks such as
+converting DTOs (Data Transfer Objects) into entities or vice versa. It can also
+be helpful when decoupling API input/output from internal models, particularly
+when working with legacy code or implementing hexagonal architectures.
+
+Installation
+------------
+
+Run this command to install the component before using it:
+
+.. code-block:: terminal
+
+    $ composer require symfony/object-mapper
+
+Usage
+-----
+
+The object mapper service will be :doc:`autowired </service_container/autowiring>`
+automatically in controllers or services when type-hinting for
+:class:`Symfony\\Component\\ObjectMapper\\ObjectMapperInterface`::
+
+    // src/Controller/UserController.php
+    namespace App\Controller;
+
+    use App\Dto\UserInput;
+    use App\Entity\User;
+    use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+    use Symfony\Component\HttpFoundation\Response;
+    use Symfony\Component\ObjectMapper\ObjectMapperInterface;
+
+    class UserController extends AbstractController
+    {
+        public function updateUser(UserInput $userInput, ObjectMapperInterface $objectMapper): Response
+        {
+            $user = new User();
+            // Map properties from UserInput to User
+            $objectMapper->map($userInput, $user);
+
+            // ... persist $user and return response
+            return new Response('User updated!');
+        }
+    }
+
+Basic Mapping
+-------------
+
+The core functionality is provided by the ``map()`` method. It accepts a
+source object and maps its properties to a target. The target can either be
+a class name (to create a new instance) or an existing object (to update it).
+
+Mapping to a New Object
+~~~~~~~~~~~~~~~~~~~~~~~
+
+Provide the target class name as the second argument::
+
+    use App\Dto\ProductInput;
+    use App\Entity\Product;
+    use Symfony\Component\ObjectMapper\ObjectMapper;
+
+    $productInput = new ProductInput();
+    $productInput->name = 'Wireless Mouse';
+    $productInput->sku = 'WM-1024';
+
+    $mapper = new ObjectMapper();
+    // creates a new Product instance and maps properties from $productInput
+    $product = $mapper->map($productInput, Product::class);
+
+    // $product is now an instance of Product
+    // with $product->name = 'Wireless Mouse' and $product->sku = 'WM-1024'
+
+Mapping to an Existing Object
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Provide an existing object instance as the second argument to update it::
+
+    use App\Dto\ProductUpdateInput;
+    use App\Entity\Product;
+    use Symfony\Component\ObjectMapper\ObjectMapper;
+
+    $product = $productRepository->find(1);
+
+    $updateInput = new ProductUpdateInput();
+    $updateInput->price = 99.99;
+
+    $mapper = new ObjectMapper();
+    // updates the existing $product instance
+    $mapper->map($updateInput, $product);
+
+    // $product->price is now 99.99
+
+Mapping from ``stdClass``
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The source object can also be an instance of ``stdClass``. This can be
+useful when working with decoded JSON data or loosely typed input::
+
+    use App\Entity\Product;
+    use Symfony\Component\ObjectMapper\ObjectMapper;
+
+    $productData = new \stdClass();
+    $productData->name = 'Keyboard';
+    $productData->sku = 'KB-001';
+
+    $mapper = new ObjectMapper();
+    $product = $mapper->map($productData, Product::class);
+
+    // $product is an instance of Product with properties mapped from $productData
+
+Configuring Mapping with Attributes
+-----------------------------------
+
+ObjectMapper uses PHP attributes to configure how properties are mapped.
+The primary attribute is :class:`Symfony\\Component\\ObjectMapper\\Attribute\\Map`.
+
+Defining the Default Target Class
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Apply ``#[Map]`` to the source class to define its default mapping target::
+
+    // src/Dto/ProductInput.php
+    namespace App\Dto;
+
+    use App\Entity\Product;
+    use Symfony\Component\ObjectMapper\Attribute\Map;
+
+    #[Map(target: Product::class)]
+    class ProductInput
+    {
+        public string $name = '';
+        public string $sku = '';
+    }
+
+    // now you can call map() without the second argument if ProductInput is the source:
+    $mapper = new ObjectMapper();
+    $product = $mapper->map($productInput); // Maps to Product automatically
+
+Configuring Property Mapping
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You can apply the ``#[Map]`` attribute to properties to customize their mapping behavior:
+
+* ``target``: Specifies the name of the property in the target object;
+* ``source``: Specifies the name of the property in the source object (useful
+  when mapping is defined on the target, see below);
+* ``if``: Defines a condition for mapping the property;
+* ``transform``: Applies a transformation to the value before mapping.
+
+This is how it looks in practice::
+
+    // src/Dto/OrderInput.php
+    namespace App\Dto;
+
+    use App\Entity\Order;
+    use Symfony\Component\ObjectMapper\Attribute\Map;
+
+    #[Map(target: Order::class)]
+    class OrderInput
+    {
+        // map 'customerEmail' from source to 'email' in target
+        #[Map(target: 'email')]
+        public string $customerEmail = '';
+
+        // do not map this property at all
+        #[Map(if: false)]
+        public string $internalNotes = '';
+
+        // only map 'discountCode' if it's a non-empty string
+        // (uses PHP's strlen() function as a condition)
+        #[Map(if: 'strlen')]
+        public ?string $discountCode = null;
+    }
+
+By default, if a property exists in the source but not in the target, it is
+ignored. If a property exists in both and no ``#[Map]`` is defined, the mapper
+assumes a direct mapping when names match.
+
+Conditional Mapping with Services
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+For complex conditions, you can use a dedicated service implementing
+:class:`Symfony\\Component\\ObjectMapper\\ConditionCallableInterface`::
+
+    // src/ObjectMapper/IsShippableCondition.php
+    namespace App\ObjectMapper;
+
+    use App\Dto\OrderInput;
+    use App\Entity\Order; // Target type hint
+    use Symfony\Component\ObjectMapper\ConditionCallableInterface;
+
+    /**
+     * @implements ConditionCallableInterface<OrderInput, Order>
+     */
+    final class IsShippableCondition implements ConditionCallableInterface
+    {
+        public function __invoke(mixed $value, object $source, ?object $target): bool
+        {
+            // example: Only map shipping address if order total is above 50
+            return $source->total > 50;
+        }
+    }
+
+Then, pass the service name (its class name by default) to the ``if`` parameter::
+
+    // src/Dto/OrderInput.php
+    namespace App\Dto;
+
+    use App\Entity\Order;
+    use App\ObjectMapper\IsShippableCondition;
+    use Symfony\Component\ObjectMapper\Attribute\Map;
+
+    #[Map(target: Order::class)]
+    class OrderInput
+    {
+        public float $total = 0.0;
+
+        #[Map(if: IsShippableCondition::class)]
+        public ?string $shippingAddress = null;
+    }
+
+For this to work, ``IsShippableCondition`` must be registered as a service.
+
+.. _object_mapper-conditional-property-target:
+
+Conditional Property Mapping based on Target
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When a source class maps to multiple targets, you may want to include or exclude
+certain properties depending on which target is being used. Use the
+:class:`Symfony\\Component\\ObjectMapper\\Condition\\TargetClass` condition within
+the ``if`` parameter of a property's ``#[Map]`` attribute to achieve this.
+
+This pattern is useful for building multiple representations (e.g., public vs. admin)
+from a given source object, and can be used as an alternative to
+:ref:`serialization groups <serializer-groups-attribute>`::
+
+    // src/Entity/User.php
+    namespace App\Entity;
+
+    use App\Dto\AdminUserProfile;
+    use App\Dto\PublicUserProfile;
+    use Symfony\Component\ObjectMapper\Attribute\Map;
+    use Symfony\Component\ObjectMapper\Condition\TargetClass;
+
+    // this User entity can be mapped to two different DTOs
+    #[Map(target: PublicUserProfile::class)]
+    #[Map(target: AdminUserProfile::class)]
+    class User
+    {
+        // map 'lastLoginIp' to 'ipAddress' ONLY when the target is AdminUserProfile
+        #[Map(target: 'ipAddress', if: new TargetClass(AdminUserProfile::class))]
+        public ?string $lastLoginIp = '192.168.1.100';
+
+        // map 'registrationDate' to 'memberSince' for both targets
+        #[Map(target: 'memberSince')]
+        public \DateTimeImmutable $registrationDate;
+
+        public function __construct() {
+            $this->registrationDate = new \DateTimeImmutable();
+        }
+    }
+
+    // src/Dto/PublicUserProfile.php
+    namespace App\Dto;
+    class PublicUserProfile
+    {
+        public \DateTimeImmutable $memberSince;
+        // no $ipAddress property here
+    }
+
+    // src/Dto/AdminUserProfile.php
+    namespace App\Dto;
+    class AdminUserProfile
+    {
+        public \DateTimeImmutable $memberSince;
+        public ?string $ipAddress = null; // mapped from lastLoginIp
+    }
+
+    // usage:
+    $user = new User();
+    $mapper = new ObjectMapper();
+
+    $publicProfile = $mapper->map($user, PublicUserProfile::class);
+    // no IP address available
+
+    $adminProfile = $mapper->map($user, AdminUserProfile::class);
+    // $adminProfile->ipAddress = '192.168.1.100'
+
+Transforming Values
+-------------------
+
+Use the ``transform`` option within ``#[Map]`` to change a value before it is
+assigned to the target. This can be a callable (e.g., a built-in PHP function,
+static method, or anonymous function) or a service implementing
+:class:`Symfony\\Component\\ObjectMapper\\TransformCallableInterface`.
+
+Using Callables
+~~~~~~~~~~~~~~~
+
+Consider the following static utility method::
+
+    // src/Util/PriceFormatter.php
+    namespace App\Util;
+
+    class PriceFormatter
+    {
+        public static function format(float $value, object $source): string
+        {
+            return number_format($value, 2, '.', '');
+        }
+    }
+
+You can use that method to format a property when mapping it::
+
+    // src/Dto/ProductInput.php
+    namespace App\Dto;
+
+    use App\Entity\Product;
+    use App\Util\PriceFormatter;
+    use Symfony\Component\ObjectMapper\Attribute\Map;
+
+    #[Map(target: Product::class)]
+    class ProductInput
+    {
+        // use a static method from another class for formatting
+        #[Map(target: 'displayPrice', transform: [PriceFormatter::class, 'format'])]
+        public float $price = 0.0;
+
+        // can also use built-in PHP functions
+        #[Map(transform: 'intval')]
+        public string $stockLevel = '100';
+    }
+
+Using Transformer Services
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Similar to conditions, complex transformations can be encapsulated in services
+implementing :class:`Symfony\\Component\\ObjectMapper\\TransformCallableInterface`::
+
+    // src/ObjectMapper/FullNameTransformer.php
+    namespace App\ObjectMapper;
+
+    use App\Dto\UserInput;
+    use App\Entity\User;
+    use Symfony\Component\ObjectMapper\TransformCallableInterface;
+
+    /**
+     * @implements TransformCallableInterface<UserInput, User>
+     */
+    final class FullNameTransformer implements TransformCallableInterface
+    {
+        public function __invoke(mixed $value, object $source, ?object $target): mixed
+        {
+            return trim($source->firstName . ' ' . $source->lastName);
+        }
+    }
+
+Then, use this service to format the mapped property::
+
+    // src/Dto/UserInput.php
+    namespace App\Dto;
+
+    use App\Entity\User;
+    use App\ObjectMapper\FullNameTransformer;
+    use Symfony\Component\ObjectMapper\Attribute\Map;
+
+    #[Map(target: User::class)]
+    class UserInput
+    {
+        // this property's value will be generated by the transformer
+        #[Map(target: 'fullName', transform: FullNameTransformer::class)]
+        public string $firstName = '';
+
+        public string $lastName = '';
+    }
+
+Class-Level Transformation
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You can define a transformation at the class level using the ``transform``
+parameter on the ``#[Map]`` attribute. This callable runs *after* the target
+object is created (if the target is a class name, ``newInstanceWithoutConstructor``
+is used), but *before* any properties are mapped. It must return a correctly
+initialized instance of the target class (replacing the one created by the mapper
+if needed)::
+
+    // src/Dto/LegacyUserData.php
+    namespace App\Dto;
+
+    use App\Entity\User;
+    use Symfony\Component\ObjectMapper\Attribute\Map;
+
+    // use a static factory method on the target User class for instantiation
+    #[Map(target: User::class, transform: [User::class, 'createFromLegacy'])]
+    class LegacyUserData
+    {
+        public int $userId = 0;
+        public string $name = '';
+    }
+
+And the related target object must define the ``createFromLegacy()`` method::
+
+    // src/Entity/User.php
+    namespace App\Entity;
+    class User
+    {
+        public string $name = '';
+        private int $legacyId = 0;
+
+        // uses a private constructor to avoid direct instantiation
+        private function __construct() {}
+
+        public static function createFromLegacy(mixed $value, object $source): self
+        {
+            // $value is the initially created (empty) User object
+            // $source is the LegacyUserData object
+            $user = new self();
+            $user->legacyId = $source->userId;
+
+            // property mapping will happen *after* this method returns $user
+            return $user;
+        }
+    }
+
+Mapping Multiple Targets
+------------------------
+
+A source class can be configured to map to multiple different target classes.
+Apply the ``#[Map]`` attribute multiple times at the class level, typically
+using the ``if`` condition to determine which target is appropriate based on the
+source object's state or other logic::
+
+    // src/Dto/EventInput.php
+    namespace App\Dto;
+
+    use App\Entity\OnlineEvent;
+    use App\Entity\PhysicalEvent;
+    use Symfony\Component\ObjectMapper\Attribute\Map;
+
+    #[Map(target: OnlineEvent::class, if: [self::class, 'isOnline'])]
+    #[Map(target: PhysicalEvent::class, if: [self::class, 'isPhysical'])]
+    class EventInput
+    {
+        public string $type = 'online'; // e.g., 'online' or 'physical'
+        public string $title = '';
+
+        /**
+         * In class-level conditions, $value is null.
+         */
+        public static function isOnline(?mixed $value, object $source): bool
+        {
+            return 'online' === $source->type;
+        }
+
+        public static function isPhysical(?mixed $value, object $source): bool
+        {
+            return 'physical' === $source->type;
+        }
+    }
+
+    // consider that the src/Entity/OnlineEvent.php and PhysicalEvent.php
+    // files exist and define the needed classes
+
+    // usage:
+    $eventInput = new EventInput();
+    $eventInput->type = 'physical';
+    $mapper = new ObjectMapper();
+    $event = $mapper->map($eventInput); // automatically maps to PhysicalEvent
+
+Mapping Based on Target Properties (Source Mapping)
+---------------------------------------------------
+
+Sometimes, it's more convenient to define how a target object should retrieve
+its values from a source, especially when working with external data formats.
+This is done using the ``source`` parameter in the ``#[Map]`` attribute on the
+target class's properties.
+
+Note that if both the ``source`` and the ``target`` classes define the ``#[Map]``
+attribute, the ``source`` takes precedence.
+
+Consider the following class that stores the data obtained from an external API
+that uses snake_case property names::
+
+    // src/Api/Payload.php
+    namespace App\Api;
+
+    class Payload
+    {
+        public string $product_name = '';
+        public float $price_amount = 0.0;
+    }
+
+In your application, classes use camelCase for property names, so you can map
+them as follows::
+
+    // src/Entity/Product.php
+    namespace App\Entity;
+
+    use App\Api\Payload;
+    use Symfony\Component\ObjectMapper\Attribute\Map;
+
+    // define that Product can be mapped from Payload
+    #[Map(source: Payload::class)]
+    class Product
+    {
+        // define where 'name' should get its value from in the Payload source
+        #[Map(source: 'product_name')]
+        public string $name = '';
+
+        // define where 'price' should get its value from
+        #[Map(source: 'price_amount')]
+        public float $price = 0.0;
+    }
+
+Using it in practice::
+
+    $payload = new Payload();
+    $payload->product_name = 'Super Widget';
+    $payload->price_amount = 123.45;
+
+    $mapper = new ObjectMapper();
+    // map from the payload to the Product class
+    $product = $mapper->map($payload, Product::class);
+
+    // $product->name = 'Super Widget'
+    // $product->price = 123.45
+
+When using source-based mapping, the ``ObjectMapper`` will automatically use the
+target's ``#[Map(source: ...)]`` attributes if no mapping is defined on the
+source class.
+
+Handling Recursion
+------------------
+
+The ObjectMapper automatically detects and handles recursive relationships between
+objects (e.g., a ``User`` has a ``manager`` which is another ``User``, who might
+manage the first user). When it encounters previously mapped objects in the graph,
+it reuses the corresponding target instances to prevent infinite loops::
+
+    // src/Entity/User.php
+    namespace App\Entity;
+
+    use App\Dto\UserDto;
+    use Symfony\Component\ObjectMapper\Attribute\Map;
+
+    #[Map(target: UserDto::class)]
+    class User
+    {
+        public string $name = '';
+        public ?User $manager = null;
+    }
+
+The target DTO object defines the ``User`` class as its source and the
+ObjectMapper component detects the cyclic reference::
+
+    // src/Dto/UserDto.php
+    namespace App\Dto;
+
+    use Symfony\Component\ObjectMapper\Attribute\Map;
+
+    #[Map(source: \App\Entity\User::class)] // can also define mapping here
+    class UserDto
+    {
+        public string $name = '';
+        public ?UserDto $manager = null;
+    }
+
+Using it in practice::
+
+    $manager = new User();
+    $manager->name = 'Alice';
+    $employee = new User();
+    $employee->name = 'Bob';
+    $employee->manager = $manager;
+    // manager's manager is the employee:
+    $manager->manager = $employee;
+
+    $mapper = new ObjectMapper();
+    $employeeDto = $mapper->map($employee, UserDto::class);
+
+    // recursion is handled correctly:
+    // $employeeDto->name === 'Bob'
+    // $employeeDto->manager->name === 'Alice'
+    // $employeeDto->manager->manager === $employeeDto
+
+.. _objectmapper-custom-mapping-logic:
+
+Custom Mapping Logic
+--------------------
+
+For very complex mapping scenarios or if you prefer separating mapping rules from
+your DTOs/Entities, you can implement a custom mapping strategy using the
+:class:`Symfony\\Component\\ObjectMapper\\Metadata\\ObjectMapperMetadataFactoryInterface`.
+This allows defining mapping rules within dedicated mapper services, similar
+to the approach used by libraries like MapStruct in the Java ecosystem.
+
+First, create your custom metadata factory. The following example reads mapping
+rules defined via ``#[Map]`` attributes on a dedicated mapper service class,
+specifically on its ``map`` method for property mappings and on the class itself
+for the source-to-target relationship::
+
+    namespace App\ObjectMapper\Metadata;
+
+    use Symfony\Component\ObjectMapper\Attribute\Map;
+    use Symfony\Component\ObjectMapper\Metadata\Mapping;
+    use Symfony\Component\ObjectMapper\Metadata\ObjectMapperMetadataFactoryInterface;
+    use Symfony\Component\ObjectMapper\ObjectMapperInterface;
+
+    /**
+     * A Metadata factory that implements basics similar to MapStruct.
+     * Reads mapping configuration from attributes on a dedicated mapper service.
+     */
+    final class MapStructMapperMetadataFactory implements ObjectMapperMetadataFactoryInterface
+    {
+        /**
+         * @param class-string<ObjectMapperInterface> $mapperClass The FQCN of the mapper service class
+         */
+        public function __construct(private readonly string $mapperClass)
+        {
+            if (!is_a($this->mapperClass, ObjectMapperInterface::class, true)) {
+                throw new \RuntimeException(sprintf('Mapper class "%s" must implement "%s".', $this->mapperClass, ObjectMapperInterface::class));
+            }
+        }
+
+        public function create(object $object, ?string $property = null, array $context = []): array
+        {
+            try {
+                $refl = new \ReflectionClass($this->mapperClass);
+            } catch (\ReflectionException $e) {
+                throw new \RuntimeException("Failed to reflect mapper class: " . $e->getMessage(), 0, $e);
+            }
+
+            $mapConfigs = [];
+            $sourceIdentifier = $property ?? $object::class;
+
+            // read attributes from the map method (for property mapping) or the class (for class mapping)
+            $attributesSource = $property ? $refl->getMethod('map') : $refl;
+            foreach ($attributesSource->getAttributes(Map::class, \ReflectionAttribute::IS_INSTANCEOF) as $attribute) {
+                $map = $attribute->newInstance();
+
+                // check if the attribute's source matches the current property or source class
+                if ($map->source === $sourceIdentifier) {
+                    $mapConfigs[] = new Mapping($map->target, $map->source, $map->if, $map->transform);
+                }
+            }
+
+            // if it's a property lookup and no specific mapping was found, map to the same property
+            if ($property && empty($mapConfigs)) {
+                $mapConfigs[] = new Mapping(target: $property, source: $property);
+            }
+
+            return $mapConfigs;
+        }
+    }
+
+Next, define your mapper service class. This class implements ``ObjectMapperInterface``
+but typically delegates the actual mapping back to a standard ``ObjectMapper``
+instance configured with the custom metadata factory. Mapping rules are defined
+using ``#[Map]`` attributes on this class and its ``map`` method::
+
+    namespace App\ObjectMapper;
+
+    use App\Dto\LegacyUser;
+    use App\Dto\UserDto;
+    use App\ObjectMapper\Metadata\MapStructMapperMetadataFactory;
+    use Symfony\Component\ObjectMapper\Attribute\Map;
+    use Symfony\Component\ObjectMapper\ObjectMapper;
+    use Symfony\Component\ObjectMapper\ObjectMapperInterface;
+
+    // define the source-to-target mapping at the class level
+    #[Map(source: LegacyUser::class, target: UserDto::class)]
+    class LegacyUserMapper implements ObjectMapperInterface
+    {
+        private readonly ObjectMapperInterface $objectMapper;
+
+        // inject the standard ObjectMapper or necessary dependencies
+        public function __construct(?ObjectMapperInterface $objectMapper = null)
+        {
+            // create an ObjectMapper instance configured with *this* mapper's rules
+            $metadataFactory = new MapStructMapperMetadataFactory(self::class);
+            $this->objectMapper = $objectMapper ?? new ObjectMapper($metadataFactory);
+        }
+
+        // define property-specific mapping rules on the map method
+        #[Map(source: 'fullName', target: 'name')] // Map LegacyUser::fullName to UserDto::name
+        #[Map(source: 'creationTimestamp', target: 'registeredAt', transform: [\DateTimeImmutable::class, 'createFromFormat'])]
+        #[Map(source: 'status', if: false)] // Ignore the 'status' property from LegacyUser
+        public function map(object $source, object|string|null $target = null): object
+        {
+            // delegate the actual mapping to the configured ObjectMapper
+            return $this->objectMapper->map($source, $target);
+        }
+    }
+
+Finally, use your custom mapper service::
+
+    use App\Dto\LegacyUser;
+    use App\ObjectMapper\LegacyUserMapper;
+
+    $legacyUser = new LegacyUser();
+    $legacyUser->fullName = 'Jane Doe';
+    $legacyUser->status = 'active'; // this will be ignored
+
+    // instantiate your custom mapper service
+    $mapperService = new LegacyUserMapper();
+
+    // use the map method of your service
+    $userDto = $mapperService->map($legacyUser); // Target (UserDto) is inferred from #[Map] on LegacyUserMapper
+
+This approach keeps mapping logic centralized within dedicated services, which can
+be beneficial for complex applications or when adhering to specific architectural patterns.
+
+Advanced Configuration
+----------------------
+
+The ``ObjectMapper`` constructor accepts optional arguments for advanced usage:
+
+* ``ObjectMapperMetadataFactoryInterface $metadataFactory``: Allows custom metadata
+  factories, such as the one shown in :ref:`the MapStruct-like example <objectmapper-custom-mapping-logic>`.
+  The default is :class:`Symfony\\Component\\ObjectMapper\\Metadata\\ReflectionObjectMapperMetadataFactory`,
+  which uses ``#[Map]`` attributes from source and target classes.
+* ``?PropertyAccessorInterface $propertyAccessor``: Lets you customize how
+  properties are read and written to the target object, useful for accessing
+  private properties or using getters/setters.
+* ``?ContainerInterface $transformCallableLocator``: A PSR-11 container (service locator)
+  that resolves service IDs referenced by the ``transform`` option in ``#[Map]``.
+* ``?ContainerInterface $conditionCallableLocator``: A PSR-11 container for resolving
+  service IDs used in ``if`` conditions within ``#[Map]``.
+
+These dependencies are automatically configured when you use the
+``ObjectMapperInterface`` service provided by Symfony.
diff --git a/page_creation.rst b/page_creation.rst
index f8b2fdaf251..0e2fd78e180 100644
--- a/page_creation.rst
+++ b/page_creation.rst
@@ -18,7 +18,7 @@ two-step process:
 .. admonition:: Screencast
     :class: screencast
 
-    Do you prefer video tutorials? Check out the `Harmonious Development with Symfony`_
+    Do you prefer video tutorials? Check out the `Cosmic Coding with Symfony`_
     screencast series.
 
 .. seealso::
@@ -36,7 +36,7 @@ Creating a Page: Route and Controller
 
 Suppose you want to create a page - ``/lucky/number`` - that generates a lucky (well,
 random) number and prints it. To do that, create a "Controller" class and a
-"controller" method inside of it::
+"number" method inside of it::
 
     <?php
     // src/Controller/LuckyController.php
@@ -80,7 +80,7 @@ metadata to code):
           }
       }
 
-That's it! If you are using :doc:`the Symfony web server </setup/symfony_server>`,
+That's it! If you are using :ref:`the Symfony web server <symfony-cli-server>`,
 try it out by going to: http://localhost:8000/lucky/number
 
 .. tip::
@@ -273,10 +273,10 @@ when needed.
 What's Next?
 ------------
 
-Congrats! You're already starting to master Symfony and learn a whole new
+Congrats! You're already starting to learn Symfony and discover a whole new
 way of building beautiful, functional, fast and maintainable applications.
 
-OK, time to finish mastering the fundamentals by reading these articles:
+OK, time to finish learning the fundamentals by reading these articles:
 
 * :doc:`/routing`
 * :doc:`/controller`
@@ -302,5 +302,5 @@ Go Deeper with HTTP & Framework Fundamentals
 
 .. _`Twig`: https://twig.symfony.com
 .. _`Composer`: https://getcomposer.org
-.. _`Harmonious Development with Symfony`: https://symfonycasts.com/screencast/symfony/setup
+.. _`Cosmic Coding with Symfony`: https://symfonycasts.com/screencast/symfony/setup
 .. _`attributes`: https://www.php.net/manual/en/language.attributes.overview.php
diff --git a/performance.rst b/performance.rst
index cd9dacddb1a..828333f338b 100644
--- a/performance.rst
+++ b/performance.rst
@@ -362,6 +362,13 @@ method does, which stops an event and then restarts it immediately::
     // Lap information is stored as "periods" within the event:
     // $event->getPeriods();
 
+    // Gets the last event period:
+    // $event->getLastPeriod();
+
+.. versionadded:: 7.2
+
+    The ``getLastPeriod()`` method was introduced in Symfony 7.2.
+
 Profiling Sections
 ..................
 
@@ -382,10 +389,16 @@ All events that don't belong to any named section are added to the special secti
 called ``__root__``. This way you can get all stopwatch events, even if you don't
 know their names, as follows::
 
-    foreach($this->stopwatch->getSectionEvents('__root__') as $event) {
+    use Symfony\Component\Stopwatch\Stopwatch;
+
+    foreach($this->stopwatch->getSectionEvents(Stopwatch::ROOT) as $event) {
         echo (string) $event;
     }
 
+.. versionadded:: 7.2
+
+    The ``Stopwatch::ROOT`` constant as a shortcut for ``__root__`` was introduced in Symfony 7.2.
+
 Learn more
 ----------
 
diff --git a/profiler.rst b/profiler.rst
index 57d412472ba..7fc97c8ee33 100644
--- a/profiler.rst
+++ b/profiler.rst
@@ -217,9 +217,48 @@ user by dynamically rewriting the current page rather than loading entire new
 pages from a server.
 
 By default, the debug toolbar displays the information of the initial page load
-and doesn't refresh after each AJAX request. However, you can set the
-``Symfony-Debug-Toolbar-Replace`` header to a value of ``'1'`` in the response to
-the AJAX request to force the refresh of the toolbar::
+and doesn't refresh after each AJAX request. However, you can configure the
+toolbar to be refreshed after each AJAX request by enabling ``ajax_replace`` in the
+``web_profiler`` configuration:
+
+.. configuration-block::
+
+    .. code-block:: yaml
+
+        # config/packages/web_profiler.yaml
+        web_profiler:
+            toolbar:
+                ajax_replace: true
+
+    .. code-block:: xml
+
+        <!-- config/packages/web_profiler.xml -->
+        <?xml version="1.0" ?>
+        <container xmlns="http://symfony.com/schema/dic/services"
+            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+            xmlns:framework="http://symfony.com/schema/dic/symfony"
+            xmlns:web-profiler="http://symfony.com/schema/dic/webprofiler"
+            xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd
+                http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
+
+                <web-profiler:config>
+                    <web-profiler:toolbar ajax-replace="true"/>
+                </web-profiler:config>
+        </container>
+
+    .. code-block:: php
+
+        // config/packages/web_profiler.php
+        use Symfony\Config\WebProfilerConfig;
+
+        return static function (WebProfilerConfig $profiler): void {
+            $profiler->toolbar()
+                ->ajaxReplace(true);
+        };
+
+If you need a more sophisticated solution, you can set the
+``Symfony-Debug-Toolbar-Replace`` header to a value of ``'1'`` in the response
+yourself::
 
     $response->headers->set('Symfony-Debug-Toolbar-Replace', '1');
 
@@ -228,31 +267,21 @@ production. To do that, create an :doc:`event subscriber </event_dispatcher>`
 and listen to the :ref:`kernel.response <component-http-kernel-kernel-response>`
 event::
 
+    use Symfony\Component\DependencyInjection\Attribute\When;
     use Symfony\Component\EventDispatcher\EventSubscriberInterface;
     use Symfony\Component\HttpKernel\Event\ResponseEvent;
     use Symfony\Component\HttpKernel\KernelInterface;
 
     // ...
 
+    #[When(env: 'dev')]
     class MySubscriber implements EventSubscriberInterface
     {
-        public function __construct(
-            private KernelInterface $kernel,
-        ) {
-        }
-
         // ...
 
         public function onKernelResponse(ResponseEvent $event): void
         {
-            if (!$this->kernel->isDebug()) {
-                return;
-            }
-
-            $request = $event->getRequest();
-            if (!$request->isXmlHttpRequest()) {
-                return;
-            }
+            // Your custom logic here
 
             $response = $event->getResponse();
             $response->headers->set('Symfony-Debug-Toolbar-Replace', '1');
diff --git a/quick_tour/the_architecture.rst b/quick_tour/the_architecture.rst
index a323461885d..3b66570b3d3 100644
--- a/quick_tour/the_architecture.rst
+++ b/quick_tour/the_architecture.rst
@@ -159,29 +159,22 @@ Twig Extension & Autoconfiguration
 Thanks to Symfony's service handling, you can *extend* Symfony in many ways, like
 by creating an event subscriber or a security voter for complex authorization
 rules. Let's add a new filter to Twig called ``greet``. How? Create a class
-that extends ``AbstractExtension``::
+with your logic::
 
     // src/Twig/GreetExtension.php
     namespace App\Twig;
 
     use App\GreetingGenerator;
-    use Twig\Extension\AbstractExtension;
-    use Twig\TwigFilter;
+    use Twig\Attribute\AsTwigFilter;
 
-    class GreetExtension extends AbstractExtension
+    class GreetExtension
     {
         public function __construct(
             private GreetingGenerator $greetingGenerator,
         ) {
         }
 
-        public function getFilters(): array
-        {
-            return [
-                new TwigFilter('greet', [$this, 'greetUser']),
-            ];
-        }
-
+        #[AsTwigFilter('greet')]
         public function greetUser(string $name): string
         {
             $greeting =  $this->greetingGenerator->getRandomGreeting();
@@ -198,7 +191,7 @@ After creating just *one* file, you can use this immediately:
     {# Will print something like "Hey Symfony!" #}
     <h1>{{ name|greet }}</h1>
 
-How does this work? Symfony notices that your class extends ``AbstractExtension``
+How does this work? Symfony notices that your class uses the ``#[AsTwigFilter]`` attribute
 and so *automatically* registers it as a Twig extension. This is called autoconfiguration,
 and it works for *many* many things. Create a class and then extend a base class
 (or implement an interface). Symfony takes care of the rest.
diff --git a/quick_tour/the_big_picture.rst b/quick_tour/the_big_picture.rst
index b069cb4f716..ba7cc78e28b 100644
--- a/quick_tour/the_big_picture.rst
+++ b/quick_tour/the_big_picture.rst
@@ -42,8 +42,8 @@ Symfony application:
 Can we already load the project in a browser? Yes! You can set up
 :doc:`Nginx or Apache </setup/web_server_configuration>` and configure their
 document root to be the ``public/`` directory. But, for development, it's better
-to :doc:`install the Symfony local web server </setup/symfony_server>` and run
-it as follows:
+to install the :doc:`Symfony CLI </setup/symfony_cli>` tool and run its
+:ref:`local web server <symfony-cli-server>` as follows:
 
 .. code-block:: terminal
 
diff --git a/rate_limiter.rst b/rate_limiter.rst
index 6c158ee52d0..5fd453f0534 100644
--- a/rate_limiter.rst
+++ b/rate_limiter.rst
@@ -230,6 +230,12 @@ prevents that number from being higher than 5,000).
 Rate Limiting in Action
 -----------------------
 
+.. versionadded:: 7.3
+
+    :class:`Symfony\\Component\\RateLimiter\\RateLimiterFactoryInterface` was
+    added and should now be used for autowiring instead of
+    :class:`Symfony\\Component\\RateLimiter\\RateLimiterFactory`.
+
 After having installed and configured the rate limiter, inject it in any service
 or controller and call the ``consume()`` method to try to consume a given number
 of tokens. For example, this controller uses the previous rate limiter to control
@@ -242,13 +248,13 @@ the number of requests to the API::
     use Symfony\Component\HttpFoundation\Request;
     use Symfony\Component\HttpFoundation\Response;
     use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException;
-    use Symfony\Component\RateLimiter\RateLimiterFactory;
+    use Symfony\Component\RateLimiter\RateLimiterFactoryInterface;
 
     class ApiController extends AbstractController
     {
         // if you're using service autowiring, the variable name must be:
         // "rate limiter name" (in camelCase) + "Limiter" suffix
-        public function index(Request $request, RateLimiterFactory $anonymousApiLimiter): Response
+        public function index(Request $request, RateLimiterFactoryInterface $anonymousApiLimiter): Response
         {
             // create a limiter based on a unique identifier of the client
             // (e.g. the client's IP address, a username/email, an API key, etc.)
@@ -291,11 +297,11 @@ using the ``reserve()`` method::
     use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
     use Symfony\Component\HttpFoundation\Request;
     use Symfony\Component\HttpFoundation\Response;
-    use Symfony\Component\RateLimiter\RateLimiterFactory;
+    use Symfony\Component\RateLimiter\RateLimiterFactoryInterface;
 
     class ApiController extends AbstractController
     {
-        public function registerUser(Request $request, RateLimiterFactory $authenticatedApiLimiter): Response
+        public function registerUser(Request $request, RateLimiterFactoryInterface $authenticatedApiLimiter): Response
         {
             $apiKey = $request->headers->get('apikey');
             $limiter = $authenticatedApiLimiter->create($apiKey);
@@ -350,11 +356,11 @@ the :class:`Symfony\\Component\\RateLimiter\\Reservation` object returned by the
     use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
     use Symfony\Component\HttpFoundation\Request;
     use Symfony\Component\HttpFoundation\Response;
-    use Symfony\Component\RateLimiter\RateLimiterFactory;
+    use Symfony\Component\RateLimiter\RateLimiterFactoryInterface;
 
     class ApiController extends AbstractController
     {
-        public function index(Request $request, RateLimiterFactory $anonymousApiLimiter): Response
+        public function index(Request $request, RateLimiterFactoryInterface $anonymousApiLimiter): Response
         {
             $limiter = $anonymousApiLimiter->create($request->getClientIp());
             $limit = $limiter->consume();
@@ -461,9 +467,10 @@ simultaneous requests (e.g. three servers of a company hitting your API at the
 same time). Rate limiters use :doc:`locks </lock>` to protect their operations
 against these race conditions.
 
-By default, Symfony uses the global lock configured by ``framework.lock``, but
-you can use a specific :ref:`named lock <lock-named-locks>` via the
-``lock_factory`` option (or none at all):
+By default, if the :doc:`lock </lock>` component is installed, Symfony uses the
+global lock configured by ``framework.lock``, but you can use a specific
+:ref:`named lock <lock-named-locks>` via the ``lock_factory`` option (or none
+at all):
 
 .. configuration-block::
 
@@ -534,6 +541,129 @@ you can use a specific :ref:`named lock <lock-named-locks>` via the
                 ;
         };
 
+.. versionadded:: 7.3
+
+    Before Symfony 7.3, configuring a rate limiter and using the default configured
+    lock factory (``lock.factory``) failed if the Symfony Lock component was not
+    installed in the application.
+
+Compound Rate Limiter
+---------------------
+
+.. versionadded:: 7.3
+
+    Support for configuring compound rate limiters was introduced in Symfony 7.3.
+
+You can configure multiple rate limiters to work together:
+
+.. configuration-block::
+
+    .. code-block:: yaml
+
+        # config/packages/rate_limiter.yaml
+        framework:
+            rate_limiter:
+                two_per_minute:
+                    policy: 'fixed_window'
+                    limit: 2
+                    interval: '1 minute'
+                five_per_hour:
+                    policy: 'fixed_window'
+                    limit: 5
+                    interval: '1 hour'
+                contact_form:
+                    policy: 'compound'
+                    limiters: [two_per_minute, five_per_hour]
+
+    .. code-block:: xml
+
+        <!-- config/packages/rate_limiter.xml -->
+        <?xml version="1.0" encoding="UTF-8" ?>
+        <container xmlns="http://symfony.com/schema/dic/services"
+            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+            xmlns:framework="http://symfony.com/schema/dic/symfony"
+            xsi:schemaLocation="http://symfony.com/schema/dic/services
+                https://symfony.com/schema/dic/services/services-1.0.xsd
+                http://symfony.com/schema/dic/symfony
+                https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
+
+            <framework:config>
+                <framework:rate-limiter>
+                    <framework:limiter name="two_per_minute"
+                        policy="fixed_window"
+                        limit="2"
+                        interval="1 minute"
+                    />
+
+                    <framework:limiter name="five_per_hour"
+                        policy="fixed_window"
+                        limit="5"
+                        interval="1 hour"
+                    />
+
+                    <framework:limiter name="contact_form"
+                        policy="compound"
+                    >
+                        <limiter>two_per_minute</limiter>
+                        <limiter>five_per_hour</limiter>
+                    </framework:limiter>
+                </framework:rate-limiter>
+            </framework:config>
+        </container>
+
+    .. code-block:: php
+
+        // config/packages/rate_limiter.php
+        use Symfony\Config\FrameworkConfig;
+
+        return static function (FrameworkConfig $framework): void {
+            $framework->rateLimiter()
+                ->limiter('two_per_minute')
+                    ->policy('fixed_window')
+                    ->limit(2)
+                    ->interval('1 minute')
+                ;
+
+            $framework->rateLimiter()
+                ->limiter('two_per_minute')
+                    ->policy('fixed_window')
+                    ->limit(5)
+                    ->interval('1 hour')
+                ;
+
+            $framework->rateLimiter()
+                ->limiter('contact_form')
+                    ->policy('compound')
+                    ->limiters(['two_per_minute', 'five_per_hour'])
+                ;
+        };
+
+Then, inject and use as normal::
+
+    // src/Controller/ContactController.php
+    namespace App\Controller;
+
+    use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+    use Symfony\Component\HttpFoundation\Request;
+    use Symfony\Component\HttpFoundation\Response;
+    use Symfony\Component\RateLimiter\RateLimiterFactoryInterface;
+
+    class ContactController extends AbstractController
+    {
+        public function registerUser(Request $request, RateLimiterFactoryInterface $contactFormLimiter): Response
+        {
+            $limiter = $contactFormLimiter->create($request->getClientIp());
+
+            if (false === $limiter->consume(1)->isAccepted()) {
+                // either of the two limiters has been reached
+            }
+
+            // ...
+        }
+
+        // ...
+    }
+
 .. _`DoS attacks`: https://cheatsheetseries.owasp.org/cheatsheets/Denial_of_Service_Cheat_Sheet.html
 .. _`Apache mod_ratelimit`: https://httpd.apache.org/docs/current/mod/mod_ratelimit.html
 .. _`NGINX rate limiting`: https://www.nginx.com/blog/rate-limiting-nginx/
diff --git a/reference/attributes.rst b/reference/attributes.rst
index 7975a8aaee4..968c7df1568 100644
--- a/reference/attributes.rst
+++ b/reference/attributes.rst
@@ -14,7 +14,7 @@ Doctrine Bridge
 Command
 ~~~~~~~
 
-* :ref:`AsCommand <console_registering-the-command>`
+* :ref:`AsCommand <console_creating-command>`
 
 Contracts
 ~~~~~~~~~
@@ -43,6 +43,7 @@ Dependency Injection
 * :ref:`TaggedLocator <service-subscribers-locators_defining-service-locator>`
 * :ref:`Target <autowiring-multiple-implementations-same-type>`
 * :ref:`When <service-container_limiting-to-env>`
+* :ref:`WhenNot <service-container_limiting-to-env>`
 
 .. deprecated:: 7.1
 
@@ -78,6 +79,7 @@ HttpKernel
 Messenger
 ~~~~~~~~~
 
+* :ref:`AsMessage <messenger-message-attribute>`
 * :ref:`AsMessageHandler <messenger-handler>`
 
 RemoteEvent
@@ -121,6 +123,9 @@ Twig
 ~~~~
 
 * :ref:`Template <templates-template-attribute>`
+* :ref:`AsTwigFilter <templates-twig-filter-attribute>`
+* :ref:`AsTwigFunction <templates-twig-function-attribute>`
+* ``AsTwigTest``
 
 Symfony UX
 ~~~~~~~~~~
diff --git a/reference/configuration/debug.rst b/reference/configuration/debug.rst
index 9b8dc2a6f0c..6ca05b49bd7 100644
--- a/reference/configuration/debug.rst
+++ b/reference/configuration/debug.rst
@@ -8,14 +8,14 @@ key in your application configuration.
 .. code-block:: terminal
 
     # displays the default config values defined by Symfony
-    $ php bin/console config:dump-reference framework
+    $ php bin/console config:dump-reference debug
 
     # displays the actual config values used by your application
-    $ php bin/console debug:config framework
+    $ php bin/console debug:config debug
 
     # displays the config values used by your application and replaces the
     # environment variables with their actual values
-    $ php bin/console debug:config --resolve-env framework
+    $ php bin/console debug:config --resolve-env debug
 
 .. note::
 
@@ -23,35 +23,6 @@ key in your application configuration.
     namespace and the related XSD schema is available at:
     ``https://symfony.com/schema/dic/debug/debug-1.0.xsd``
 
-Configuration
--------------
-
-max_items
-~~~~~~~~~
-
-**type**: ``integer`` **default**: ``2500``
-
-This is the maximum number of items to dump. Setting this option to ``-1``
-disables the limit.
-
-min_depth
-~~~~~~~~~
-
-**type**: ``integer`` **default**: ``1``
-
-Configures the minimum tree depth until which all items are guaranteed to
-be cloned. After this depth is reached, only ``max_items`` items will be
-cloned. The default value is ``1``, which is consistent with older Symfony
-versions.
-
-max_string_length
-~~~~~~~~~~~~~~~~~
-
-**type**: ``integer`` **default**: ``-1``
-
-This option configures the maximum string length before truncating the
-string. The default value (``-1``) means that strings are never truncated.
-
 .. _configuration-debug-dump_destination:
 
 dump_destination
@@ -101,3 +72,29 @@ Typically, you would set this to ``php://stderr``:
 
 
 Configure it to ``"tcp://%env(VAR_DUMPER_SERVER)%"`` in order to use the :ref:`ServerDumper feature <var-dumper-dump-server>`.
+
+max_items
+~~~~~~~~~
+
+**type**: ``integer`` **default**: ``2500``
+
+This is the maximum number of items to dump. Setting this option to ``-1``
+disables the limit.
+
+max_string_length
+~~~~~~~~~~~~~~~~~
+
+**type**: ``integer`` **default**: ``-1``
+
+This option configures the maximum string length before truncating the
+string. The default value (``-1``) means that strings are never truncated.
+
+min_depth
+~~~~~~~~~
+
+**type**: ``integer`` **default**: ``1``
+
+Configures the minimum tree depth until which all items are guaranteed to
+be cloned. After this depth is reached, only ``max_items`` items will be
+cloned. The default value is ``1``, which is consistent with older Symfony
+versions.
diff --git a/reference/configuration/doctrine.rst b/reference/configuration/doctrine.rst
index 272ad6b6804..db6336e1ee6 100644
--- a/reference/configuration/doctrine.rst
+++ b/reference/configuration/doctrine.rst
@@ -100,6 +100,36 @@ The following block shows all possible configuration keys:
             </doctrine:config>
         </container>
 
+    .. code-block:: php
+
+        use Symfony\Config\DoctrineConfig;
+
+        return static function (DoctrineConfig $doctrine): void {
+            $dbal = $doctrine->dbal();
+
+            $dbal = $dbal
+                ->connection('default')
+                    ->dbname('database')
+                    ->host('localhost')
+                    ->port(1234)
+                    ->user('user')
+                    ->password('secret')
+                    ->driver('pdo_mysql')
+                    ->url('mysql://db_user:db_password@127.0.0.1:3306/db_name') // if the url option is specified, it will override the above config
+                    ->driverClass(App\DBAL\MyDatabaseDriver::class) // the DBAL driverClass option
+                    ->option('foo', 'bar') // the DBAL driverOptions option
+                    ->path('%kernel.project_dir%/var/data/data.sqlite')
+                    ->memory(true)
+                    ->unixSocket('/tmp/mysql.sock')
+                    ->wrapperClass(App\DBAL\MyConnectionWrapper::class) // the DBAL wrapperClass option
+                    ->charset('utf8mb4')
+                    ->logging('%kernel.debug%')
+                    ->platformService(App\DBAL\MyDatabasePlatformService::class)
+                    ->serverVersion('8.0.37')
+                    ->mappingType('enum', 'string')
+                    ->type('custom', App\DBAL\MyCustomType::class);
+        };
+
 .. note::
 
     The ``server_version`` option was added in Doctrine DBAL 2.5, which
@@ -125,24 +155,49 @@ The following block shows all possible configuration keys:
 If you want to configure multiple connections in YAML, put them under the
 ``connections`` key and give them a unique name:
 
-.. code-block:: yaml
+.. configuration-block::
 
-    doctrine:
-        dbal:
-            default_connection:       default
-            connections:
-                default:
-                    dbname:           Symfony
-                    user:             root
-                    password:         null
-                    host:             localhost
-                    server_version:   '8.0.37'
-                customer:
-                    dbname:           customer
-                    user:             root
-                    password:         null
-                    host:             localhost
-                    server_version:   '8.2.0'
+    .. code-block:: yaml
+
+        doctrine:
+            dbal:
+                default_connection:       default
+                connections:
+                    default:
+                        dbname:           Symfony
+                        user:             root
+                        password:         null
+                        host:             localhost
+                        server_version:   '8.0.37'
+                    customer:
+                        dbname:           customer
+                        user:             root
+                        password:         null
+                        host:             localhost
+                        server_version:   '8.2.0'
+
+    .. code-block:: php
+
+        use Symfony\Config\DoctrineConfig;
+
+        return static function (DoctrineConfig $doctrine): void {
+            $dbal = $doctrine->dbal();
+            $dbal->defaultConnection('default');
+
+            $dbal->connection('default')
+                ->dbname('Symfony')
+                ->user('root')
+                ->password('null')
+                ->host('localhost')
+                ->serverVersion('8.0.37');
+
+            $dbal->connection('customer')
+                ->dbname('customer')
+                ->user('root')
+                ->password('null')
+                ->host('localhost')
+                ->serverVersion('8.2.0');
+        };
 
 The ``database_connection`` service always refers to the *default* connection,
 which is the first one defined or the one configured via the
@@ -172,20 +227,45 @@ Doctrine ORM Configuration
 This following configuration example shows all the configuration defaults
 that the ORM resolves to:
 
-.. code-block:: yaml
+.. configuration-block::
 
-    doctrine:
-        orm:
-            auto_mapping: true
-            # the standard distribution overrides this to be true in debug, false otherwise
-            auto_generate_proxy_classes: false
-            proxy_namespace: Proxies
-            proxy_dir: '%kernel.cache_dir%/doctrine/orm/Proxies'
-            default_entity_manager: default
-            metadata_cache_driver: array
-            query_cache_driver: array
-            result_cache_driver: array
-            naming_strategy: doctrine.orm.naming_strategy.default
+    .. code-block:: yaml
+
+        doctrine:
+            orm:
+                auto_mapping: false
+                # the standard distribution overrides this to be true in debug, false otherwise
+                auto_generate_proxy_classes: false
+                proxy_namespace: Proxies
+                proxy_dir: '%kernel.cache_dir%/doctrine/orm/Proxies'
+                default_entity_manager: default
+                metadata_cache_driver: array
+                query_cache_driver: array
+                result_cache_driver: array
+                naming_strategy: doctrine.orm.naming_strategy.default
+
+    .. code-block:: php
+
+        use Symfony\Config\DoctrineConfig;
+
+        return static function (DoctrineConfig $doctrine): void {
+            $orm = $doctrine->orm();
+
+            $orm
+                ->entityManager('default')
+                ->connection('default')
+                ->autoMapping(true)
+                ->metadataCacheDriver()->type('array')
+                ->queryCacheDriver()->type('array')
+                ->resultCacheDriver()->type('array')
+                ->namingStrategy('doctrine.orm.naming_strategy.default');
+
+            $orm
+                ->autoGenerateProxyClasses(false)
+                ->proxyNamespace('Proxies')
+                ->proxyDir('%kernel.cache_dir%/doctrine/orm/Proxies')
+                ->defaultEntityManager('default');
+        };
 
 There are lots of other configuration options that you can use to overwrite
 certain classes, but those are for very advanced use-cases only.
@@ -230,35 +310,70 @@ Caching Drivers
 Use any of the existing :doc:`Symfony Cache </cache>` pools or define new pools
 to cache each of Doctrine ORM elements (queries, results, etc.):
 
-.. code-block:: yaml
+.. configuration-block::
 
-    # config/packages/prod/doctrine.yaml
-    framework:
-        cache:
-            pools:
-                doctrine.result_cache_pool:
-                    adapter: cache.app
-                doctrine.system_cache_pool:
-                    adapter: cache.system
+    .. code-block:: yaml
 
-    doctrine:
-        orm:
-            # ...
-            metadata_cache_driver:
-                type: pool
-                pool: doctrine.system_cache_pool
-            query_cache_driver:
-                type: pool
-                pool: doctrine.system_cache_pool
-            result_cache_driver:
-                type: pool
-                pool: doctrine.result_cache_pool
+        # config/packages/prod/doctrine.yaml
+        framework:
+            cache:
+                pools:
+                    doctrine.result_cache_pool:
+                        adapter: cache.app
+                    doctrine.system_cache_pool:
+                        adapter: cache.system
 
-            # in addition to Symfony Cache pools, you can also use the
-            # 'type: service' option to use any service as the cache
-            query_cache_driver:
-                type: service
-                id: App\ORM\MyCacheService
+        doctrine:
+            orm:
+                # ...
+                metadata_cache_driver:
+                    type: pool
+                    pool: doctrine.system_cache_pool
+                query_cache_driver:
+                    type: pool
+                    pool: doctrine.system_cache_pool
+                result_cache_driver:
+                    type: pool
+                    pool: doctrine.result_cache_pool
+
+                # in addition to Symfony cache pools, you can also use the
+                # 'type: service' option to use any service as a cache pool
+                query_cache_driver:
+                    type: service
+                    id: App\ORM\MyCacheService
+
+    .. code-block:: php
+
+        use Symfony\Config\DoctrineConfig;
+        use Symfony\Config\FrameworkConfig;
+
+        return static function (FrameworkConfig $framework, DoctrineConfig $doctrine): void {
+            $framework
+                ->cache()
+                    ->pool('doctrine.result_cache_pool')
+                        ->adapters('cache.app')
+                    ->pool('doctrine.system_cache_pool')
+                        ->adapters('cache.system');
+
+            $doctrine->orm()
+                // ...
+                ->entityManager('default')
+                ->metadataCacheDriver()
+                    ->type('pool')
+                    ->pool('doctrine.system_cache_pool')
+                ->queryCacheDriver()
+                    ->type('pool')
+                    ->pool('doctrine.system_cache_pool')
+                ->resultCacheDriver()
+                    ->type('pool')
+                    ->pool('doctrine.result_cache_pool')
+
+                // in addition to Symfony cache pools, you can also use the
+                // 'type: service' option to use any service as a cache pool
+                ->queryCacheDriver()
+                    ->type('service')
+                    ->id(App\ORM\MyCacheService::class);
+        };
 
 Mapping Configuration
 ~~~~~~~~~~~~~~~~~~~~~
@@ -507,9 +622,9 @@ set up the connection using environment variables for the certificate paths:
                     server-version="8.0.31"
                     driver="pdo_mysql">
 
-                    <doctrine:option key="1007">%env(MYSQL_SSL_KEY)%</doctrine:option>
-                    <doctrine:option key="1008">%env(MYSQL_SSL_CERT)%</doctrine:option>
-                    <doctrine:option key="1009">%env(MYSQL_SSL_CA)%</doctrine:option>
+                    <doctrine:option key-type="constant" key="PDO::MYSQL_ATTR_SSL_KEY">%env(MYSQL_SSL_KEY)%</doctrine:option>
+                    <doctrine:option key-type="constant" key="PDO::MYSQL_ATTR_SSL_CERT">%env(MYSQL_SSL_CERT)%</doctrine:option>
+                    <doctrine:option key-type="constant" key="PDO::MYSQL_ATTR_SSL_CA">%env(MYSQL_SSL_CA)%</doctrine:option>
                 </doctrine:dbal>
             </doctrine:config>
         </container>
diff --git a/reference/configuration/framework.rst b/reference/configuration/framework.rst
index d4ff35a6381..e60e5d67c99 100644
--- a/reference/configuration/framework.rst
+++ b/reference/configuration/framework.rst
@@ -19,234 +19,112 @@ configured under the ``framework`` key in your application configuration.
     namespace and the related XSD schema is available at:
     ``https://symfony.com/schema/dic/symfony/symfony-1.0.xsd``
 
-Configuration
--------------
-
-.. _configuration-framework-secret:
-
-secret
-~~~~~~
-
-**type**: ``string`` **required**
-
-This is a string that should be unique to your application and it's commonly
-used to add more entropy to security related operations. Its value should
-be a series of characters, numbers and symbols chosen randomly and the
-recommended length is around 32 characters.
-
-In practice, Symfony uses this value for encrypting the cookies used
-in the :doc:`remember me functionality </security/remember_me>` and for
-creating signed URIs when using :ref:`ESI (Edge Side Includes) <edge-side-includes>`.
-That's why you should treat this value as if it were a sensitive credential and
-**never make it public**.
-
-This option becomes the service container parameter named ``kernel.secret``,
-which you can use whenever the application needs an immutable random string
-to add more entropy.
-
-As with any other security-related parameter, it is a good practice to change
-this value from time to time. However, keep in mind that changing this value
-will invalidate all signed URIs and Remember Me cookies. That's why, after
-changing this value, you should regenerate the application cache and log
-out all the application users.
-
-handle_all_throwables
-~~~~~~~~~~~~~~~~~~~~~
-
-**type**: ``boolean`` **default**: ``true``
+annotations
+~~~~~~~~~~~
 
-When set to ``true``, the Symfony kernel will catch all ``\Throwable`` exceptions
-thrown by the application and will turn them into HTTP responses.
+.. _reference-annotations-cache:
 
-.. _configuration-framework-http_cache:
+cache
+.....
 
-http_cache
-~~~~~~~~~~
+**type**: ``string`` **default**: ``php_array``
 
-enabled
-.......
+This option can be one of the following values:
 
-**type**: ``boolean`` **default**: ``false``
+php_array
+    Use a PHP array to cache annotations in memory
+file
+    Use the filesystem to cache annotations
+none
+    Disable the caching of annotations
 
 debug
 .....
 
 **type**: ``boolean`` **default**: ``%kernel.debug%``
 
-If true, exceptions are thrown when things go wrong. Otherwise, the cache will
-try to carry on and deliver a meaningful response.
-
-trace_level
-...........
-
-**type**: ``string`` **possible values**: ``'none'``, ``'short'`` or ``'full'``
-
-For 'short', a concise trace of the main request will be added as an HTTP header.
-'full' will add traces for all requests (including ESI subrequests).
-(default: 'full' if in debug; 'none' otherwise)
-
-trace_header
-............
-
-**type**: ``string``
-
-Header name to use for traces. (default: X-Symfony-Cache)
-
-default_ttl
-...........
-
-**type**: ``integer``
-
-The number of seconds that a cache entry should be considered fresh when no
-explicit freshness information is provided in a response. Explicit
-Cache-Control or Expires headers override this value. (default: 0)
-
-private_headers
-...............
-
-**type**: ``array``
-
-Set of request headers that trigger "private" cache-control behavior on responses
-that don't explicitly state whether the response is public or private via a
-Cache-Control directive. (default: Authorization and Cookie)
-
-skip_response_headers
-.....................
-
-**type**: ``array`` **default**: ``Set-Cookie``
-
-Set of response headers that will never be cached even when the response is cacheable
-and public.
-
-allow_reload
-............
-
-**type**: ``string``
-
-Specifies whether the client can force a cache reload by including a
-Cache-Control "no-cache" directive in the request. Set it to ``true``
-for compliance with RFC 2616. (default: false)
-
-allow_revalidate
-................
-
-**type**: ``string``
-
-Specifies whether the client can force a cache revalidate by including a
-Cache-Control "max-age=0" directive in the request. Set it to ``true``
-for compliance with RFC 2616. (default: false)
-
-stale_while_revalidate
-......................
-
-**type**: ``integer``
-
-Specifies the default number of seconds (the granularity is the second as the
-Response TTL precision is a second) during which the cache can immediately return
-a stale response while it revalidates it in the background (default: 2).
-This setting is overridden by the stale-while-revalidate HTTP Cache-Control
-extension (see RFC 5861).
+Whether to enable debug mode for caching. If enabled, the cache will
+automatically update when the original file is changed (both with code and
+annotation changes). For performance reasons, it is recommended to disable
+debug mode in production, which will happen automatically if you use the
+default value.
 
-stale_if_error
+file_cache_dir
 ..............
 
-**type**: ``integer``
-
-Specifies the default number of seconds (the granularity is the second) during
-which the cache can serve a stale response when an error is encountered
-(default: 60). This setting is overridden by the stale-if-error HTTP
-Cache-Control extension (see RFC 5861).
-
- .. _configuration-framework-http_method_override:
-
-http_method_override
-~~~~~~~~~~~~~~~~~~~~
-
-**type**: ``boolean`` **default**: ``false``
-
-This determines whether the ``_method`` request parameter is used as the
-intended HTTP method on POST requests. If enabled, the
-:method:`Request::enableHttpMethodParameterOverride <Symfony\\Component\\HttpFoundation\\Request::enableHttpMethodParameterOverride>`
-method gets called automatically. It becomes the service container parameter
-named ``kernel.http_method_override``.
-
-.. seealso::
-
-    :ref:`Changing the Action and HTTP Method <forms-change-action-method>` of
-    Symfony forms.
+**type**: ``string`` **default**: ``%kernel.cache_dir%/annotations``
 
-.. warning::
+The directory to store cache files for annotations, in case
+``annotations.cache`` is set to ``'file'``.
 
-    If you're using the :ref:`HttpCache Reverse Proxy <symfony2-reverse-proxy>`
-    with this option, the kernel will ignore the ``_method`` parameter,
-    which could lead to errors.
+.. _reference-assets:
 
-    To fix this, invoke the ``enableHttpMethodParameterOverride()`` method
-    before creating the ``Request`` object::
+assets
+~~~~~~
 
-        // public/index.php
+The following options configure the behavior of the
+:ref:`Twig asset() function <reference-twig-function-asset>`.
 
-        // ...
-        $kernel = new CacheKernel($kernel);
+.. _reference-assets-base-path:
 
-        Request::enableHttpMethodParameterOverride(); // <-- add this line
-        $request = Request::createFromGlobals();
-        // ...
+base_path
+.........
 
-trust_x_sendfile_type_header
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+**type**: ``string``
 
-**type**: ``boolean`` **default**: ``false``
+This option allows you to prepend a base path to the URLs generated for assets:
 
-``X-Sendfile`` is a special HTTP header that tells web servers to replace the
-response contents by the file that is defined in that header. This improves
-performance because files are no longer served by your application but directly
-by the web server.
+.. configuration-block::
 
-This configuration option determines whether to trust ``x-sendfile`` header for
-BinaryFileResponse. If enabled, Symfony calls the
-:method:`BinaryFileResponse::trustXSendfileTypeHeader <Symfony\\Component\\HttpFoundation\\BinaryFileResponse::trustXSendfileTypeHeader>`
-method automatically. It becomes the service container parameter named
-``kernel.trust_x_sendfile_type_header``.
+    .. code-block:: yaml
 
-.. _reference-framework-trusted-headers:
+        # config/packages/framework.yaml
+        framework:
+            # ...
+            assets:
+                base_path: '/images'
 
-trusted_headers
-~~~~~~~~~~~~~~~
+    .. code-block:: xml
 
-The ``trusted_headers`` option is needed to configure which client information
-should be trusted (e.g. their host) when running Symfony behind a load balancer
-or a reverse proxy. See :doc:`/deployment/proxies`.
+        <!-- config/packages/framework.xml -->
+        <?xml version="1.0" encoding="UTF-8" ?>
+        <container xmlns="http://symfony.com/schema/dic/services"
+            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+            xmlns:framework="http://symfony.com/schema/dic/symfony"
+            xsi:schemaLocation="http://symfony.com/schema/dic/services
+                https://symfony.com/schema/dic/services/services-1.0.xsd
+                http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
 
-.. _reference-framework-trusted-proxies:
+            <framework:config>
+                <framework:assets base-path="/images"/>
+            </framework:config>
+        </container>
 
-trusted_proxies
-~~~~~~~~~~~~~~~
+    .. code-block:: php
 
-The ``trusted_proxies`` option is needed to get precise information about the
-client (e.g. their IP address) when running Symfony behind a load balancer or a
-reverse proxy. See :doc:`/deployment/proxies`.
+        // config/packages/framework.php
+        use Symfony\Config\FrameworkConfig;
 
-ide
-~~~
+        return static function (FrameworkConfig $framework): void {
+            // ...
+            $framework->assets()
+                ->basePath('/images');
+        };
 
-**type**: ``string`` **default**: ``%env(default::SYMFONY_IDE)%``
+With this configuration, a call to ``asset('logo.png')`` will generate
+``/images/logo.png`` instead of ``/logo.png``.
 
-Symfony turns file paths seen in variable dumps and exception messages into
-links that open those files right inside your browser. If you prefer to open
-those files in your favorite IDE or text editor, set this option to any of the
-following values: ``phpstorm``, ``sublime``, ``textmate``, ``macvim``, ``emacs``,
-``atom`` and ``vscode``.
+.. _reference-templating-base-urls:
+.. _reference-assets-base-urls:
 
-.. note::
+base_urls
+.........
 
-    The ``phpstorm`` option is supported natively by PhpStorm on macOS and
-    Windows; Linux requires installing `phpstorm-url-handler`_.
+**type**: ``array``
 
-If you use another editor, the expected configuration value is a URL template
-that contains an ``%f`` placeholder where the file path is expected and ``%l``
-placeholder for the line number (percentage signs (``%``) must be escaped by
-doubling them to prevent Symfony from interpreting them as container parameters).
+This option allows you to define base URLs to be used for assets.
+If multiple base URLs are provided, Symfony will select one from the
+collection each time it generates an asset's path:
 
 .. configuration-block::
 
@@ -254,7 +132,10 @@ doubling them to prevent Symfony from interpreting them as container parameters)
 
         # config/packages/framework.yaml
         framework:
-            ide: 'myide://open?url=file://%%f&line=%%l'
+            # ...
+            assets:
+                base_urls:
+                    - 'http://cdn.example.com/'
 
     .. code-block:: xml
 
@@ -267,7 +148,9 @@ doubling them to prevent Symfony from interpreting them as container parameters)
                 https://symfony.com/schema/dic/services/services-1.0.xsd
                 http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
 
-            <framework:config ide="myide://open?url=file://%%f&line=%%l"/>
+            <framework:config>
+                <framework:assets base-url="http://cdn.example.com/"/>
+            </framework:config>
         </container>
 
     .. code-block:: php
@@ -276,198 +159,130 @@ doubling them to prevent Symfony from interpreting them as container parameters)
         use Symfony\Config\FrameworkConfig;
 
         return static function (FrameworkConfig $framework): void {
-            $framework->ide('myide://open?url=file://%%f&line=%%l');
-        };
-
-Since every developer uses a different IDE, the recommended way to enable this
-feature is to configure it on a system level. First, you can define this option
-in the ``SYMFONY_IDE`` environment variable, which Symfony reads automatically
-when ``framework.ide`` config is not set.
-
-Another alternative is to set the ``xdebug.file_link_format`` option in your
-``php.ini`` configuration file. The format to use is the same as for the
-``framework.ide`` option, but without the need to escape the percent signs
-(``%``) by doubling them:
-
-.. code-block:: ini
-
-    // example for PhpStorm
-    xdebug.file_link_format="phpstorm://open?file=%f&line=%l"
-
-    // example for PhpStorm with Jetbrains Toolbox
-    xdebug.file_link_format="jetbrains://phpstorm/navigate/reference?project=example&path=%f:%l"
-
-    // example for Sublime Text
-    xdebug.file_link_format="subl://open?url=file://%f&line=%l"
+            // ...
+            $framework->assets()
+                ->baseUrls(['http://cdn.example.com/']);
+        };
 
-.. note::
+.. _reference-assets-json-manifest-path:
+.. _reference-templating-json-manifest-path:
 
-    If both ``framework.ide`` and ``xdebug.file_link_format`` are defined,
-    Symfony uses the value of the ``xdebug.file_link_format`` option.
+json_manifest_path
+..................
 
-.. tip::
+**type**: ``string`` **default**: ``null``
 
-    Setting the ``xdebug.file_link_format`` ini option works even if the Xdebug
-    extension is not enabled.
+The file path or absolute URL to a ``manifest.json`` file containing an
+associative array of asset names and their respective compiled names. A common
+cache-busting technique using a "manifest" file works by writing out assets with
+a "hash" appended to their file names (e.g. ``main.ae433f1cb.css``) during a
+front-end compilation routine.
 
 .. tip::
 
-    When running your app in a container or in a virtual machine, you can tell
-    Symfony to map files from the guest to the host by changing their prefix.
-    This map should be specified at the end of the URL template, using ``&`` and
-    ``>`` as guest-to-host separators:
-
-    .. code-block:: text
-
-        // /path/to/guest/.../file will be opened
-        // as /path/to/host/.../file on the host
-        // and /var/www/app/ as /projects/my_project/ also
-        'myide://%%f:%%l&/path/to/guest/>/path/to/host/&/var/www/app/>/projects/my_project/&...'
-
-        // example for PhpStorm
-        'phpstorm://open?file=%%f&line=%%l&/var/www/app/>/projects/my_project/'
-
-.. _reference-framework-test:
-
-test
-~~~~
-
-**type**: ``boolean``
-
-If this configuration setting is present (and not ``false``), then the services
-related to testing your application (e.g. ``test.client``) are loaded. This
-setting should be present in your ``test`` environment (usually via
-``config/packages/test/framework.yaml``).
-
-.. seealso::
-
-    For more information, see :doc:`/testing`.
-
-.. _config-framework-default_locale:
-
-default_locale
-~~~~~~~~~~~~~~
-
-**type**: ``string`` **default**: ``en``
-
-The default locale is used if no ``_locale`` routing parameter has been
-set. It is available with the
-:method:`Request::getDefaultLocale <Symfony\\Component\\HttpFoundation\\Request::getDefaultLocale>`
-method.
-
-.. seealso::
-
-    You can read more information about the default locale in
-    :ref:`translation-default-locale`.
-
-.. _reference-translator-enabled-locales:
-.. _reference-enabled-locales:
-
-enabled_locales
-...............
-
-**type**: ``array`` **default**: ``[]`` (empty array = enable all locales)
+    Symfony's :ref:`Webpack Encore <frontend-webpack-encore>` supports
+    :ref:`outputting hashed assets <encore-long-term-caching>`. Moreover, this
+    can be incorporated into many other workflows, including Webpack and
+    Gulp using `webpack-manifest-plugin`_ and `gulp-rev`_, respectively.
 
-Symfony applications generate by default the translation files for validation
-and security messages in all locales. If your application only uses some
-locales, use this option to restrict the files generated by Symfony and improve
-performance a bit:
+This option can be set globally for all assets and individually for each asset
+package:
 
 .. configuration-block::
 
     .. code-block:: yaml
 
-        # config/packages/translation.yaml
+        # config/packages/framework.yaml
         framework:
-            enabled_locales: ['en', 'es']
+            assets:
+                # this manifest is applied to every asset (including packages)
+                json_manifest_path: "%kernel.project_dir%/public/build/manifest.json"
+                # you can use absolute URLs too and Symfony will download them automatically
+                # json_manifest_path: 'https://cdn.example.com/manifest.json'
+                packages:
+                    foo_package:
+                        # this package uses its own manifest (the default file is ignored)
+                        json_manifest_path: "%kernel.project_dir%/public/build/a_different_manifest.json"
+                        # Throws an exception when an asset is not found in the manifest
+                        strict_mode: %kernel.debug%
+                    bar_package:
+                        # this package uses the global manifest (the default file is used)
+                        base_path: '/images'
 
     .. code-block:: xml
 
-        <!-- config/packages/translation.xml -->
+        <!-- config/packages/framework.xml -->
         <?xml version="1.0" encoding="UTF-8" ?>
         <container xmlns="http://symfony.com/schema/dic/services"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns:framework="http://symfony.com/schema/dic/symfony"
-            xsi:schemaLocation="http://symfony.com/schema/dic/services
-                https://symfony.com/schema/dic/services/services-1.0.xsd
+            xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd
                 http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
 
             <framework:config>
-                <enabled-locale>en</enabled-locale>
-                <enabled-locale>es</enabled-locale>
+                <!-- this manifest is applied to every asset (including packages) -->
+                <framework:assets json-manifest-path="%kernel.project_dir%/public/build/manifest.json">
+                <!-- you can use absolute URLs too and Symfony will download them automatically -->
+                <!-- <framework:assets json-manifest-path="https://cdn.example.com/manifest.json"> -->
+                    <!-- this package uses its own manifest (the default file is ignored) -->
+                    <!-- Throws an exception when an asset is not found in the manifest -->
+                    <framework:package
+                        name="foo_package"
+                        json-manifest-path="%kernel.project_dir%/public/build/a_different_manifest.json" strict-mode="%kernel.debug%"/>
+                    <!-- this package uses the global manifest (the default file is used) -->
+                    <framework:package
+                        name="bar_package"
+                        base-path="/images"/>
+                </framework:assets>
             </framework:config>
         </container>
 
     .. code-block:: php
 
-        // config/packages/translation.php
+        // config/packages/framework.php
         use Symfony\Config\FrameworkConfig;
 
         return static function (FrameworkConfig $framework): void {
-            $framework->enabledLocales(['en', 'es']);
-        };
-
-An added bonus of defining the enabled locales is that they are automatically
-added as a requirement of the :ref:`special _locale parameter <routing-locale-parameter>`.
-For example, if you define this value as ``['ar', 'he', 'ja', 'zh']``, the
-``_locale`` routing parameter will have an ``ar|he|ja|zh`` requirement. If some
-user makes requests with a locale not included in this option, they'll see a 404 error.
-
-set_content_language_from_locale
-................................
-
-**type**: ``boolean`` **default**: ``false``
-
-If this option is set to ``true``, the response will have a ``Content-Language``
-HTTP header set with the ``Request`` locale.
-
-set_locale_from_accept_language
-...............................
-
-**type**: ``boolean`` **default**: ``false``
-
-If this option is set to ``true``, the ``Request`` locale will automatically be
-set to the value of the ``Accept-Language`` HTTP header.
+            // ...
+            $framework->assets()
+                // this manifest is applied to every asset (including packages)
+                ->jsonManifestPath('%kernel.project_dir%/public/build/manifest.json');
 
-When the ``_locale`` request attribute is passed, the ``Accept-Language`` header
-is ignored.
+            // you can use absolute URLs too and Symfony will download them automatically
+            // 'json_manifest_path' => 'https://cdn.example.com/manifest.json',
+            $framework->assets()->package('foo_package')
+                // this package uses its own manifest (the default file is ignored)
+                ->jsonManifestPath('%kernel.project_dir%/public/build/a_different_manifest.json')
+                // Throws an exception when an asset is not found in the manifest
+                ->setStrictMode('%kernel.debug%');
 
-disallow_search_engine_index
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+            $framework->assets()->package('bar_package')
+                // this package uses the global manifest (the default file is used)
+                ->basePath('/images');
+        };
 
-**type**: ``boolean`` **default**: ``true`` when the debug mode is enabled, ``false`` otherwise.
+.. note::
 
-If ``true``, Symfony adds a ``X-Robots-Tag: noindex`` HTTP tag to all responses
-(unless your own app adds that header, in which case it's not modified). This
-`X-Robots-Tag HTTP header`_ tells search engines to not index your web site.
-This option is a protection measure in case you accidentally publish your site
-in debug mode.
+    This parameter cannot be set at the same time as ``version`` or ``version_strategy``.
+    Additionally, this option cannot be nullified at the package scope if a global manifest
+    file is specified.
 
-.. _configuration-framework-trusted-hosts:
+.. tip::
 
-trusted_hosts
-~~~~~~~~~~~~~
+    If you request an asset that is *not found* in the ``manifest.json`` file, the original -
+    *unmodified* - asset path will be returned.
+    You can set ``strict_mode`` to ``true`` to get an exception when an asset is *not found*.
 
-**type**: ``array`` | ``string`` **default**: ``[]``
+.. note::
 
-A lot of different attacks have been discovered relying on inconsistencies
-in handling the ``Host`` header by various software (web servers, reverse
-proxies, web frameworks, etc.). Basically, every time the framework is
-generating an absolute URL (when sending an email to reset a password for
-instance), the host might have been manipulated by an attacker.
+    If a URL is set, the JSON manifest is downloaded on each request using the `http_client`_.
 
-.. seealso::
+.. _reference-framework-assets-packages:
 
-    You can read `HTTP Host header attacks`_ for more information about
-    these kinds of attacks.
+packages
+........
 
-The Symfony :method:`Request::getHost() <Symfony\\Component\\HttpFoundation\\Request::getHost>`
-method might be vulnerable to some of these attacks because it depends on
-the configuration of your web server. One simple solution to avoid these
-attacks is to configure a list of hosts that your Symfony application can respond
-to. That's the purpose of this ``trusted_hosts`` option. If the incoming
-request's hostname doesn't match one of the regular expressions in this list,
-the application won't respond and the user will receive a 400 response.
+You can group assets into packages, to specify different base URLs for them:
 
 .. configuration-block::
 
@@ -475,7 +290,11 @@ the application won't respond and the user will receive a 400 response.
 
         # config/packages/framework.yaml
         framework:
-            trusted_hosts:  ['^example\.com$', '^example\.org$']
+            # ...
+            assets:
+                packages:
+                    avatars:
+                        base_urls: 'http://static_cdn.example.com/avatars'
 
     .. code-block:: xml
 
@@ -489,9 +308,11 @@ the application won't respond and the user will receive a 400 response.
                 http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
 
             <framework:config>
-                <framework:trusted-host>^example\.com$</framework:trusted-host>
-                <framework:trusted-host>^example\.org$</framework:trusted-host>
-                <!-- ... -->
+                <framework:assets>
+                    <framework:package
+                        name="avatars"
+                        base-url="http://static_cdn.example.com/avatars"/>
+                </framework:assets>
             </framework:config>
         </container>
 
@@ -501,79 +322,60 @@ the application won't respond and the user will receive a 400 response.
         use Symfony\Config\FrameworkConfig;
 
         return static function (FrameworkConfig $framework): void {
-            $framework->trustedHosts(['^example\.com$', '^example\.org$']);
+            // ...
+            $framework->assets()
+                ->package('avatars')
+                    ->baseUrls(['http://static_cdn.example.com/avatars']);
         };
 
-Hosts can also be configured to respond to any subdomain, via
-``^(.+\.)?example\.com$`` for instance.
+Now you can use the ``avatars`` package in your templates:
 
-In addition, you can also set the trusted hosts in the front controller
-using the ``Request::setTrustedHosts()`` method::
+.. code-block:: html+twig
 
-    // public/index.php
-    Request::setTrustedHosts(['^(.+\.)?example\.com$', '^(.+\.)?example\.org$']);
+    <img src="{{ asset('...', 'avatars') }}">
 
-The default value for this option is an empty array, meaning that the application
-can respond to any given host.
-
-.. seealso::
-
-    Read more about this in the `Security Advisory Blog post`_.
-
-.. _reference-framework-form:
-
-form
-~~~~
-
-.. _reference-form-enabled:
-
-enabled
-.......
-
-**type**: ``boolean`` **default**: ``true`` or ``false`` depending on your installation
-
-Whether to enable the form services or not in the service container. If
-you don't use forms, setting this to ``false`` may increase your application's
-performance because less services will be loaded into the container.
-
-This option will automatically be set to ``true`` when one of the child
-settings is configured.
-
-.. note::
-
-    This will automatically enable the `validation`_.
-
-.. seealso::
+Each package can configure the following options:
 
-    For more details, see :doc:`/forms`.
+* :ref:`base_path <reference-assets-base-path>`
+* :ref:`base_urls <reference-assets-base-urls>`
+* :ref:`version_strategy <reference-assets-version-strategy>`
+* :ref:`version <reference-framework-assets-version>`
+* :ref:`version_format <reference-assets-version-format>`
+* :ref:`json_manifest_path <reference-assets-json-manifest-path>`
+* :ref:`strict_mode <reference-assets-strict-mode>`
 
-.. _reference-form-field-name:
+.. _reference-assets-strict-mode:
 
-field_name
-..........
+strict_mode
+...........
 
-**type**: ``string`` **default**: ``_token``
+**type**: ``boolean`` **default**: ``false``
 
-This is the field name that you should give to the CSRF token field of your forms.
+When enabled, the strict mode asserts that all requested assets are in the
+manifest file. This option is useful to detect typos or missing assets, the
+recommended value is ``%kernel.debug%``.
 
-.. _reference-framework-csrf-protection:
+.. _reference-framework-assets-version:
+.. _ref-framework-assets-version:
 
-csrf_protection
-~~~~~~~~~~~~~~~
+version
+.......
 
-.. seealso::
+**type**: ``string``
 
-    For more information about CSRF protection, see :doc:`/security/csrf`.
+This option is used to *bust* the cache on assets by globally adding a query
+parameter to all rendered asset paths (e.g. ``/images/logo.png?v2``). This
+applies only to assets rendered via the Twig ``asset()`` function (or PHP
+equivalent).
 
-.. _reference-csrf_protection-enabled:
+For example, suppose you have the following:
 
-enabled
-.......
+.. code-block:: html+twig
 
-**type**: ``boolean`` **default**: ``true`` or ``false`` depending on your installation
+    <img src="{{ asset('images/logo.png') }}" alt="Symfony!"/>
 
-This option can be used to disable CSRF protection on *all* forms. But you
-can also :ref:`disable CSRF protection on individual forms <form-csrf-customization>`.
+By default, this will render a path to your image such as ``/images/logo.png``.
+Now, activate the ``version`` option:
 
 .. configuration-block::
 
@@ -582,7 +384,8 @@ can also :ref:`disable CSRF protection on individual forms <form-csrf-customizat
         # config/packages/framework.yaml
         framework:
             # ...
-            csrf_protection: true
+            assets:
+                version: 'v2'
 
     .. code-block:: xml
 
@@ -593,10 +396,10 @@ can also :ref:`disable CSRF protection on individual forms <form-csrf-customizat
             xmlns:framework="http://symfony.com/schema/dic/symfony"
             xsi:schemaLocation="http://symfony.com/schema/dic/services
                 https://symfony.com/schema/dic/services/services-1.0.xsd
-                http://symfony.com/schema/dic/symfony
-                https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
+                http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
+
             <framework:config>
-                <framework:csrf-protection enabled="true"/>
+                <framework:assets version="v2"/>
             </framework:config>
         </container>
 
@@ -604,46 +407,81 @@ can also :ref:`disable CSRF protection on individual forms <form-csrf-customizat
 
         // config/packages/framework.php
         use Symfony\Config\FrameworkConfig;
+
         return static function (FrameworkConfig $framework): void {
-            $framework->csrfProtection()
-                ->enabled(true)
-            ;
+            // ...
+            $framework->assets()
+                ->version('v2');
         };
 
-If you're using forms, but want to avoid starting your session (e.g. using
-forms in an API-only website), ``csrf_protection`` will need to be set to
-``false``.
+Now, the same asset will be rendered as ``/images/logo.png?v2`` If you use
+this feature, you **must** manually increment the ``version`` value
+before each deployment so that the query parameters change.
 
-.. _config-framework-error_controller:
+You can also control how the query string works via the `version_format`_
+option.
 
-error_controller
-~~~~~~~~~~~~~~~~
+.. note::
 
-**type**: ``string`` **default**: ``error_controller``
+    This parameter cannot be set at the same time as ``version_strategy`` or ``json_manifest_path``.
 
-This is the controller that is called when an exception is thrown anywhere in
-your application. The default controller
-(:class:`Symfony\\Component\\HttpKernel\\Controller\\ErrorController`)
-renders specific templates under different error conditions (see
-:doc:`/controller/error_pages`).
+.. tip::
 
-esi
-~~~
+    As with all settings, you can use a parameter as value for the
+    ``version``. This makes it easier to increment the cache on each
+    deployment.
 
-.. seealso::
+.. _reference-templating-version-format:
+.. _reference-assets-version-format:
 
-    You can read more about Edge Side Includes (ESI) in :ref:`edge-side-includes`.
+version_format
+..............
 
-.. _reference-esi-enabled:
+**type**: ``string`` **default**: ``%%s?%%s``
 
-enabled
-.......
+This specifies a :phpfunction:`sprintf` pattern that will be used with the
+`version`_ option to construct an asset's path. By default, the pattern
+adds the asset's version as a query string. For example, if
+``version_format`` is set to ``%%s?version=%%s`` and ``version``
+is set to ``5``, the asset's path would be ``/images/logo.png?version=5``.
 
-**type**: ``boolean`` **default**: ``false``
+.. note::
 
-Whether to enable the edge side includes support in the framework.
+    All percentage signs (``%``) in the format string must be doubled to
+    escape the character. Without escaping, values might inadvertently be
+    interpreted as :ref:`service-container-parameters`.
 
-You can also set ``esi`` to ``true`` to enable it:
+.. tip::
+
+    Some CDN's do not support cache-busting via query strings, so injecting
+    the version into the actual file path is necessary. Thankfully,
+    ``version_format`` is not limited to producing versioned query
+    strings.
+
+    The pattern receives the asset's original path and version as its first
+    and second parameters, respectively. Since the asset's path is one
+    parameter, you cannot modify it in-place (e.g. ``/images/logo-v5.png``);
+    however, you can prefix the asset's path using a pattern of
+    ``version-%%2$s/%%1$s``, which would result in the path
+    ``version-5/images/logo.png``.
+
+    URL rewrite rules could then be used to disregard the version prefix
+    before serving the asset. Alternatively, you could copy assets to the
+    appropriate version path as part of your deployment process and forgot
+    any URL rewriting. The latter option is useful if you would like older
+    asset versions to remain accessible at their original URL.
+
+.. _reference-assets-version-strategy:
+.. _reference-templating-version-strategy:
+
+version_strategy
+................
+
+**type**: ``string`` **default**: ``null``
+
+The service id of the :doc:`asset version strategy </frontend/custom_version_strategy>`
+applied to the assets. This option can be set globally for all assets and
+individually for each asset package:
 
 .. configuration-block::
 
@@ -651,7 +489,19 @@ You can also set ``esi`` to ``true`` to enable it:
 
         # config/packages/framework.yaml
         framework:
-            esi: true
+            assets:
+                # this strategy is applied to every asset (including packages)
+                version_strategy: 'app.asset.my_versioning_strategy'
+                packages:
+                    foo_package:
+                        # this package removes any versioning (its assets won't be versioned)
+                        version: ~
+                    bar_package:
+                        # this package uses its own strategy (the default strategy is ignored)
+                        version_strategy: 'app.asset.another_version_strategy'
+                    baz_package:
+                        # this package inherits the default strategy
+                        base_path: '/images'
 
     .. code-block:: xml
 
@@ -660,12 +510,24 @@ You can also set ``esi`` to ``true`` to enable it:
         <container xmlns="http://symfony.com/schema/dic/services"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns:framework="http://symfony.com/schema/dic/symfony"
-            xsi:schemaLocation="http://symfony.com/schema/dic/services
-                https://symfony.com/schema/dic/services/services-1.0.xsd
+            xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd
                 http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
 
             <framework:config>
-                <framework:esi/>
+                <framework:assets version-strategy="app.asset.my_versioning_strategy">
+                    <!-- this package removes any versioning (its assets won't be versioned) -->
+                    <framework:package
+                        name="foo_package"
+                        version="null"/>
+                    <!-- this package uses its own strategy (the default strategy is ignored) -->
+                    <framework:package
+                        name="bar_package"
+                        version-strategy="app.asset.another_version_strategy"/>
+                    <!-- this package inherits the default strategy -->
+                    <framework:package
+                        name="baz_package"
+                        base_path="/images"/>
+                </framework:assets>
             </framework:config>
         </container>
 
@@ -675,128 +537,117 @@ You can also set ``esi`` to ``true`` to enable it:
         use Symfony\Config\FrameworkConfig;
 
         return static function (FrameworkConfig $framework): void {
-            $framework->esi()->enabled(true);
-        };
-
-fragments
-~~~~~~~~~
+            // ...
+            $framework->assets()
+                ->versionStrategy('app.asset.my_versioning_strategy');
 
-.. seealso::
+            $framework->assets()->package('foo_package')
+                // this package removes any versioning (its assets won't be versioned)
+                ->version(null);
 
-    Learn more about fragments in the
-    :ref:`HTTP Cache article <http_cache-fragments>`.
+            $framework->assets()->package('bar_package')
+                // this package uses its own strategy (the default strategy is ignored)
+                ->versionStrategy('app.asset.another_version_strategy');
 
-.. _reference-fragments-enabled:
+            $framework->assets()->package('baz_package')
+                // this package inherits the default strategy
+                ->basePath('/images');
+        };
 
-enabled
-.......
+.. note::
 
-**type**: ``boolean`` **default**: ``false``
+    This parameter cannot be set at the same time as ``version`` or ``json_manifest_path``.
 
-Whether to enable the fragment listener or not. The fragment listener is
-used to render ESI fragments independently of the rest of the page.
+.. _reference-cache:
 
-This setting is automatically set to ``true`` when one of the child settings
-is configured.
+cache
+~~~~~
 
-hinclude_default_template
-.........................
+.. _reference-cache-app:
 
-**type**: ``string`` **default**: ``null``
+app
+...
 
-Sets the content shown during the loading of the fragment or when JavaScript
-is disabled. This can be either a template name or the content itself.
+**type**: ``string`` **default**: ``cache.adapter.filesystem``
 
-.. seealso::
+The cache adapter used by the ``cache.app`` service. The FrameworkBundle
+ships with multiple adapters: ``cache.adapter.apcu``, ``cache.adapter.system``,
+``cache.adapter.filesystem``, ``cache.adapter.psr6``, ``cache.adapter.redis``,
+``cache.adapter.memcached``, ``cache.adapter.pdo`` and
+``cache.adapter.doctrine_dbal``.
 
-    See :ref:`templates-hinclude` for more information about hinclude.
+There's also a special adapter called ``cache.adapter.array`` which stores
+contents in memory using a PHP array and it's used to disable caching (mostly on
+the ``dev`` environment).
 
-.. _reference-fragments-path:
+.. tip::
 
-path
-....
+    It might be tough to understand at the beginning, so to avoid confusion
+    remember that all pools perform the same actions but on different medium
+    given the adapter they are based on. Internally, a pool wraps the definition
+    of an adapter.
 
-**type**: ``string`` **default**: ``/_fragment``
+default_doctrine_provider
+.........................
 
-The path prefix for fragments. The fragment listener will only be executed
-when the request starts with this path.
+**type**: ``string``
 
-.. _reference-http-client:
+The service name to use as your default Doctrine provider. The provider is
+available as the ``cache.default_doctrine_provider`` service.
 
-http_client
-~~~~~~~~~~~
+default_memcached_provider
+..........................
 
-When the HttpClient component is installed, an HTTP client is available
-as a service named ``http_client`` or using the autowiring alias
-:class:`Symfony\\Contracts\\HttpClient\\HttpClientInterface`.
+**type**: ``string`` **default**: ``memcached://localhost``
 
-.. _reference-http-client-default-options:
+The DSN to use by the Memcached provider. The provider is available as the ``cache.default_memcached_provider``
+service.
 
-This service can be configured using ``framework.http_client.default_options``:
+default_pdo_provider
+....................
 
-.. configuration-block::
+**type**: ``string`` **default**: ``doctrine.dbal.default_connection``
 
-    .. code-block:: yaml
+The service id of the database connection, which should be either a PDO or a
+Doctrine DBAL instance. The provider is available as the ``cache.default_pdo_provider``
+service.
 
-        # config/packages/framework.yaml
-        framework:
-            # ...
-            http_client:
-                max_host_connections: 10
-                default_options:
-                    headers: { 'X-Powered-By': 'ACME App' }
-                    max_redirects: 7
+default_psr6_provider
+.....................
 
-    .. code-block:: xml
+**type**: ``string``
 
-        <!-- config/packages/framework.xml -->
-        <?xml version="1.0" encoding="UTF-8" ?>
-        <container xmlns="http://symfony.com/schema/dic/services"
-            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-            xmlns:framework="http://symfony.com/schema/dic/symfony"
-            xsi:schemaLocation="http://symfony.com/schema/dic/services
-                https://symfony.com/schema/dic/services/services-1.0.xsd
-                http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
+The service name to use as your default PSR-6 provider. It is available as
+the ``cache.default_psr6_provider`` service.
 
-            <framework:config>
-                <framework:http-client max-host-connections="10">
-                    <framework:default-options max-redirects="7">
-                        <framework:header name="X-Powered-By">ACME App</framework:header>
-                    </framework:default-options>
-                </framework:http-client>
-            </framework:config>
-        </container>
+default_redis_provider
+......................
 
-    .. code-block:: php
+**type**: ``string`` **default**: ``redis://localhost``
 
-        // config/packages/framework.php
-        $container->loadFromExtension('framework', [
-            'http_client' => [
-                'max_host_connections' => 10,
-                'default_options' => [
-                    'headers' => [
-                        'X-Powered-By' => 'ACME App',
-                    ],
-                    'max_redirects' => 7,
-                ],
-            ],
-        ]);
+The DSN to use by the Redis provider. The provider is available as the ``cache.default_redis_provider``
+service.
 
-    .. code-block:: php-standalone
+directory
+.........
 
-        $client = HttpClient::create([
-            'headers' => [
-                'X-Powered-By' => 'ACME App',
-            ],
-            'max_redirects' => 7,
-        ], 10);
+**type**: ``string`` **default**: ``%kernel.cache_dir%/pools``
 
-.. _reference-http-client-scoped-clients:
+The path to the cache directory used by services inheriting from the
+``cache.adapter.filesystem`` adapter (including ``cache.app``).
 
-Multiple pre-configured HTTP client services can be defined, each with its
-service name defined as a key under ``scoped_clients``. Scoped clients inherit
-the default options defined for the ``http_client`` service. You can override
-these options and can define a few others:
+pools
+.....
+
+**type**: ``array``
+
+A list of cache pools to be created by the framework extension.
+
+.. seealso::
+
+    For more information about how pools work, see :ref:`cache pools <component-cache-cache-pools>`.
+
+To configure a Redis cache pool with a default lifetime of 1 hour, do the following:
 
 .. configuration-block::
 
@@ -804,12 +655,11 @@ these options and can define a few others:
 
         # config/packages/framework.yaml
         framework:
-            # ...
-            http_client:
-                scoped_clients:
-                    my_api.client:
-                        auth_bearer: secret_bearer_token
-                        # ...
+            cache:
+                pools:
+                    cache.mycache:
+                        adapter: cache.adapter.redis
+                        default_lifetime: 3600
 
     .. code-block:: xml
 
@@ -823,313 +673,1142 @@ these options and can define a few others:
                 http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
 
             <framework:config>
-                <framework:http-client>
-                    <framework:scoped-client name="my_api.client" auth-bearer="secret_bearer_token"/>
-                </framework:http-client>
+                <framework:cache>
+                    <framework:pool
+                        name="cache.mycache"
+                        adapter="cache.adapter.redis"
+                        default-lifetime="3600"
+                    />
+                </framework:cache>
+                <!-- ... -->
             </framework:config>
         </container>
 
     .. code-block:: php
 
         // config/packages/framework.php
-        $container->loadFromExtension('framework', [
-            'http_client' => [
-                'scoped_clients' => [
-                    'my_api.client' => [
-                        'auth_bearer' => 'secret_bearer_token',
-                        // ...
-                    ],
-                ],
-            ],
-        ]);
+        use Symfony\Config\FrameworkConfig;
 
-    .. code-block:: php-standalone
+        return static function (FrameworkConfig $framework): void {
+            $framework->cache()
+                ->pool('cache.mycache')
+                    ->adapters(['cache.adapter.redis'])
+                    ->defaultLifetime(3600);
+        };
 
-        $client = HttpClient::createForBaseUri('https://...', [
-            'auth_bearer' => 'secret_bearer_token',
-            // ...
-        ]);
+adapter
+"""""""
 
-Options defined for scoped clients apply only to URLs that match either their
-`base_uri`_ or the `scope`_ option when it is defined. Non-matching URLs always
-use default options.
+**type**: ``string`` **default**: ``cache.app``
 
-Each scoped client also defines a corresponding named autowiring alias.
-If you use for example
-``Symfony\Contracts\HttpClient\HttpClientInterface $myApiClient``
-as the type and name of an argument, autowiring will inject the ``my_api.client``
-service into your autowired classes.
+The service name of the adapter to use. You can specify one of the default
+services that follow the pattern ``cache.adapter.[type]``. Alternatively you
+can specify another cache pool as base, which will make this pool inherit the
+settings from the base pool as defaults.
 
-auth_basic
-..........
+.. note::
+
+    Your service needs to implement the ``Psr\Cache\CacheItemPoolInterface`` interface.
+
+clearer
+"""""""
 
 **type**: ``string``
 
-The username and password used to create the ``Authorization`` HTTP header
-used in HTTP Basic authentication. The value of this option must follow the
-format ``username:password``.
+The cache clearer used to clear your PSR-6 cache.
 
-auth_bearer
-...........
+.. seealso::
 
-**type**: ``string``
+    For more information, see :class:`Symfony\\Component\\HttpKernel\\CacheClearer\\Psr6CacheClearer`.
 
-The token used to create the ``Authorization`` HTTP header used in HTTP Bearer
-authentication (also called token authentication).
+default_lifetime
+""""""""""""""""
 
-auth_ntlm
-.........
+**type**: ``integer`` | ``string``
 
-**type**: ``string``
+Default lifetime of your cache items. Give an integer value to set the default
+lifetime in seconds. A string value could be ISO 8601 time interval, like ``"PT5M"``
+or a PHP date expression that is accepted by ``strtotime()``, like ``"5 minutes"``.
 
-The username and password used to create the ``Authorization`` HTTP header used
-in the `Microsoft NTLM authentication protocol`_. The value of this option must
-follow the format ``username:password``. This authentication mechanism requires
-using the cURL-based transport.
+If no value is provided, the cache adapter will fallback to the default value on
+the actual cache storage.
 
-.. _reference-http-client-base-uri:
+.. _reference-cache-pools-name:
 
-base_uri
-........
+name
+""""
 
-**type**: ``string``
+**type**: ``prototype``
 
-URI that is merged into relative URIs, following the rules explained in the
-`RFC 3986`_ standard. This is useful when all the requests you make share a
-common prefix (e.g. ``https://api.github.com/``) so you can avoid adding it to
-every request.
+Name of the pool you want to create.
 
-Here are some common examples of how ``base_uri`` merging works in practice:
+.. note::
 
-==========================  ==================  =============================
-``base_uri``                Relative URI        Actual Requested URI
-==========================  ==================  =============================
-http://example.org          /bar                http://example.org/bar
-http://example.org/foo      /bar                http://example.org/bar
-http://example.org/foo      bar                 http://example.org/bar
-http://example.org/foo/     /bar                http://example.org/bar
-http://example.org/foo/     bar                 http://example.org/foo/bar
-http://example.org          http://symfony.com  http://symfony.com
-http://example.org/?bar     bar                 http://example.org/bar
-http://example.org/api/v4   /bar                http://example.org/bar
-http://example.org/api/v4/  /bar                http://example.org/bar
-http://example.org/api/v4   bar                 http://example.org/api/bar
-http://example.org/api/v4/  bar                 http://example.org/api/v4/bar
-==========================  ==================  =============================
+    Your pool name must differ from ``cache.app`` or ``cache.system``.
 
-bindto
-......
+provider
+""""""""
 
 **type**: ``string``
 
-A network interface name, IP address, a host name or a UNIX socket to use as the
-outgoing network interface.
+Overwrite the default service name or DSN respectively, if you do not want to
+use what is configured as ``default_X_provider`` under ``cache``. See the
+description of the default provider setting above for information on how to
+specify your specific provider.
 
-buffer
-......
+public
+""""""
 
-**type**: ``boolean`` | ``Closure``
+**type**: ``boolean`` **default**: ``false``
 
-Buffering the response means that you can access its content multiple times
-without performing the request again. Buffering is enabled by default when the
-content type of the response is ``text/*``, ``application/json`` or ``application/xml``.
+Whether your service should be public or not.
 
-If this option is a boolean value, the response is buffered when the value is
-``true``. If this option is a closure, the response is buffered when the
-returned value is ``true`` (the closure receives as argument an array with the
-response headers).
+tags
+""""
 
-cafile
-......
+**type**: ``boolean`` | ``string`` **default**: ``null``
 
-**type**: ``string``
+Whether your service should be able to handle tags or not.
+Can also be the service id of another cache pool where tags will be stored.
 
-The path of the certificate authority file that contains one or more
-certificates used to verify the other servers' certificates.
+.. _reference-cache-prefix-seed:
 
-capath
-......
+prefix_seed
+...........
 
-**type**: ``string``
+**type**: ``string`` **default**: ``_%kernel.project_dir%.%kernel.container_class%``
 
-The path to a directory that contains one or more certificate authority files.
+This value is used as part of the "namespace" generated for the
+cache item keys. A common practice is to use the unique name of the application
+(e.g. ``symfony.com``) because that prevents naming collisions when deploying
+multiple applications into the same path (on different servers) that share the
+same cache backend.
 
-ciphers
-.......
+It's also useful when using `blue/green deployment`_ strategies and more
+generally, when you need to abstract out the actual deployment directory (for
+example, when warming caches offline).
 
-**type**: ``string``
+.. note::
 
-A list of the names of the ciphers allowed for the TLS connections. They
-can be separated by colons, commas or spaces (e.g. ``'RC4-SHA:TLS13-AES-128-GCM-SHA256'``).
+    The ``prefix_seed`` option is used at compile time. This means
+    that any change made to this value after container's compilation
+    will have no effect.
 
-crypto_method
-.............
+.. _reference-cache-system:
 
-**type**: ``integer``
+system
+......
 
-The minimum version of TLS to accept. The value must be one of the
-``STREAM_CRYPTO_METHOD_TLSv*_CLIENT`` constants defined by PHP.
+**type**: ``string`` **default**: ``cache.adapter.system``
 
-.. _reference-http-client-retry-delay:
+The cache adapter used by the ``cache.system`` service. It supports the same
+adapters available for the ``cache.app`` service.
 
-delay
-.....
+.. _reference-framework-csrf-protection:
 
-**type**: ``integer`` **default**: ``1000``
+csrf_protection
+~~~~~~~~~~~~~~~
 
-The initial delay in milliseconds used to compute the waiting time between retries.
+.. seealso::
 
-.. _reference-http-client-retry-enabled:
+    For more information about CSRF protection, see :doc:`/security/csrf`.
 
 enabled
 .......
 
-**type**: ``boolean`` **default**: ``false``
-
-Whether to enable the support for retry failed HTTP request or not.
-This setting is automatically set to true when one of the child settings is configured.
+**type**: ``boolean`` **default**: ``true`` or ``false`` depending on your installation
 
-extra
-.....
+This option can be used to disable CSRF protection on *all* forms. But you
+can also :ref:`disable CSRF protection on individual forms <form-csrf-customization>`.
 
-**type**: ``array``
+.. configuration-block::
 
-Arbitrary additional data to pass to the HTTP client for further use.
-This can be particularly useful when :ref:`decorating an existing client <extensibility>`.
+    .. code-block:: yaml
 
-.. _http-headers:
+        # config/packages/framework.yaml
+        framework:
+            # ...
+            csrf_protection: true
 
-headers
-.......
+    .. code-block:: xml
 
-**type**: ``array``
+        <!-- config/packages/framework.xml -->
+        <?xml version="1.0" encoding="UTF-8" ?>
+        <container xmlns="http://symfony.com/schema/dic/services"
+            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+            xmlns:framework="http://symfony.com/schema/dic/symfony"
+            xsi:schemaLocation="http://symfony.com/schema/dic/services
+                https://symfony.com/schema/dic/services/services-1.0.xsd
+                http://symfony.com/schema/dic/symfony
+                https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
+            <framework:config>
+                <framework:csrf-protection enabled="true"/>
+            </framework:config>
+        </container>
 
-An associative array of the HTTP headers added before making the request. This
-value must use the format ``['header-name' => 'value0, value1, ...']``.
+    .. code-block:: php
 
-.. _reference-http-client-retry-http-codes:
+        // config/packages/framework.php
+        use Symfony\Config\FrameworkConfig;
+        return static function (FrameworkConfig $framework): void {
+            $framework->csrfProtection()
+                ->enabled(true)
+            ;
+        };
 
-http_codes
-..........
+If you're using forms, but want to avoid starting your session (e.g. using
+forms in an API-only website), ``csrf_protection`` will need to be set to
+``false``.
 
-**type**: ``array`` **default**: :method:`Symfony\\Component\\HttpClient\\Retry\\GenericRetryStrategy::DEFAULT_RETRY_STATUS_CODES`
+stateless_token_ids
+...................
 
-The list of HTTP status codes that triggers a retry of the request.
+**type**: ``array`` **default**: ``[]``
 
-http_version
-............
+The list of CSRF token ids that will use :ref:`stateless CSRF protection <csrf-stateless-tokens>`.
 
-**type**: ``string`` | ``null`` **default**: ``null``
+.. versionadded:: 7.2
 
-The HTTP version to use, typically ``'1.1'``  or ``'2.0'``. Leave it to ``null``
-to let Symfony select the best version automatically.
+    The ``stateless_token_ids`` option was introduced in Symfony 7.2.
 
-.. _reference-http-client-retry-jitter:
+check_header
+............
 
-jitter
-......
+**type**: ``integer`` or ``bool`` **default**: ``false``
 
-**type**: ``float`` **default**: ``0.1`` (must be between 0.0 and 1.0)
+Whether to check the CSRF token in an HTTP header in addition to the cookie when
+using :ref:`stateless CSRF protection <csrf-stateless-tokens>`. You can also set
+this to ``2`` (the value of the ``CHECK_ONLY_HEADER`` constant on the
+:class:`Symfony\\Component\\Security\\Csrf\\SameOriginCsrfTokenManager` class)
+to check only the header and ignore the cookie.
 
-This option adds some randomness to the delay. It's useful to avoid sending
-multiple requests to the server at the exact same time. The randomness is
-calculated as ``delay * jitter``. For example: if delay is ``1000ms`` and jitter
-is ``0.2``, the actual delay will be a number between ``800`` and ``1200`` (1000 +/- 20%).
+.. versionadded:: 7.2
 
-local_cert
-..........
+    The ``check_header`` option was introduced in Symfony 7.2.
 
-**type**: ``string``
+cookie_name
+...........
 
-The path to a file that contains the `PEM formatted`_ certificate used by the
-HTTP client. This is often combined with the ``local_pk`` and ``passphrase``
-options.
+**type**: ``string`` **default**: ``csrf-token``
 
-local_pk
-........
+The name of the cookie (and HTTP header) to use for the double-submit when using
+:ref:`stateless CSRF protection <csrf-stateless-tokens>`.
 
-**type**: ``string``
+.. versionadded:: 7.2
 
-The path of a file that contains the `PEM formatted`_ private key of the
-certificate defined in the ``local_cert`` option.
+    The ``cookie_name`` option was introduced in Symfony 7.2.
 
-.. _reference-http-client-retry-max-delay:
+.. _config-framework-default_locale:
 
-max_delay
-.........
+default_locale
+~~~~~~~~~~~~~~
 
-**type**: ``integer`` **default**: ``0``
+**type**: ``string`` **default**: ``en``
 
-The maximum amount of milliseconds initial to wait between retries.
-Use ``0`` to not limit the duration.
+The default locale is used if no ``_locale`` routing parameter has been
+set. It is available with the
+:method:`Request::getDefaultLocale <Symfony\\Component\\HttpFoundation\\Request::getDefaultLocale>`
+method.
 
-max_duration
-............
+.. seealso::
 
-**type**: ``float`` **default**: ``0``
+    You can read more information about the default locale in
+    :ref:`translation-default-locale`.
 
-The maximum execution time, in seconds, that the request and the response are
-allowed to take. A value lower than or equal to 0 means it is unlimited.
+.. _reference-translator-enabled-locales:
+.. _reference-enabled-locales:
 
-max_host_connections
-....................
+enabled_locales
+...............
 
-**type**: ``integer`` **default**: ``6``
+**type**: ``array`` **default**: ``[]`` (empty array = enable all locales)
 
-Defines the maximum amount of simultaneously open connections to a single host
-(considering a "host" the same as a "host name + port number" pair). This limit
-also applies for proxy connections, where the proxy is considered to be the host
-for which this limit is applied.
+Symfony applications generate by default the translation files for validation
+and security messages in all locales. If your application only uses some
+locales, use this option to restrict the files generated by Symfony and improve
+performance a bit:
 
-max_redirects
-.............
+.. configuration-block::
 
-**type**: ``integer`` **default**: ``20``
+    .. code-block:: yaml
 
-The maximum number of redirects to follow. Use ``0`` to not follow any
-redirection.
+        # config/packages/translation.yaml
+        framework:
+            enabled_locales: ['en', 'es']
 
-.. _reference-http-client-retry-max-retries:
+    .. code-block:: xml
 
-max_retries
-...........
+        <!-- config/packages/translation.xml -->
+        <?xml version="1.0" encoding="UTF-8" ?>
+        <container xmlns="http://symfony.com/schema/dic/services"
+            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+            xmlns:framework="http://symfony.com/schema/dic/symfony"
+            xsi:schemaLocation="http://symfony.com/schema/dic/services
+                https://symfony.com/schema/dic/services/services-1.0.xsd
+                http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
 
-**type**: ``integer`` **default**: ``3``
+            <framework:config>
+                <enabled-locale>en</enabled-locale>
+                <enabled-locale>es</enabled-locale>
+            </framework:config>
+        </container>
 
-The maximum number of retries for failing requests. When the maximum is reached,
-the client returns the last received response.
+    .. code-block:: php
 
-.. _reference-http-client-retry-multiplier:
+        // config/packages/translation.php
+        use Symfony\Config\FrameworkConfig;
 
-multiplier
-..........
+        return static function (FrameworkConfig $framework): void {
+            $framework->enabledLocales(['en', 'es']);
+        };
 
-**type**: ``float`` **default**: ``2``
+An added bonus of defining the enabled locales is that they are automatically
+added as a requirement of the :ref:`special _locale parameter <routing-locale-parameter>`.
+For example, if you define this value as ``['ar', 'he', 'ja', 'zh']``, the
+``_locale`` routing parameter will have an ``ar|he|ja|zh`` requirement. If some
+user makes requests with a locale not included in this option, they'll see a 404 error.
 
-This value is multiplied to the delay each time a retry occurs, to distribute
-retries in time instead of making all of them sequentially.
+set_content_language_from_locale
+................................
 
-no_proxy
-........
+**type**: ``boolean`` **default**: ``false``
 
-**type**: ``string`` | ``null`` **default**: ``null``
+If this option is set to ``true``, the response will have a ``Content-Language``
+HTTP header set with the ``Request`` locale.
 
-A comma separated list of hosts that do not require a proxy to be reached, even
-if one is configured. Use the ``'*'`` wildcard to match all hosts and an empty
-string to match none (disables the proxy).
+set_locale_from_accept_language
+...............................
 
-passphrase
-..........
+**type**: ``boolean`` **default**: ``false``
 
-**type**: ``string``
+If this option is set to ``true``, the ``Request`` locale will automatically be
+set to the value of the ``Accept-Language`` HTTP header.
 
-The passphrase used to encrypt the certificate stored in the file defined in the
-``local_cert`` option.
+When the ``_locale`` request attribute is passed, the ``Accept-Language`` header
+is ignored.
 
-peer_fingerprint
-................
+disallow_search_engine_index
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-**type**: ``array``
+**type**: ``boolean`` **default**: ``true`` when the debug mode is enabled, ``false`` otherwise.
+
+If ``true``, Symfony adds a ``X-Robots-Tag: noindex`` HTTP tag to all responses
+(unless your own app adds that header, in which case it's not modified). This
+`X-Robots-Tag HTTP header`_ tells search engines to not index your web site.
+This option is a protection measure in case you accidentally publish your site
+in debug mode.
+
+.. _config-framework-error_controller:
+
+error_controller
+~~~~~~~~~~~~~~~~
+
+**type**: ``string`` **default**: ``error_controller``
+
+This is the controller that is called when an exception is thrown anywhere in
+your application. The default controller
+(:class:`Symfony\\Component\\HttpKernel\\Controller\\ErrorController`)
+renders specific templates under different error conditions (see
+:doc:`/controller/error_pages`).
+
+esi
+~~~
+
+.. seealso::
+
+    You can read more about Edge Side Includes (ESI) in :ref:`edge-side-includes`.
+
+.. _reference-esi-enabled:
+
+enabled
+.......
+
+**type**: ``boolean`` **default**: ``false``
+
+Whether to enable the edge side includes support in the framework.
+
+You can also set ``esi`` to ``true`` to enable it:
+
+.. configuration-block::
+
+    .. code-block:: yaml
+
+        # config/packages/framework.yaml
+        framework:
+            esi: true
+
+    .. code-block:: xml
+
+        <!-- config/packages/framework.xml -->
+        <?xml version="1.0" encoding="UTF-8" ?>
+        <container xmlns="http://symfony.com/schema/dic/services"
+            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+            xmlns:framework="http://symfony.com/schema/dic/symfony"
+            xsi:schemaLocation="http://symfony.com/schema/dic/services
+                https://symfony.com/schema/dic/services/services-1.0.xsd
+                http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
+
+            <framework:config>
+                <framework:esi/>
+            </framework:config>
+        </container>
+
+    .. code-block:: php
+
+        // config/packages/framework.php
+        use Symfony\Config\FrameworkConfig;
+
+        return static function (FrameworkConfig $framework): void {
+            $framework->esi()->enabled(true);
+        };
+
+.. _framework_exceptions:
+
+exceptions
+~~~~~~~~~~
+
+**type**: ``array``
+
+Defines the :ref:`log level </logging>`, :ref:`log channel </logging/channels_handlers>`
+and HTTP status code applied to the exceptions that match the given exception class:
+
+.. configuration-block::
+
+    .. code-block:: yaml
+
+        # config/packages/exceptions.yaml
+        framework:
+            exceptions:
+                Symfony\Component\HttpKernel\Exception\BadRequestHttpException:
+                    log_level: 'debug'
+                    status_code: 422
+                    log_channel: 'custom_channel'
+
+    .. code-block:: xml
+
+        <!-- config/packages/exceptions.xml -->
+        <?xml version="1.0" encoding="UTF-8" ?>
+        <container xmlns="http://symfony.com/schema/dic/services"
+            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+            xmlns:framework="http://symfony.com/schema/dic/symfony"
+            xsi:schemaLocation="http://symfony.com/schema/dic/services
+                https://symfony.com/schema/dic/services/services-1.0.xsd
+                http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
+
+            <framework:config>
+                <framework:exception
+                    class="Symfony\Component\HttpKernel\Exception\BadRequestHttpException"
+                    log-level="debug"
+                    status-code="422"
+                    log-channel="custom_channel"
+                />
+                <!-- ... -->
+            </framework:config>
+        </container>
+
+    .. code-block:: php
+
+        // config/packages/exceptions.php
+        use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
+        use Symfony\Config\FrameworkConfig;
+
+        return static function (FrameworkConfig $framework): void {
+            $framework->exception(BadRequestHttpException::class)
+                ->logLevel('debug')
+                ->statusCode(422)
+                ->logChannel('custom_channel')
+            ;
+        };
+
+.. versionadded:: 7.3
+
+    The ``log_channel`` option was introduced in Symfony 7.3.
+
+The order in which you configure exceptions is important because Symfony will
+use the configuration of the first exception that matches ``instanceof``:
+
+.. code-block:: yaml
+
+        # config/packages/exceptions.yaml
+        framework:
+            exceptions:
+                Exception:
+                    log_level: 'debug'
+                    status_code: 404
+                # The following configuration will never be used because \RuntimeException extends \Exception
+                RuntimeException:
+                    log_level: 'debug'
+                    status_code: 422
+
+You can map a status code and a set of headers to an exception thanks
+to the ``#[WithHttpStatus]`` attribute on the exception class::
+
+    namespace App\Exception;
+
+    use Symfony\Component\HttpKernel\Attribute\WithHttpStatus;
+
+    #[WithHttpStatus(422, [
+       'Retry-After' => 10,
+       'X-Custom-Header' => 'header-value',
+    ])]
+    class CustomException extends \Exception
+    {
+    }
+
+It is also possible to map a log level on a custom exception class using
+the ``#[WithLogLevel]`` attribute::
+
+    namespace App\Exception;
+
+    use Psr\Log\LogLevel;
+    use Symfony\Component\HttpKernel\Attribute\WithLogLevel;
+
+    #[WithLogLevel(LogLevel::WARNING)]
+    class CustomException extends \Exception
+    {
+    }
+
+The attributes can also be added to interfaces directly::
+
+    namespace App\Exception;
+
+    use Symfony\Component\HttpKernel\Attribute\WithHttpStatus;
+
+    #[WithHttpStatus(422)]
+    interface CustomExceptionInterface
+    {
+    }
+
+    class CustomException extends \Exception implements CustomExceptionInterface
+    {
+    }
+
+.. versionadded:: 7.1
+
+    Support to use ``#[WithHttpStatus]`` and ``#[WithLogLevel]`` attributes
+    on interfaces was introduced in Symfony 7.1.
+
+.. _reference-framework-form:
+
+form
+~~~~
+
+.. _reference-form-enabled:
+
+enabled
+.......
+
+**type**: ``boolean`` **default**: ``true`` or ``false`` depending on your installation
+
+Whether to enable the form services or not in the service container. If
+you don't use forms, setting this to ``false`` may increase your application's
+performance because less services will be loaded into the container.
+
+This option will automatically be set to ``true`` when one of the child
+settings is configured.
+
+.. note::
+
+    This will automatically enable the `validation`_.
+
+.. seealso::
+
+    For more details, see :doc:`/forms`.
+
+csrf_protection
+...............
+
+field_name
+''''''''''
+
+**type**: ``string`` **default**: ``_token``
+
+This is the field name that you should give to the CSRF token field of your forms.
+
+field_attr
+''''''''''
+
+**type**: ``array`` **default**: ``['data-controller' => 'csrf-protection']``
+
+HTML attributes to add to the CSRF token field of your forms.
+
+token_id
+''''''''
+
+**type**: ``string`` **default**: ``null``
+
+The CSRF token ID used to validate the CSRF tokens of your forms. This setting
+applies only to form types that use :ref:`service autoconfiguration <services-autoconfigure>`,
+which typically means your own form types, not those registered by third-party bundles.
+
+fragments
+~~~~~~~~~
+
+.. seealso::
+
+    Learn more about fragments in the
+    :ref:`HTTP Cache article <http_cache-fragments>`.
+
+.. _reference-fragments-enabled:
+
+enabled
+.......
+
+**type**: ``boolean`` **default**: ``false``
+
+Whether to enable the fragment listener or not. The fragment listener is
+used to render ESI fragments independently of the rest of the page.
+
+This setting is automatically set to ``true`` when one of the child settings
+is configured.
+
+hinclude_default_template
+.........................
+
+**type**: ``string`` **default**: ``null``
+
+Sets the content shown during the loading of the fragment or when JavaScript
+is disabled. This can be either a template name or the content itself.
+
+.. seealso::
+
+    See :ref:`templates-hinclude` for more information about hinclude.
+
+.. _reference-fragments-path:
+
+path
+....
+
+**type**: ``string`` **default**: ``/_fragment``
+
+The path prefix for fragments. The fragment listener will only be executed
+when the request starts with this path.
+
+handle_all_throwables
+~~~~~~~~~~~~~~~~~~~~~
+
+**type**: ``boolean`` **default**: ``true``
+
+When set to ``true``, the Symfony kernel will catch all ``\Throwable`` exceptions
+thrown by the application and will turn them into HTTP responses.
+
+html_sanitizer
+~~~~~~~~~~~~~~
+
+The ``html_sanitizer`` option (and its children) are used to configure
+custom HTML sanitizers. Read more about the options in the
+:ref:`HTML sanitizer documentation <html-sanitizer-configuration>`.
+
+.. _configuration-framework-http_cache:
+
+http_cache
+~~~~~~~~~~
+
+allow_reload
+............
+
+**type**: ``boolean`` **default**: ``false``
+
+Specifies whether the client can force a cache reload by including a
+Cache-Control "no-cache" directive in the request. Set it to ``true``
+for compliance with RFC 2616.
+
+allow_revalidate
+................
+
+**type**: ``boolean`` **default**: ``false``
+
+Specifies whether the client can force a cache revalidate by including a
+Cache-Control "max-age=0" directive in the request. Set it to ``true``
+for compliance with RFC 2616.
+
+debug
+.....
+
+**type**: ``boolean`` **default**: ``%kernel.debug%``
+
+If true, exceptions are thrown when things go wrong. Otherwise, the cache will
+try to carry on and deliver a meaningful response.
+
+default_ttl
+...........
+
+**type**: ``integer`` **default**: ``0``
+
+The number of seconds that a cache entry should be considered fresh when no
+explicit freshness information is provided in a response. Explicit
+Cache-Control or Expires headers override this value.
+
+enabled
+.......
+
+**type**: ``boolean`` **default**: ``false``
+
+private_headers
+...............
+
+**type**: ``array`` **default**: ``['Authorization', 'Cookie']``
+
+Set of request headers that trigger "private" cache-control behavior on responses
+that don't explicitly state whether the response is public or private via a
+Cache-Control directive.
+
+skip_response_headers
+.....................
+
+**type**: ``array`` **default**: ``Set-Cookie``
+
+Set of response headers that will never be cached even when the response is cacheable
+and public.
+
+stale_if_error
+..............
+
+**type**: ``integer`` **default**: ``60``
+
+Specifies the default number of seconds (the granularity is the second) during
+which the cache can serve a stale response when an error is encountered.
+This setting is overridden by the stale-if-error HTTP
+Cache-Control extension (see RFC 5861).
+
+stale_while_revalidate
+......................
+
+**type**: ``integer`` **default**: ``2``
+
+Specifies the default number of seconds (the granularity is the second as the
+Response TTL precision is a second) during which the cache can immediately return
+a stale response while it revalidates it in the background.
+This setting is overridden by the stale-while-revalidate HTTP Cache-Control
+extension (see RFC 5861).
+
+trace_header
+............
+
+**type**: ``string`` **default**: ``'X-Symfony-Cache'``
+
+Header name to use for traces.
+
+trace_level
+...........
+
+**type**: ``string`` **possible values**: ``'none'``, ``'short'`` or ``'full'``
+
+For 'short', a concise trace of the main request will be added as an HTTP header.
+'full' will add traces for all requests (including ESI subrequests).
+(default: ``'full'`` if in debug; ``'none'`` otherwise)
+
+.. _reference-http-client:
+
+http_client
+~~~~~~~~~~~
+
+When the HttpClient component is installed, an HTTP client is available
+as a service named ``http_client`` or using the autowiring alias
+:class:`Symfony\\Contracts\\HttpClient\\HttpClientInterface`.
+
+.. _reference-http-client-default-options:
+
+This service can be configured using ``framework.http_client.default_options``:
+
+.. configuration-block::
+
+    .. code-block:: yaml
+
+        # config/packages/framework.yaml
+        framework:
+            # ...
+            http_client:
+                max_host_connections: 10
+                default_options:
+                    headers: { 'X-Powered-By': 'ACME App' }
+                    max_redirects: 7
+
+    .. code-block:: xml
+
+        <!-- config/packages/framework.xml -->
+        <?xml version="1.0" encoding="UTF-8" ?>
+        <container xmlns="http://symfony.com/schema/dic/services"
+            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+            xmlns:framework="http://symfony.com/schema/dic/symfony"
+            xsi:schemaLocation="http://symfony.com/schema/dic/services
+                https://symfony.com/schema/dic/services/services-1.0.xsd
+                http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
+
+            <framework:config>
+                <framework:http-client max-host-connections="10">
+                    <framework:default-options max-redirects="7">
+                        <framework:header name="X-Powered-By">ACME App</framework:header>
+                    </framework:default-options>
+                </framework:http-client>
+            </framework:config>
+        </container>
+
+    .. code-block:: php
+
+        // config/packages/framework.php
+        $container->loadFromExtension('framework', [
+            'http_client' => [
+                'max_host_connections' => 10,
+                'default_options' => [
+                    'headers' => [
+                        'X-Powered-By' => 'ACME App',
+                    ],
+                    'max_redirects' => 7,
+                ],
+            ],
+        ]);
+
+    .. code-block:: php-standalone
+
+        $client = HttpClient::create([
+            'headers' => [
+                'X-Powered-By' => 'ACME App',
+            ],
+            'max_redirects' => 7,
+        ], 10);
+
+.. _reference-http-client-scoped-clients:
+
+Multiple pre-configured HTTP client services can be defined, each with its
+service name defined as a key under ``scoped_clients``. Scoped clients inherit
+the default options defined for the ``http_client`` service. You can override
+these options and can define a few others:
+
+.. configuration-block::
+
+    .. code-block:: yaml
+
+        # config/packages/framework.yaml
+        framework:
+            # ...
+            http_client:
+                scoped_clients:
+                    my_api.client:
+                        auth_bearer: secret_bearer_token
+                        # ...
+
+    .. code-block:: xml
+
+        <!-- config/packages/framework.xml -->
+        <?xml version="1.0" encoding="UTF-8" ?>
+        <container xmlns="http://symfony.com/schema/dic/services"
+            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+            xmlns:framework="http://symfony.com/schema/dic/symfony"
+            xsi:schemaLocation="http://symfony.com/schema/dic/services
+                https://symfony.com/schema/dic/services/services-1.0.xsd
+                http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
+
+            <framework:config>
+                <framework:http-client>
+                    <framework:scoped-client name="my_api.client" auth-bearer="secret_bearer_token"/>
+                </framework:http-client>
+            </framework:config>
+        </container>
+
+    .. code-block:: php
+
+        // config/packages/framework.php
+        $container->loadFromExtension('framework', [
+            'http_client' => [
+                'scoped_clients' => [
+                    'my_api.client' => [
+                        'auth_bearer' => 'secret_bearer_token',
+                        // ...
+                    ],
+                ],
+            ],
+        ]);
+
+    .. code-block:: php-standalone
+
+        $client = HttpClient::createForBaseUri('https://...', [
+            'auth_bearer' => 'secret_bearer_token',
+            // ...
+        ]);
+
+Options defined for scoped clients apply only to URLs that match either their
+`base_uri`_ or the `scope`_ option when it is defined. Non-matching URLs always
+use default options.
+
+Each scoped client also defines a corresponding named autowiring alias.
+If you use for example
+``Symfony\Contracts\HttpClient\HttpClientInterface $myApiClient``
+as the type and name of an argument, autowiring will inject the ``my_api.client``
+service into your autowired classes.
+
+auth_basic
+..........
+
+**type**: ``string``
+
+The username and password used to create the ``Authorization`` HTTP header
+used in HTTP Basic authentication. The value of this option must follow the
+format ``username:password``.
+
+auth_bearer
+...........
+
+**type**: ``string``
+
+The token used to create the ``Authorization`` HTTP header used in HTTP Bearer
+authentication (also called token authentication).
+
+auth_ntlm
+.........
+
+**type**: ``string``
+
+The username and password used to create the ``Authorization`` HTTP header used
+in the `Microsoft NTLM authentication protocol`_. The value of this option must
+follow the format ``username:password``. This authentication mechanism requires
+using the cURL-based transport.
+
+.. _reference-http-client-base-uri:
+
+base_uri
+........
+
+**type**: ``string``
+
+URI that is merged into relative URIs, following the rules explained in the
+`RFC 3986`_ standard. This is useful when all the requests you make share a
+common prefix (e.g. ``https://api.github.com/``) so you can avoid adding it to
+every request.
+
+Here are some common examples of how ``base_uri`` merging works in practice:
+
+==========================  ==================  =============================
+``base_uri``                Relative URI        Actual Requested URI
+==========================  ==================  =============================
+http://example.org          /bar                http://example.org/bar
+http://example.org/foo      /bar                http://example.org/bar
+http://example.org/foo      bar                 http://example.org/bar
+http://example.org/foo/     /bar                http://example.org/bar
+http://example.org/foo/     bar                 http://example.org/foo/bar
+http://example.org          http://symfony.com  http://symfony.com
+http://example.org/?bar     bar                 http://example.org/bar
+http://example.org/api/v4   /bar                http://example.org/bar
+http://example.org/api/v4/  /bar                http://example.org/bar
+http://example.org/api/v4   bar                 http://example.org/api/bar
+http://example.org/api/v4/  bar                 http://example.org/api/v4/bar
+==========================  ==================  =============================
+
+bindto
+......
+
+**type**: ``string``
+
+A network interface name, IP address, a host name or a UNIX socket to use as the
+outgoing network interface.
+
+buffer
+......
+
+**type**: ``boolean`` | ``Closure``
+
+Buffering the response means that you can access its content multiple times
+without performing the request again. Buffering is enabled by default when the
+content type of the response is ``text/*``, ``application/json`` or ``application/xml``.
+
+If this option is a boolean value, the response is buffered when the value is
+``true``. If this option is a closure, the response is buffered when the
+returned value is ``true`` (the closure receives as argument an array with the
+response headers).
+
+cafile
+......
+
+**type**: ``string``
+
+The path of the certificate authority file that contains one or more
+certificates used to verify the other servers' certificates.
+
+capath
+......
+
+**type**: ``string``
+
+The path to a directory that contains one or more certificate authority files.
+
+ciphers
+.......
+
+**type**: ``string``
+
+A list of the names of the ciphers allowed for the TLS connections. They
+can be separated by colons, commas or spaces (e.g. ``'RC4-SHA:TLS13-AES-128-GCM-SHA256'``).
+
+crypto_method
+.............
+
+**type**: ``integer``
+
+The minimum version of TLS to accept. The value must be one of the
+``STREAM_CRYPTO_METHOD_TLSv*_CLIENT`` constants defined by PHP.
+
+.. _reference-http-client-retry-delay:
+
+delay
+.....
+
+**type**: ``integer`` **default**: ``1000``
+
+The initial delay in milliseconds used to compute the waiting time between retries.
+
+.. _reference-http-client-retry-enabled:
+
+enabled
+.......
+
+**type**: ``boolean`` **default**: ``false``
+
+Whether to enable the support for retry failed HTTP request or not.
+This setting is automatically set to true when one of the child settings is configured.
+
+extra
+.....
+
+**type**: ``array``
+
+Arbitrary additional data to pass to the HTTP client for further use.
+This can be particularly useful when :ref:`decorating an existing client <extensibility>`.
+
+.. _http-headers:
+
+headers
+.......
+
+**type**: ``array``
+
+An associative array of the HTTP headers added before making the request. This
+value must use the format ``['header-name' => 'value0, value1, ...']``.
+
+.. _reference-http-client-retry-http-codes:
+
+http_codes
+..........
+
+**type**: ``array`` **default**: :method:`Symfony\\Component\\HttpClient\\Retry\\GenericRetryStrategy::DEFAULT_RETRY_STATUS_CODES`
+
+The list of HTTP status codes that triggers a retry of the request.
+
+http_version
+............
+
+**type**: ``string`` | ``null`` **default**: ``null``
+
+The HTTP version to use, typically ``'1.1'``  or ``'2.0'``. Leave it to ``null``
+to let Symfony select the best version automatically.
+
+.. _reference-http-client-retry-jitter:
+
+jitter
+......
+
+**type**: ``float`` **default**: ``0.1`` (must be between 0.0 and 1.0)
+
+This option adds some randomness to the delay. It's useful to avoid sending
+multiple requests to the server at the exact same time. The randomness is
+calculated as ``delay * jitter``. For example: if delay is ``1000ms`` and jitter
+is ``0.2``, the actual delay will be a number between ``800`` and ``1200`` (1000 +/- 20%).
+
+local_cert
+..........
+
+**type**: ``string``
+
+The path to a file that contains the `PEM formatted`_ certificate used by the
+HTTP client. This is often combined with the ``local_pk`` and ``passphrase``
+options.
+
+local_pk
+........
+
+**type**: ``string``
+
+The path of a file that contains the `PEM formatted`_ private key of the
+certificate defined in the ``local_cert`` option.
+
+.. _reference-http-client-retry-max-delay:
+
+max_delay
+.........
+
+**type**: ``integer`` **default**: ``0``
+
+The maximum amount of milliseconds initial to wait between retries.
+Use ``0`` to not limit the duration.
+
+max_duration
+............
+
+**type**: ``float`` **default**: ``0``
+
+The maximum execution time, in seconds, that the request and the response are
+allowed to take. A value lower than or equal to 0 means it is unlimited.
+
+.. _reference-http-client-max-host-connections:
+
+max_host_connections
+....................
+
+**type**: ``integer`` **default**: ``6``
+
+Defines the maximum amount of simultaneously open connections to a single host
+(considering a "host" the same as a "host name + port number" pair). This limit
+also applies for proxy connections, where the proxy is considered to be the host
+for which this limit is applied.
+
+max_redirects
+.............
+
+**type**: ``integer`` **default**: ``20``
+
+The maximum number of redirects to follow. Use ``0`` to not follow any
+redirection.
+
+.. _reference-http-client-retry-max-retries:
+
+max_retries
+...........
+
+**type**: ``integer`` **default**: ``3``
+
+The maximum number of retries for failing requests. When the maximum is reached,
+the client returns the last received response.
+
+.. _reference-http-client-retry-multiplier:
+
+multiplier
+..........
+
+**type**: ``float`` **default**: ``2``
+
+This value is multiplied to the delay each time a retry occurs, to distribute
+retries in time instead of making all of them sequentially.
+
+no_proxy
+........
+
+**type**: ``string`` | ``null`` **default**: ``null``
+
+A comma separated list of hosts that do not require a proxy to be reached, even
+if one is configured. Use the ``'*'`` wildcard to match all hosts and an empty
+string to match none (disables the proxy).
+
+passphrase
+..........
+
+**type**: ``string``
+
+The passphrase used to encrypt the certificate stored in the file defined in the
+``local_cert`` option.
+
+peer_fingerprint
+................
+
+**type**: ``array``
 
 When negotiating a TLS connection, the server sends a certificate
 indicating its identity. A public key is extracted from this certificate and if
@@ -1251,7 +1930,7 @@ timeout
 
 **type**: ``float`` **default**: depends on your PHP config
 
-Time, in seconds, to wait for a response. If the response takes longer, a
+Time, in seconds, to wait for network activity. If the connection is idle for longer, a
 :class:`Symfony\\Component\\HttpClient\\Exception\\TransportException` is thrown.
 Its default value is the same as the value of PHP's `default_socket_timeout`_
 config option.
@@ -1275,147 +1954,64 @@ connection is verified for authenticity. Authenticating the certificate is not
 enough to be sure about the server, so you should combine this with the
 ``verify_host`` option.
 
-html_sanitizer
-~~~~~~~~~~~~~~
-
-The ``html_sanitizer`` option (and its children) are used to configure
-custom HTML sanitizers. Read more about the options in the
-:ref:`HTML sanitizer documentation <html-sanitizer-configuration>`.
-
-profiler
-~~~~~~~~
-
-.. _reference-profiler-enabled:
-
-enabled
-.......
-
-**type**: ``boolean`` **default**: ``false``
-
-The profiler can be enabled by setting this option to ``true``. When you
-install it using Symfony Flex, the profiler is enabled in the ``dev``
-and ``test`` environments.
-
-.. note::
-
-    The profiler works independently from the Web Developer Toolbar, see
-    the :doc:`WebProfilerBundle configuration </reference/configuration/web_profiler>`
-    on how to disable/enable the toolbar.
-
-collect
-.......
-
-**type**: ``boolean`` **default**: ``true``
-
-This option configures the way the profiler behaves when it is enabled. If set
-to ``true``, the profiler collects data for all requests. If you want to only
-collect information on-demand, you can set the ``collect`` flag to ``false`` and
-activate the data collectors manually::
-
-    $profiler->enable();
-
-collect_parameter
-.................
-
-**type**: ``string`` **default**: ``null``
-
-This specifies name of a query parameter, a body parameter or a request attribute
-used to enable or disable collection of data by the profiler for each request.
-Combine it with the ``collect`` option to enable/disable the profiler on demand:
-
-* If the ``collect`` option is set to ``true`` but this parameter exists in a
-  request and has any value other than ``true``, ``yes``, ``on`` or ``1``, the
-  request data will not be collected;
-* If the ``collect`` option is set to ``false``, but this parameter exists in a
-  request and has value of ``true``, ``yes``, ``on`` or ``1``, the request data
-  will be collected.
-
-only_exceptions
-...............
-
-**type**: ``boolean`` **default**: ``false``
-
-When this is set to ``true``, the profiler will only be enabled when an
-exception is thrown during the handling of the request.
-
-.. _only_master_requests:
-
-only_main_requests
-..................
-
-**type**: ``boolean`` **default**: ``false``
-
-When this is set to ``true``, the profiler will only be enabled on the main
-requests (and not on the subrequests).
-
-.. _profiler-dsn:
-
-dsn
-...
-
-**type**: ``string`` **default**: ``file:%kernel.cache_dir%/profiler``
-
-The DSN where to store the profiling information.
-
-.. _collect_serializer_data:
+ .. _configuration-framework-http_method_override:
 
-collect_serializer_data
-.......................
+http_method_override
+~~~~~~~~~~~~~~~~~~~~
 
 **type**: ``boolean`` **default**: ``false``
 
-Set this option to ``true`` to enable the serializer data collector and its
-profiler panel. When this option is ``true``, all normalizers and encoders are
-decorated by traceable implementations that collect profiling information about them.
-
-rate_limiter
-~~~~~~~~~~~~
-
-.. _reference-rate-limiter-name:
+This determines whether the ``_method`` request parameter is used as the
+intended HTTP method on POST requests. If enabled, the
+:method:`Request::enableHttpMethodParameterOverride <Symfony\\Component\\HttpFoundation\\Request::enableHttpMethodParameterOverride>`
+method gets called automatically. It becomes the service container parameter
+named ``kernel.http_method_override``.
 
-name
-....
+.. seealso::
 
-**type**: ``prototype``
+    :ref:`Changing the Action and HTTP Method <forms-change-action-method>` of
+    Symfony forms.
 
-Name of the rate limiter you want to create.
+.. warning::
 
-lock_factory
-""""""""""""
+    If you're using the :ref:`HttpCache Reverse Proxy <symfony2-reverse-proxy>`
+    with this option, the kernel will ignore the ``_method`` parameter,
+    which could lead to errors.
 
-**type**: ``string`` **default:** ``lock.factory``
+    To fix this, invoke the ``enableHttpMethodParameterOverride()`` method
+    before creating the ``Request`` object::
 
-The service that is used to create a lock. The service has to be an instance of
-the :class:`Symfony\\Component\\Lock\\LockFactory` class.
+        // public/index.php
 
-policy
-""""""
+        // ...
+        $kernel = new CacheKernel($kernel);
 
-**type**: ``string`` **required**
+        Request::enableHttpMethodParameterOverride(); // <-- add this line
+        $request = Request::createFromGlobals();
+        // ...
 
-The name of the rate limiting algorithm to use. Example names are ``fixed_window``,
-``sliding_window`` and ``no_limit``. See :ref:`Rate Limiter Policies <rate-limiter-policies>`)
-for more information.
+.. _reference-framework-ide:
 
-request
-~~~~~~~
+ide
+~~~
 
-formats
-.......
+**type**: ``string`` **default**: ``%env(default::SYMFONY_IDE)%``
 
-**type**: ``array`` **default**: ``[]``
+Symfony turns file paths seen in variable dumps and exception messages into
+links that open those files right inside your browser. If you prefer to open
+those files in your favorite IDE or text editor, set this option to any of the
+following values: ``phpstorm``, ``sublime``, ``textmate``, ``macvim``, ``emacs``,
+``atom`` and ``vscode``.
 
-This setting is used to associate additional request formats (e.g. ``html``)
-to one or more mime types (e.g. ``text/html``), which will allow you to use the
-format & mime types to call
-:method:`Request::getFormat($mimeType) <Symfony\\Component\\HttpFoundation\\Request::getFormat>` or
-:method:`Request::getMimeType($format) <Symfony\\Component\\HttpFoundation\\Request::getMimeType>`.
+.. note::
 
-In practice, this is important because Symfony uses it to automatically set the
-``Content-Type`` header on the ``Response`` (if you don't explicitly set one).
-If you pass an array of mime types, the first will be used for the header.
+    The ``phpstorm`` option is supported natively by PhpStorm on macOS and
+    Windows; Linux requires installing `phpstorm-url-handler`_.
 
-To configure a ``jsonp`` format:
+If you use another editor, the expected configuration value is a URL template
+that contains an ``%f`` placeholder where the file path is expected and ``%l``
+placeholder for the line number (percentage signs (``%``) must be escaped by
+doubling them to prevent Symfony from interpreting them as container parameters).
 
 .. configuration-block::
 
@@ -1423,30 +2019,20 @@ To configure a ``jsonp`` format:
 
         # config/packages/framework.yaml
         framework:
-            request:
-                formats:
-                    jsonp: 'application/javascript'
+            ide: 'myide://open?url=file://%%f&line=%%l'
 
     .. code-block:: xml
 
         <!-- config/packages/framework.xml -->
         <?xml version="1.0" encoding="UTF-8" ?>
-
         <container xmlns="http://symfony.com/schema/dic/services"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns:framework="http://symfony.com/schema/dic/symfony"
             xsi:schemaLocation="http://symfony.com/schema/dic/services
                 https://symfony.com/schema/dic/services/services-1.0.xsd
-                http://symfony.com/schema/dic/symfony
-                https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
+                http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
 
-            <framework:config>
-                <framework:request>
-                    <framework:format name="jsonp">
-                        <framework:mime-type>application/javascript</framework:mime-type>
-                    </framework:format>
-                </framework:request>
-            </framework:config>
+            <framework:config ide="myide://open?url=file://%%f&line=%%l"/>
         </container>
 
     .. code-block:: php
@@ -1455,193 +2041,98 @@ To configure a ``jsonp`` format:
         use Symfony\Config\FrameworkConfig;
 
         return static function (FrameworkConfig $framework): void {
-            $framework->request()
-                ->format('jsonp', 'application/javascript');
+            $framework->ide('myide://open?url=file://%%f&line=%%l');
         };
 
-router
-~~~~~~
-
-resource
-........
-
-**type**: ``string`` **required**
-
-The path the main routing resource (e.g. a YAML file) that contains the
-routes and imports the router should load.
-
-.. _reference-router-type:
-
-type
-....
-
-**type**: ``string``
-
-The type of the resource to hint the loaders about the format. This isn't
-needed when you use the default routers with the expected file extensions
-(``.xml``, ``.yaml``, ``.php``).
-
-default_uri
-...........
-
-**type**: ``string``
-
-The default URI used to generate URLs in a non-HTTP context (see
-:ref:`Generating URLs in Commands <router-generate-urls-commands>`).
-
-http_port
-.........
-
-**type**: ``integer`` **default**: ``80``
-
-The port for normal http requests (this is used when matching the scheme).
+Since every developer uses a different IDE, the recommended way to enable this
+feature is to configure it on a system level. First, you can define this option
+in the ``SYMFONY_IDE`` environment variable, which Symfony reads automatically
+when ``framework.ide`` config is not set.
 
-https_port
-..........
+Another alternative is to set the ``xdebug.file_link_format`` option in your
+``php.ini`` configuration file. The format to use is the same as for the
+``framework.ide`` option, but without the need to escape the percent signs
+(``%``) by doubling them:
 
-**type**: ``integer`` **default**: ``443``
+.. code-block:: ini
 
-The port for https requests (this is used when matching the scheme).
+    // example for PhpStorm
+    xdebug.file_link_format="phpstorm://open?file=%f&line=%l"
 
-strict_requirements
-...................
+    // example for PhpStorm with Jetbrains Toolbox
+    xdebug.file_link_format="jetbrains://phpstorm/navigate/reference?project=example&path=%f:%l"
 
-**type**: ``mixed`` **default**: ``true``
+    // example for Sublime Text
+    xdebug.file_link_format="subl://open?url=file://%f&line=%l"
 
-Determines the routing generator behavior. When generating a route that
-has specific :ref:`parameter requirements <routing-requirements>`, the generator
-can behave differently in case the used parameters do not meet these requirements.
+.. note::
 
-The value can be one of:
+    If both ``framework.ide`` and ``xdebug.file_link_format`` are defined,
+    Symfony uses the value of the ``xdebug.file_link_format`` option.
 
-``true``
-    Throw an exception when the requirements are not met;
-``false``
-    Disable exceptions when the requirements are not met and return ``''``
-    instead;
-``null``
-    Disable checking the requirements (thus, match the route even when the
-    requirements don't match).
+.. tip::
 
-``true`` is recommended in the development environment, while ``false``
-or ``null`` might be preferred in production.
+    Setting the ``xdebug.file_link_format`` ini option works even if the Xdebug
+    extension is not enabled.
 
-utf8
-....
+.. tip::
 
-**type**: ``boolean`` **default**: ``true``
+    When running your app in a container or in a virtual machine, you can tell
+    Symfony to map files from the guest to the host by changing their prefix.
+    This map should be specified at the end of the URL template, using ``&`` and
+    ``>`` as guest-to-host separators:
 
-When this option is set to ``true``, the regular expressions used in the
-:ref:`requirements of route parameters <routing-requirements>` will be run
-using the `utf-8 modifier`_. This will for example match any UTF-8 character
-when using ``.``, instead of matching only a single byte.
+    .. code-block:: text
 
-If the charset of your application is UTF-8 (as defined in the
-:ref:`getCharset() method <configuration-kernel-charset>` of your kernel) it's
-recommended setting it to ``true``. This will make non-UTF8 URLs to generate 404
-errors.
+        // /path/to/guest/.../file will be opened
+        // as /path/to/host/.../file on the host
+        // and /var/www/app/ as /projects/my_project/ also
+        'myide://%%f:%%l&/path/to/guest/>/path/to/host/&/var/www/app/>/projects/my_project/&...'
 
-cache_dir
-.........
+        // example for PhpStorm
+        'phpstorm://open?file=%%f&line=%%l&/var/www/app/>/projects/my_project/'
 
-**type**: ``string`` **default**: ``%kernel.cache_dir%``
+.. _reference-lock:
 
-The directory where routing information will be cached. Can be set to
-``~`` (``null``) to disable route caching.
+lock
+~~~~
 
-.. deprecated:: 7.1
+**type**: ``string`` | ``array``
 
-    Setting the ``cache_dir`` option is deprecated since Symfony 7.1. The routes
-    are now always cached in the ``%kernel.build_dir%`` directory.
+The default lock adapter. If not defined, the value is set to ``semaphore`` when
+available, or to ``flock`` otherwise. Store's DSN are also allowed.
 
-secrets
-~~~~~~~
+.. _reference-lock-enabled:
 
 enabled
 .......
 
 **type**: ``boolean`` **default**: ``true``
 
-Whether to enable or not secrets managements.
-
-decryption_env_var
-..................
-
-**type**: ``string`` **default**: ``base64:default::SYMFONY_DECRYPTION_SECRET``
-
-The env var name that contains the vault decryption secret. By default, this
-value will be decoded from base64.
-
-local_dotenv_file
-.................
-
-**type**: ``string`` **default**: ``%kernel.project_dir%/.env.%kernel.environment%.local``
-
-The path to the local ``.env`` file. This file must contain the vault
-decryption key, given by the ``decryption_env_var`` option.
-
-vault_directory
-...............
-
-**type**: ``string`` **default**: ``%kernel.project_dir%/config/secrets/%kernel.runtime_environment%``
-
-The directory to store the secret vault. By default, the path includes the value
-of the :ref:`kernel.runtime_environment <configuration-kernel-runtime-environment>`
-parameter.
-
-.. _config-framework-session:
-
-session
-~~~~~~~
-
-.. _storage_id:
-
-storage_factory_id
-..................
-
-**type**: ``string`` **default**: ``session.storage.factory.native``
-
-The service ID used for creating the ``SessionStorageInterface`` that stores
-the session. This service is available in the Symfony application via the
-``session.storage.factory`` service alias. The class has to implement
-:class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\SessionStorageFactoryInterface`.
-To see a list of all available storages, run:
-
-.. code-block:: terminal
-
-    $ php bin/console debug:container session.storage.factory.
-
-.. _config-framework-session-handler-id:
+Whether to enable the support for lock or not. This setting is
+automatically set to ``true`` when one of the child settings is configured.
 
-handler_id
-..........
+.. _reference-lock-resources:
 
-**type**: ``string`` | ``null`` **default**: ``null``
+resources
+.........
 
-If ``framework.session.save_path`` is not set, the default value of this option
-is ``null``, which means to use the session handler configured in php.ini. If the
-``framework.session.save_path`` option is set, then Symfony stores sessions using
-the native file session handler.
+**type**: ``array``
 
-It is possible to :ref:`store sessions in a database <session-database>`,
-and also to configure the session handler with a DSN:
+A map of lock stores to be created by the framework extension, with
+the name as key and DSN or service id as value:
 
 .. configuration-block::
 
     .. code-block:: yaml
 
-        # config/packages/framework.yaml
+        # config/packages/lock.yaml
         framework:
-            session:
-                # a few possible examples
-                handler_id: 'redis://localhost'
-                handler_id: '%env(REDIS_URL)%'
-                handler_id: '%env(DATABASE_URL)%'
-                handler_id: 'file://%kernel.project_dir%/var/sessions'
+            lock: '%env(LOCK_DSN)%'
 
     .. code-block:: xml
 
-        <!-- config/packages/framework.xml -->
+        <!-- config/packages/lock.xml -->
         <?xml version="1.0" encoding="UTF-8" ?>
         <container xmlns="http://symfony.com/schema/dic/services"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
@@ -1649,250 +2140,181 @@ and also to configure the session handler with a DSN:
             xsi:schemaLocation="http://symfony.com/schema/dic/services
                 https://symfony.com/schema/dic/services/services-1.0.xsd
                 http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
+
             <framework:config>
-                <!-- a few possible examples -->
-                <framework:session enabled="true"
-                    handler-id="redis://localhost"
-                    handler-id="%env(REDIS_URL)%"
-                    handler-id="%env(DATABASE_URL)%"
-                    handler-id="file://%kernel.project_dir%/var/sessions"/>
+                <framework:lock>
+                    <framework:resource name="default">%env(LOCK_DSN)%</framework:resource>
+                </framework:lock>
             </framework:config>
         </container>
 
     .. code-block:: php
 
-        // config/packages/framework.php
-        use function Symfony\Component\DependencyInjection\Loader\Configurator\env;
+        // config/packages/lock.php
         use Symfony\Config\FrameworkConfig;
 
         return static function (FrameworkConfig $framework): void {
-            // ...
-
-            $framework->session()
-                // a few possible examples
-                ->handlerId('redis://localhost')
-                ->handlerId(env('REDIS_URL'))
-                ->handlerId(env('DATABASE_URL'))
-                ->handlerId('file://%kernel.project_dir%/var/sessions');
+            $framework->lock()
+                ->resource('default', [env('LOCK_DSN')]);
         };
 
-.. note::
-
-    Supported DSN protocols are the following:
+.. seealso::
 
-    * ``file``
-    * ``redis``
-    * ``rediss`` (Redis over TLS)
-    * ``memcached`` (requires :doc:`symfony/cache </components/cache>`)
-    * ``pdo_oci`` (requires :doc:`doctrine/dbal </doctrine/dbal>`)
-    * ``mssql``
-    * ``mysql``
-    * ``mysql2``
-    * ``pgsql``
-    * ``postgres``
-    * ``postgresql``
-    * ``sqlsrv``
-    * ``sqlite``
-    * ``sqlite3``
+    For more details, see :doc:`/lock`.
 
-.. _name:
+.. _reference-lock-resources-name:
 
 name
-....
-
-**type**: ``string``
-
-This specifies the name of the session cookie.
-
-If not set, ``php.ini``'s `session.name`_ directive will be relied on.
-
-cookie_lifetime
-...............
+""""
 
-**type**: ``integer``
+**type**: ``prototype``
 
-This determines the lifetime of the session - in seconds.
-Setting this value to ``0`` means the cookie is valid for
-the length of the browser session.
+Name of the lock you want to create.
 
-If not set, ``php.ini``'s `session.cookie_lifetime`_ directive will be relied on.
+mailer
+~~~~~~
 
-cookie_path
-...........
+.. _mailer-dsn:
 
-**type**: ``string``
+dsn
+...
 
-This determines the path to set in the session cookie.
+**type**: ``string`` **default**: ``null``
 
-If not set, ``php.ini``'s `session.cookie_path`_ directive will be relied on.
+The DSN used by the mailer. When several DSN may be used, use
+``transports`` option (see below) instead.
 
-cache_limiter
-.............
+envelope
+........
 
-**type**: ``string`` **default**: ``0``
+recipients
+""""""""""
 
-If set to ``0``, Symfony won't set any particular header related to the cache
-and it will rely on ``php.ini``'s `session.cache_limiter`_ directive.
+**type**: ``array``
 
-Unlike the other session options, ``cache_limiter`` is set as a regular
-:ref:`container parameter <configuration-parameters>`:
+The "envelope recipient" which is used as the value of ``RCPT TO`` during the
+the `SMTP session`_. This value overrides any other recipient set in the code.
 
 .. configuration-block::
 
     .. code-block:: yaml
 
-        # config/services.yaml
-        parameters:
-            session.storage.options:
-                cache_limiter: 0
+        # config/packages/mailer.yaml
+        framework:
+            mailer:
+                dsn: 'smtp://localhost:25'
+                envelope:
+                    recipients: ['admin@symfony.com', 'lead@symfony.com']
 
     .. code-block:: xml
 
-        <!-- config/services.xml -->
+        <!-- config/packages/mailer.xml -->
         <?xml version="1.0" encoding="UTF-8" ?>
         <container xmlns="http://symfony.com/schema/dic/services"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+            xmlns:framework="http://symfony.com/schema/dic/symfony"
             xsi:schemaLocation="http://symfony.com/schema/dic/services
-                https://symfony.com/schema/dic/services/services-1.0.xsd">
-
-            <parameters>
-                <parameter key="session.storage.options" type="collection">
-                    <parameter key="cache_limiter">0</parameter>
-                </parameter>
-            </parameters>
+                https://symfony.com/schema/dic/services/services-1.0.xsd
+                http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
+            <framework:config>
+                <framework:mailer dsn="smtp://localhost:25">
+                    <framework:envelope>
+                        <framework:recipient>admin@symfony.com</framework:recipient>
+                        <framework:recipient>lead@symfony.com</framework:recipient>
+                    </framework:envelope>
+                </framework:mailer>
+            </framework:config>
         </container>
 
     .. code-block:: php
 
-        // config/services.php
-        $container->setParameter('session.storage.options', [
-            'cache_limiter' => 0,
-        ]);
+        // config/packages/mailer.php
+        namespace Symfony\Component\DependencyInjection\Loader\Configurator;
 
-Be aware that if you configure it, you'll have to set other session-related options
-as parameters as well.
+        return static function (ContainerConfigurator $container): void {
+            $container->extension('framework', [
+                'mailer' => [
+                    'dsn' => 'smtp://localhost:25',
+                    'envelope' => [
+                        'recipients' => [
+                            'admin@symfony.com',
+                            'lead@symfony.com',
+                        ],
+                    ],
+                ],
+            ]);
+        };
 
-cookie_domain
-.............
+sender
+""""""
 
 **type**: ``string``
 
-This determines the domain to set in the session cookie.
-
-If not set, ``php.ini``'s `session.cookie_domain`_ directive will be relied on.
-
-cookie_samesite
-...............
-
-**type**: ``string`` or ``null`` **default**: ``null``
-
-It controls the way cookies are sent when the HTTP request did not originate
-from the same domain that is associated with the cookies. Setting this option is
-recommended to mitigate `CSRF security attacks`_.
-
-By default, browsers send all cookies related to the domain of the HTTP request.
-This may be a problem for example when you visit a forum and some malicious
-comment includes a link like ``https://some-bank.com/?send_money_to=attacker&amount=1000``.
-If you were previously logged into your bank website, the browser will send all
-those cookies when making that HTTP request.
+The "envelope sender" which is used as the value of ``MAIL FROM`` during the
+`SMTP session`_. This value overrides any other sender set in the code.
 
-The possible values for this option are:
+.. _mailer-headers:
 
-* ``null``, use ``php.ini``'s `session.cookie_samesite`_ directive.
-* ``'none'`` (or the ``Symfony\Component\HttpFoundation\Cookie::SAMESITE_NONE`` constant), use it to allow
-  sending of cookies when the HTTP request originated from a different domain
-  (previously this was the default behavior of null, but in newer browsers ``'lax'``
-  would be applied when the header has not been set)
-* ``'strict'`` (or the ``Cookie::SAMESITE_STRICT`` constant), use it to never
-  send any cookie when the HTTP request did not originate from the same domain.
-* ``'lax'`` (or the ``Cookie::SAMESITE_LAX`` constant), use it to allow sending
-  cookies when the request originated from a different domain, but only when the
-  user consciously made the request (by clicking a link or submitting a form
-  with the ``GET`` method).
+headers
+.......
 
-cookie_secure
-.............
+**type**: ``array``
 
-**type**: ``boolean`` or ``'auto'``
+Headers to add to emails. The key (``name`` attribute in xml format) is the
+header name and value the header value.
 
-This determines whether cookies should only be sent over secure connections. In
-addition to ``true`` and ``false``, there's a special ``'auto'`` value that
-means ``true`` for HTTPS requests and ``false`` for HTTP requests.
+.. seealso::
 
-If not set, ``php.ini``'s `session.cookie_secure`_ directive will be relied on.
+    For more information, see :ref:`Configuring Emails Globally <mailer-configure-email-globally>`
 
-cookie_httponly
-...............
+message_bus
+...........
 
-**type**: ``boolean`` **default**: ``true``
+**type**: ``string`` **default**: ``null`` or default bus if Messenger component is installed
 
-This determines whether cookies should only be accessible through the HTTP
-protocol. This means that the cookie won't be accessible by scripting
-languages, such as JavaScript. This setting can effectively help to reduce
-identity theft through :ref:`XSS attacks <xss-attacks>`.
+Service identifier of the message bus to use when using the
+:doc:`Messenger component </messenger>` (e.g. ``messenger.default_bus``).
 
-gc_divisor
+transports
 ..........
 
-**type**: ``integer``
-
-See `gc_probability`_.
-
-If not set, ``php.ini``'s `session.gc_divisor`_ directive will be relied on.
-
-gc_probability
-..............
-
-**type**: ``integer`` **default**: ``1``
-
-This defines the probability that the garbage collector (GC) process is
-started on every session initialization. The probability is calculated by
-using ``gc_probability`` / ``gc_divisor``, e.g. 1/100 means there is a 1%
-chance that the GC process will start on each request.
-
-gc_maxlifetime
-..............
-
-**type**: ``integer``
-
-This determines the number of seconds after which data will be seen as "garbage"
-and potentially cleaned up. Garbage collection may occur during session
-start and depends on `gc_divisor`_ and `gc_probability`_.
-
-If not set, ``php.ini``'s `session.gc_maxlifetime`_ directive will be relied on.
+**type**: ``array``
 
-sid_length
-..........
+A :ref:`list of DSN <multiple-email-transports>` that can be used by the
+mailer. A transport name is the key and the dsn is the value.
 
-**type**: ``integer``
+messenger
+~~~~~~~~~
 
-This determines the length of session ID string, which can be an integer between
-``22`` and ``256`` (both inclusive), ``32`` being the recommended value. Longer
-session IDs are harder to guess.
+enabled
+.......
 
-If not set, ``php.ini``'s `session.sid_length`_ directive will be relied on.
+**type**: ``boolean`` **default**: ``true``
 
-sid_bits_per_character
-......................
+Whether to enable or not Messenger.
 
-**type**: ``integer``
+.. seealso::
 
-This determines the number of bits in the encoded session ID character. The possible
-values are ``4`` (0-9, a-f), ``5`` (0-9, a-v), and ``6`` (0-9, a-z, A-Z, "-", ",").
-The more bits results in stronger session ID. ``5`` is recommended value for
-most environments.
+    For more details, see the :doc:`Messenger component </messenger>`
+    documentation.
 
-If not set, ``php.ini``'s `session.sid_bits_per_character`_ directive will be relied on.
+php_errors
+~~~~~~~~~~
 
-save_path
-.........
+log
+...
 
-**type**: ``string`` | ``null`` **default**: ``%kernel.cache_dir%/sessions``
+**type**: ``boolean``, ``int`` or ``array<int, string>`` **default**: ``true``
 
-This determines the argument to be passed to the save handler. If you choose
-the default file handler, this is the path where the session files are created.
+Use the application logger instead of the PHP logger for logging PHP errors.
+When an integer value is used, it defines a bitmask of PHP errors that will
+be logged. Those integer values must be the same used in the
+`error_reporting PHP option`_. The default log levels will be used for each
+PHP error.
+When a boolean value is used, ``true`` enables logging for all PHP errors
+while ``false`` disables logging entirely.
 
-If ``null``, ``php.ini``'s `session.save_path`_ directive will be relied on:
+This option also accepts a map of PHP errors to log levels:
 
 .. configuration-block::
 
@@ -1900,8 +2322,23 @@ If ``null``, ``php.ini``'s `session.save_path`_ directive will be relied on:
 
         # config/packages/framework.yaml
         framework:
-            session:
-                save_path: ~
+            php_errors:
+                log:
+                    !php/const \E_DEPRECATED: !php/const Psr\Log\LogLevel::ERROR
+                    !php/const \E_USER_DEPRECATED: !php/const Psr\Log\LogLevel::ERROR
+                    !php/const \E_NOTICE: !php/const Psr\Log\LogLevel::ERROR
+                    !php/const \E_USER_NOTICE: !php/const Psr\Log\LogLevel::ERROR
+                    !php/const \E_STRICT: !php/const Psr\Log\LogLevel::ERROR
+                    !php/const \E_WARNING: !php/const Psr\Log\LogLevel::ERROR
+                    !php/const \E_USER_WARNING: !php/const Psr\Log\LogLevel::ERROR
+                    !php/const \E_COMPILE_WARNING: !php/const Psr\Log\LogLevel::ERROR
+                    !php/const \E_CORE_WARNING: !php/const Psr\Log\LogLevel::ERROR
+                    !php/const \E_USER_ERROR: !php/const Psr\Log\LogLevel::CRITICAL
+                    !php/const \E_RECOVERABLE_ERROR: !php/const Psr\Log\LogLevel::CRITICAL
+                    !php/const \E_COMPILE_ERROR: !php/const Psr\Log\LogLevel::CRITICAL
+                    !php/const \E_PARSE: !php/const Psr\Log\LogLevel::CRITICAL
+                    !php/const \E_ERROR: !php/const Psr\Log\LogLevel::CRITICAL
+                    !php/const \E_CORE_ERROR: !php/const Psr\Log\LogLevel::CRITICAL
 
     .. code-block:: xml
 
@@ -1915,282 +2352,241 @@ If ``null``, ``php.ini``'s `session.save_path`_ directive will be relied on:
                 http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
 
             <framework:config>
-                <framework:session save-path="null"/>
+                <!-- in XML configuration you cannot use PHP constants as the value of
+                     the 'type' attribute, which makes this format way less readable.
+                     Consider using YAML or PHP for this configuration -->
+                <framework:log type="8" logLevel="error"/>
+                <framework:log type="2" logLevel="error"/>
+                <!-- ... -->
             </framework:config>
         </container>
 
     .. code-block:: php
 
         // config/packages/framework.php
+        use Psr\Log\LogLevel;
         use Symfony\Config\FrameworkConfig;
 
         return static function (FrameworkConfig $framework): void {
-            $framework->session()
-                ->savePath(null);
+            $framework->phpErrors()->log(\E_DEPRECATED, LogLevel::ERROR);
+            $framework->phpErrors()->log(\E_USER_DEPRECATED, LogLevel::ERROR);
+            // ...
         };
 
-.. _reference-session-metadata-update-threshold:
-
-metadata_update_threshold
-.........................
+throw
+.....
 
-**type**: ``integer`` **default**: ``0``
+**type**: ``boolean`` **default**: ``%kernel.debug%``
 
-This is how many seconds to wait between updating/writing the session metadata.
-This can be useful if, for some reason, you want to limit the frequency at which
-the session persists, instead of doing that on every request.
+Throw PHP errors as ``\ErrorException`` instances. The parameter
+``debug.error_handler.throw_at`` controls the threshold.
 
-.. _reference-session-enabled:
+profiler
+~~~~~~~~
 
-enabled
+collect
 .......
 
 **type**: ``boolean`` **default**: ``true``
 
-Whether to enable the session support in the framework.
+This option configures the way the profiler behaves when it is enabled. If set
+to ``true``, the profiler collects data for all requests. If you want to only
+collect information on-demand, you can set the ``collect`` flag to ``false`` and
+activate the data collectors manually::
 
-.. configuration-block::
+    $profiler->enable();
 
-    .. code-block:: yaml
+collect_parameter
+.................
 
-        # config/packages/framework.yaml
-        framework:
-            session:
-                enabled: true
+**type**: ``string`` **default**: ``null``
 
-    .. code-block:: xml
+This specifies name of a query parameter, a body parameter or a request attribute
+used to enable or disable collection of data by the profiler for each request.
+Combine it with the ``collect`` option to enable/disable the profiler on demand:
 
-        <!-- config/packages/framework.xml -->
-        <?xml version="1.0" encoding="UTF-8" ?>
-        <container xmlns="http://symfony.com/schema/dic/services"
-            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-            xmlns:framework="http://symfony.com/schema/dic/symfony"
-            xsi:schemaLocation="http://symfony.com/schema/dic/services
-                https://symfony.com/schema/dic/services/services-1.0.xsd
-                http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
+* If the ``collect`` option is set to ``true`` but this parameter exists in a
+  request and has any value other than ``true``, ``yes``, ``on`` or ``1``, the
+  request data will not be collected;
+* If the ``collect`` option is set to ``false``, but this parameter exists in a
+  request and has value of ``true``, ``yes``, ``on`` or ``1``, the request data
+  will be collected.
 
-            <framework:config>
-                <framework:session enabled="true"/>
-            </framework:config>
-        </container>
+.. _collect_serializer_data:
 
-    .. code-block:: php
+collect_serializer_data
+.......................
 
-        // config/packages/framework.php
-        use Symfony\Config\FrameworkConfig;
+**type**: ``boolean`` **default**: ``false``
 
-        return static function (FrameworkConfig $framework): void {
-            $framework->session()
-                ->enabled(true);
-        };
+When this option is ``true``, all normalizers and encoders are
+decorated by traceable implementations that collect profiling information about them.
 
-use_cookies
-...........
+.. deprecated:: 7.3
 
-**type**: ``boolean``
+    Setting the ``collect_serializer_data`` option to ``false`` is deprecated
+    since Symfony 7.3.
 
-This specifies if the session ID is stored on the client side using cookies or
-not.
+.. _profiler-dsn:
 
-If not set, ``php.ini``'s `session.use_cookies`_ directive will be relied on.
+dsn
+...
 
-ssi
-~~~
+**type**: ``string`` **default**: ``file:%kernel.cache_dir%/profiler``
+
+The DSN where to store the profiling information.
+
+.. _reference-profiler-enabled:
 
 enabled
 .......
 
 **type**: ``boolean`` **default**: ``false``
 
-Whether to enable or not SSI support in your application.
-
-assets
-~~~~~~
-
-.. _reference-assets-base-path:
+The profiler can be enabled by setting this option to ``true``. When you
+install it using Symfony Flex, the profiler is enabled in the ``dev``
+and ``test`` environments.
 
-base_path
-.........
+.. note::
 
-**type**: ``string``
+    The profiler works independently from the Web Developer Toolbar, see
+    the :doc:`WebProfilerBundle configuration </reference/configuration/web_profiler>`
+    on how to disable/enable the toolbar.
 
-This option allows you to define a base path to be used for assets:
+only_exceptions
+...............
 
-.. configuration-block::
+**type**: ``boolean`` **default**: ``false``
 
-    .. code-block:: yaml
+When this is set to ``true``, the profiler will only be enabled when an
+exception is thrown during the handling of the request.
 
-        # config/packages/framework.yaml
-        framework:
-            # ...
-            assets:
-                base_path: '/images'
+.. _only_master_requests:
 
-    .. code-block:: xml
+only_main_requests
+..................
 
-        <!-- config/packages/framework.xml -->
-        <?xml version="1.0" encoding="UTF-8" ?>
-        <container xmlns="http://symfony.com/schema/dic/services"
-            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-            xmlns:framework="http://symfony.com/schema/dic/symfony"
-            xsi:schemaLocation="http://symfony.com/schema/dic/services
-                https://symfony.com/schema/dic/services/services-1.0.xsd
-                http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
+**type**: ``boolean`` **default**: ``false``
 
-            <framework:config>
-                <framework:assets base-path="/images"/>
-            </framework:config>
-        </container>
+When this is set to ``true``, the profiler will only be enabled on the main
+requests (and not on the subrequests).
 
-    .. code-block:: php
+property_access
+~~~~~~~~~~~~~~~
 
-        // config/packages/framework.php
-        use Symfony\Config\FrameworkConfig;
+magic_call
+..........
 
-        return static function (FrameworkConfig $framework): void {
-            // ...
-            $framework->assets()
-                ->basePath('/images');
-        };
+**type**: ``boolean`` **default**: ``false``
 
-.. _reference-templating-base-urls:
-.. _reference-assets-base-urls:
+When enabled, the ``property_accessor`` service uses PHP's
+:ref:`magic __call() method <components-property-access-magic-call>` when
+its ``getValue()`` method is called.
 
-base_urls
+magic_get
 .........
 
-**type**: ``array``
-
-This option allows you to define base URLs to be used for assets.
-If multiple base URLs are provided, Symfony will select one from the
-collection each time it generates an asset's path:
+**type**: ``boolean`` **default**: ``true``
 
-.. configuration-block::
+When enabled, the ``property_accessor`` service uses PHP's
+:ref:`magic __get() method <components-property-access-magic-get>` when
+its ``getValue()`` method is called.
 
-    .. code-block:: yaml
+magic_set
+.........
 
-        # config/packages/framework.yaml
-        framework:
-            # ...
-            assets:
-                base_urls:
-                    - 'http://cdn.example.com/'
+**type**: ``boolean`` **default**: ``true``
 
-    .. code-block:: xml
+When enabled, the ``property_accessor`` service uses PHP's
+:ref:`magic __set() method <components-property-access-writing-to-objects>` when
+its ``setValue()`` method is called.
 
-        <!-- config/packages/framework.xml -->
-        <?xml version="1.0" encoding="UTF-8" ?>
-        <container xmlns="http://symfony.com/schema/dic/services"
-            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-            xmlns:framework="http://symfony.com/schema/dic/symfony"
-            xsi:schemaLocation="http://symfony.com/schema/dic/services
-                https://symfony.com/schema/dic/services/services-1.0.xsd
-                http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
+throw_exception_on_invalid_index
+................................
 
-            <framework:config>
-                <framework:assets base-url="http://cdn.example.com/"/>
-            </framework:config>
-        </container>
+**type**: ``boolean`` **default**: ``false``
 
-    .. code-block:: php
+When enabled, the ``property_accessor`` service throws an exception when you
+try to access an invalid index of an array.
 
-        // config/packages/framework.php
-        use Symfony\Config\FrameworkConfig;
+throw_exception_on_invalid_property_path
+........................................
 
-        return static function (FrameworkConfig $framework): void {
-            // ...
-            $framework->assets()
-                ->baseUrls(['http://cdn.example.com/']);
-        };
+**type**: ``boolean`` **default**: ``true``
 
-.. _reference-framework-assets-packages:
+When enabled, the ``property_accessor`` service throws an exception when you
+try to access an invalid property path of an object.
 
-packages
-........
+property_info
+~~~~~~~~~~~~~
 
-You can group assets into packages, to specify different base URLs for them:
+.. _reference-property-info-enabled:
 
-.. configuration-block::
+enabled
+.......
 
-    .. code-block:: yaml
+**type**: ``boolean`` **default**: ``true`` or ``false`` depending on your installation
 
-        # config/packages/framework.yaml
-        framework:
-            # ...
-            assets:
-                packages:
-                    avatars:
-                        base_urls: 'http://static_cdn.example.com/avatars'
+with_constructor_extractor
+..........................
 
-    .. code-block:: xml
+**type**: ``boolean`` **default**: ``false``
 
-        <!-- config/packages/framework.xml -->
-        <?xml version="1.0" encoding="UTF-8" ?>
-        <container xmlns="http://symfony.com/schema/dic/services"
-            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-            xmlns:framework="http://symfony.com/schema/dic/symfony"
-            xsi:schemaLocation="http://symfony.com/schema/dic/services
-                https://symfony.com/schema/dic/services/services-1.0.xsd
-                http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
+Configures the ``property_info`` service to extract property information from the constructor arguments
+using the :ref:`ConstructorExtractor <components-property-information-constructor-extractor>`.
 
-            <framework:config>
-                <framework:assets>
-                    <framework:package
-                        name="avatars"
-                        base-url="http://static_cdn.example.com/avatars"/>
-                </framework:assets>
-            </framework:config>
-        </container>
+.. versionadded:: 7.3
 
-    .. code-block:: php
+    The ``with_constructor_extractor`` option was introduced in Symfony 7.3.
 
-        // config/packages/framework.php
-        use Symfony\Config\FrameworkConfig;
+rate_limiter
+~~~~~~~~~~~~
 
-        return static function (FrameworkConfig $framework): void {
-            // ...
-            $framework->assets()
-                ->package('avatars')
-                    ->baseUrls(['http://static_cdn.example.com/avatars']);
-        };
+.. _reference-rate-limiter-name:
 
-Now you can use the ``avatars`` package in your templates:
+name
+....
 
-.. code-block:: html+twig
+**type**: ``prototype``
 
-    <img src="{{ asset('...', 'avatars') }}">
+Name of the rate limiter you want to create.
 
-Each package can configure the following options:
+lock_factory
+""""""""""""
 
-* :ref:`base_path <reference-assets-base-path>`
-* :ref:`base_urls <reference-assets-base-urls>`
-* :ref:`version_strategy <reference-assets-version-strategy>`
-* :ref:`version <reference-framework-assets-version>`
-* :ref:`version_format <reference-assets-version-format>`
-* :ref:`json_manifest_path <reference-assets-json-manifest-path>`
-* :ref:`strict_mode <reference-assets-strict-mode>`
+**type**: ``string`` **default:** ``lock.factory``
 
-.. _reference-framework-assets-version:
-.. _ref-framework-assets-version:
+The service that is used to create a lock. The service has to be an instance of
+the :class:`Symfony\\Component\\Lock\\LockFactory` class.
 
-version
-.......
+policy
+""""""
 
-**type**: ``string``
+**type**: ``string`` **required**
 
-This option is used to *bust* the cache on assets by globally adding a query
-parameter to all rendered asset paths (e.g. ``/images/logo.png?v2``). This
-applies only to assets rendered via the Twig ``asset()`` function (or PHP
-equivalent).
+The name of the rate limiting algorithm to use. Example names are ``fixed_window``,
+``sliding_window`` and ``no_limit``. See :ref:`Rate Limiter Policies <rate-limiter-policies>`)
+for more information.
 
-For example, suppose you have the following:
+request
+~~~~~~~
 
-.. code-block:: html+twig
+formats
+.......
 
-    <img src="{{ asset('images/logo.png') }}" alt="Symfony!"/>
+**type**: ``array`` **default**: ``[]``
 
-By default, this will render a path to your image such as ``/images/logo.png``.
-Now, activate the ``version`` option:
+This setting is used to associate additional request formats (e.g. ``html``)
+to one or more mime types (e.g. ``text/html``), which will allow you to use the
+format & mime types to call
+:method:`Request::getFormat($mimeType) <Symfony\\Component\\HttpFoundation\\Request::getFormat>` or
+:method:`Request::getMimeType($format) <Symfony\\Component\\HttpFoundation\\Request::getMimeType>`.
+
+In practice, this is important because Symfony uses it to automatically set the
+``Content-Type`` header on the ``Response`` (if you don't explicitly set one).
+If you pass an array of mime types, the first will be used for the header.
+
+To configure a ``jsonp`` format:
 
 .. configuration-block::
 
@@ -2198,23 +2594,29 @@ Now, activate the ``version`` option:
 
         # config/packages/framework.yaml
         framework:
-            # ...
-            assets:
-                version: 'v2'
+            request:
+                formats:
+                    jsonp: 'application/javascript'
 
     .. code-block:: xml
 
         <!-- config/packages/framework.xml -->
         <?xml version="1.0" encoding="UTF-8" ?>
+
         <container xmlns="http://symfony.com/schema/dic/services"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns:framework="http://symfony.com/schema/dic/symfony"
             xsi:schemaLocation="http://symfony.com/schema/dic/services
                 https://symfony.com/schema/dic/services/services-1.0.xsd
-                http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
+                http://symfony.com/schema/dic/symfony
+                https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
 
             <framework:config>
-                <framework:assets version="v2"/>
+                <framework:request>
+                    <framework:format name="jsonp">
+                        <framework:mime-type>application/javascript</framework:mime-type>
+                    </framework:format>
+                </framework:request>
             </framework:config>
         </container>
 
@@ -2224,591 +2626,465 @@ Now, activate the ``version`` option:
         use Symfony\Config\FrameworkConfig;
 
         return static function (FrameworkConfig $framework): void {
-            // ...
-            $framework->assets()
-                ->version('v2');
+            $framework->request()
+                ->format('jsonp', 'application/javascript');
         };
 
-Now, the same asset will be rendered as ``/images/logo.png?v2`` If you use
-this feature, you **must** manually increment the ``version`` value
-before each deployment so that the query parameters change.
-
-You can also control how the query string works via the `version_format`_
-option.
-
-.. note::
-
-    This parameter cannot be set at the same time as ``version_strategy`` or ``json_manifest_path``.
-
-.. tip::
-
-    As with all settings, you can use a parameter as value for the
-    ``version``. This makes it easier to increment the cache on each
-    deployment.
-
-.. _reference-templating-version-format:
-.. _reference-assets-version-format:
-
-version_format
-..............
-
-**type**: ``string`` **default**: ``%%s?%%s``
-
-This specifies a :phpfunction:`sprintf` pattern that will be used with the
-`version`_ option to construct an asset's path. By default, the pattern
-adds the asset's version as a query string. For example, if
-``version_format`` is set to ``%%s?version=%%s`` and ``version``
-is set to ``5``, the asset's path would be ``/images/logo.png?version=5``.
-
-.. note::
-
-    All percentage signs (``%``) in the format string must be doubled to
-    escape the character. Without escaping, values might inadvertently be
-    interpreted as :ref:`service-container-parameters`.
+router
+~~~~~~
 
-.. tip::
+cache_dir
+.........
 
-    Some CDN's do not support cache-busting via query strings, so injecting
-    the version into the actual file path is necessary. Thankfully,
-    ``version_format`` is not limited to producing versioned query
-    strings.
+**type**: ``string`` **default**: ``%kernel.cache_dir%``
 
-    The pattern receives the asset's original path and version as its first
-    and second parameters, respectively. Since the asset's path is one
-    parameter, you cannot modify it in-place (e.g. ``/images/logo-v5.png``);
-    however, you can prefix the asset's path using a pattern of
-    ``version-%%2$s/%%1$s``, which would result in the path
-    ``version-5/images/logo.png``.
+The directory where routing information will be cached. Can be set to
+``~`` (``null``) to disable route caching.
 
-    URL rewrite rules could then be used to disregard the version prefix
-    before serving the asset. Alternatively, you could copy assets to the
-    appropriate version path as part of your deployment process and forgot
-    any URL rewriting. The latter option is useful if you would like older
-    asset versions to remain accessible at their original URL.
+.. deprecated:: 7.1
 
-.. _reference-assets-version-strategy:
-.. _reference-templating-version-strategy:
+    Setting the ``cache_dir`` option is deprecated since Symfony 7.1. The routes
+    are now always cached in the ``%kernel.build_dir%`` directory.
 
-version_strategy
-................
+default_uri
+...........
 
-**type**: ``string`` **default**: ``null``
+**type**: ``string``
 
-The service id of the :doc:`asset version strategy </frontend/custom_version_strategy>`
-applied to the assets. This option can be set globally for all assets and
-individually for each asset package:
+The default URI used to generate URLs in a non-HTTP context (see
+:ref:`Generating URLs in Commands <router-generate-urls-commands>`).
 
-.. configuration-block::
+http_port
+.........
 
-    .. code-block:: yaml
+**type**: ``integer`` **default**: ``80``
 
-        # config/packages/framework.yaml
-        framework:
-            assets:
-                # this strategy is applied to every asset (including packages)
-                version_strategy: 'app.asset.my_versioning_strategy'
-                packages:
-                    foo_package:
-                        # this package removes any versioning (its assets won't be versioned)
-                        version: ~
-                    bar_package:
-                        # this package uses its own strategy (the default strategy is ignored)
-                        version_strategy: 'app.asset.another_version_strategy'
-                    baz_package:
-                        # this package inherits the default strategy
-                        base_path: '/images'
+The port for normal http requests (this is used when matching the scheme).
 
-    .. code-block:: xml
+https_port
+..........
 
-        <!-- config/packages/framework.xml -->
-        <?xml version="1.0" encoding="UTF-8" ?>
-        <container xmlns="http://symfony.com/schema/dic/services"
-            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-            xmlns:framework="http://symfony.com/schema/dic/symfony"
-            xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd
-                http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
+**type**: ``integer`` **default**: ``443``
 
-            <framework:config>
-                <framework:assets version-strategy="app.asset.my_versioning_strategy">
-                    <!-- this package removes any versioning (its assets won't be versioned) -->
-                    <framework:package
-                        name="foo_package"
-                        version="null"/>
-                    <!-- this package uses its own strategy (the default strategy is ignored) -->
-                    <framework:package
-                        name="bar_package"
-                        version-strategy="app.asset.another_version_strategy"/>
-                    <!-- this package inherits the default strategy -->
-                    <framework:package
-                        name="baz_package"
-                        base_path="/images"/>
-                </framework:assets>
-            </framework:config>
-        </container>
+The port for https requests (this is used when matching the scheme).
 
-    .. code-block:: php
+resource
+........
 
-        // config/packages/framework.php
-        use Symfony\Config\FrameworkConfig;
+**type**: ``string`` **required**
 
-        return static function (FrameworkConfig $framework): void {
-            // ...
-            $framework->assets()
-                ->versionStrategy('app.asset.my_versioning_strategy');
+The path the main routing resource (e.g. a YAML file) that contains the
+routes and imports the router should load.
 
-            $framework->assets()->package('foo_package')
-                // this package removes any versioning (its assets won't be versioned)
-                ->version(null);
+strict_requirements
+...................
 
-            $framework->assets()->package('bar_package')
-                // this package uses its own strategy (the default strategy is ignored)
-                ->versionStrategy('app.asset.another_version_strategy');
+**type**: ``mixed`` **default**: ``true``
 
-            $framework->assets()->package('baz_package')
-                // this package inherits the default strategy
-                ->basePath('/images');
-        };
+Determines the routing generator behavior. When generating a route that
+has specific :ref:`parameter requirements <routing-requirements>`, the generator
+can behave differently in case the used parameters do not meet these requirements.
 
-.. note::
+The value can be one of:
 
-    This parameter cannot be set at the same time as ``version`` or ``json_manifest_path``.
+``true``
+    Throw an exception when the requirements are not met;
+``false``
+    Disable exceptions when the requirements are not met and return ``''``
+    instead;
+``null``
+    Disable checking the requirements (thus, match the route even when the
+    requirements don't match).
 
-.. _reference-assets-json-manifest-path:
-.. _reference-templating-json-manifest-path:
+``true`` is recommended in the development environment, while ``false``
+or ``null`` might be preferred in production.
 
-json_manifest_path
-..................
+.. _reference-router-type:
 
-**type**: ``string`` **default**: ``null``
+type
+....
 
-The file path or absolute URL to a ``manifest.json`` file containing an
-associative array of asset names and their respective compiled names. A common
-cache-busting technique using a "manifest" file works by writing out assets with
-a "hash" appended to their file names (e.g. ``main.ae433f1cb.css``) during a
-front-end compilation routine.
+**type**: ``string``
 
-.. tip::
+The type of the resource to hint the loaders about the format. This isn't
+needed when you use the default routers with the expected file extensions
+(``.xml``, ``.yaml``, ``.php``).
 
-    Symfony's :ref:`Webpack Encore <frontend-webpack-encore>` supports
-    :ref:`outputting hashed assets <encore-long-term-caching>`. Moreover, this
-    can be incorporated into many other workflows, including Webpack and
-    Gulp using `webpack-manifest-plugin`_ and `gulp-rev`_, respectively.
+utf8
+....
 
-This option can be set globally for all assets and individually for each asset
-package:
+**type**: ``boolean`` **default**: ``true``
 
-.. configuration-block::
+When this option is set to ``true``, the regular expressions used in the
+:ref:`requirements of route parameters <routing-requirements>` will be run
+using the `utf-8 modifier`_. This will for example match any UTF-8 character
+when using ``.``, instead of matching only a single byte.
 
-    .. code-block:: yaml
+If the charset of your application is UTF-8 (as defined in the
+:ref:`getCharset() method <configuration-kernel-charset>` of your kernel) it's
+recommended setting it to ``true``. This will make non-UTF8 URLs to generate 404
+errors.
 
-        # config/packages/framework.yaml
-        framework:
-            assets:
-                # this manifest is applied to every asset (including packages)
-                json_manifest_path: "%kernel.project_dir%/public/build/manifest.json"
-                # you can use absolute URLs too and Symfony will download them automatically
-                # json_manifest_path: 'https://cdn.example.com/manifest.json'
-                packages:
-                    foo_package:
-                        # this package uses its own manifest (the default file is ignored)
-                        json_manifest_path: "%kernel.project_dir%/public/build/a_different_manifest.json"
-                        # Throws an exception when an asset is not found in the manifest
-                        strict_mode: %kernel.debug%
-                    bar_package:
-                        # this package uses the global manifest (the default file is used)
-                        base_path: '/images'
+.. _configuration-framework-secret:
 
-    .. code-block:: xml
+secret
+~~~~~~
 
-        <!-- config/packages/framework.xml -->
-        <?xml version="1.0" encoding="UTF-8" ?>
-        <container xmlns="http://symfony.com/schema/dic/services"
-            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-            xmlns:framework="http://symfony.com/schema/dic/symfony"
-            xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd
-                http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
+**type**: ``string`` **required**
 
-            <framework:config>
-                <!-- this manifest is applied to every asset (including packages) -->
-                <framework:assets json-manifest-path="%kernel.project_dir%/public/build/manifest.json">
-                <!-- you can use absolute URLs too and Symfony will download them automatically -->
-                <!-- <framework:assets json-manifest-path="https://cdn.example.com/manifest.json"> -->
-                    <!-- this package uses its own manifest (the default file is ignored) -->
-                    <!-- Throws an exception when an asset is not found in the manifest -->
-                    <framework:package
-                        name="foo_package"
-                        json-manifest-path="%kernel.project_dir%/public/build/a_different_manifest.json" strict-mode="%kernel.debug%"/>
-                    <!-- this package uses the global manifest (the default file is used) -->
-                    <framework:package
-                        name="bar_package"
-                        base-path="/images"/>
-                </framework:assets>
-            </framework:config>
-        </container>
+This is a string that should be unique to your application and it's commonly
+used to add more entropy to security related operations. Its value should
+be a series of characters, numbers and symbols chosen randomly and the
+recommended length is around 32 characters.
 
-    .. code-block:: php
+In practice, Symfony uses this value for encrypting the cookies used
+in the :doc:`remember me functionality </security/remember_me>` and for
+creating signed URIs when using :ref:`ESI (Edge Side Includes) <edge-side-includes>`.
+That's why you should treat this value as if it were a sensitive credential and
+**never make it public**.
 
-        // config/packages/framework.php
-        use Symfony\Config\FrameworkConfig;
+This option becomes the service container parameter named ``kernel.secret``,
+which you can use whenever the application needs an immutable random string
+to add more entropy.
 
-        return static function (FrameworkConfig $framework): void {
-            // ...
-            $framework->assets()
-                // this manifest is applied to every asset (including packages)
-                ->jsonManifestPath('%kernel.project_dir%/public/build/manifest.json');
+As with any other security-related parameter, it is a good practice to change
+this value from time to time. However, keep in mind that changing this value
+will invalidate all signed URIs and Remember Me cookies. That's why, after
+changing this value, you should regenerate the application cache and log
+out all the application users.
 
-            // you can use absolute URLs too and Symfony will download them automatically
-            // 'json_manifest_path' => 'https://cdn.example.com/manifest.json',
-            $framework->assets()->package('foo_package')
-                // this package uses its own manifest (the default file is ignored)
-                ->jsonManifestPath('%kernel.project_dir%/public/build/a_different_manifest.json')
-                // Throws an exception when an asset is not found in the manifest
-                ->setStrictMode('%kernel.debug%');
+secrets
+~~~~~~~
 
-            $framework->assets()->package('bar_package')
-                // this package uses the global manifest (the default file is used)
-                ->basePath('/images');
-        };
+decryption_env_var
+..................
 
-.. note::
+**type**: ``string`` **default**: ``base64:default::SYMFONY_DECRYPTION_SECRET``
 
-    This parameter cannot be set at the same time as ``version`` or ``version_strategy``.
-    Additionally, this option cannot be nullified at the package scope if a global manifest
-    file is specified.
+The env var name that contains the vault decryption secret. By default, this
+value will be decoded from base64.
 
-.. tip::
+enabled
+.......
 
-    If you request an asset that is *not found* in the ``manifest.json`` file, the original -
-    *unmodified* - asset path will be returned.
-    You can set ``strict_mode`` to ``true`` to get an exception when an asset is *not found*.
+**type**: ``boolean`` **default**: ``true``
 
-.. note::
+Whether to enable or not secrets managements.
 
-    If a URL is set, the JSON manifest is downloaded on each request using the `http_client`_.
+local_dotenv_file
+.................
 
-.. _reference-assets-strict-mode:
+**type**: ``string`` **default**: ``%kernel.project_dir%/.env.%kernel.environment%.local``
 
-strict_mode
-...........
+The path to the local ``.env`` file. This file must contain the vault
+decryption key, given by the ``decryption_env_var`` option.
 
-**type**: ``boolean`` **default**: ``false``
+vault_directory
+...............
 
-When enabled, the strict mode asserts that all requested assets are in the
-manifest file. This option is useful to detect typos or missing assets, the
-recommended value is ``%kernel.debug%``.
+**type**: ``string`` **default**: ``%kernel.project_dir%/config/secrets/%kernel.runtime_environment%``
 
-translator
-~~~~~~~~~~
+The directory to store the secret vault. By default, the path includes the value
+of the :ref:`kernel.runtime_environment <configuration-kernel-runtime-environment>`
+parameter.
 
-cache_dir
-.........
+semaphore
+~~~~~~~~~
 
-**type**: ``string`` | ``null`` **default**: ``%kernel.cache_dir%/translations``
+**type**: ``string`` | ``array``
 
-Defines the directory where the translation cache is stored. Use ``null`` to
-disable this cache.
+The default semaphore adapter. Store's DSN are also allowed.
 
-.. _reference-translator-enabled:
+.. _reference-semaphore-enabled:
 
 enabled
 .......
 
-**type**: ``boolean`` **default**: ``true`` or ``false`` depending on your installation
+**type**: ``boolean`` **default**: ``true``
 
-Whether or not to enable the ``translator`` service in the service container.
+Whether to enable the support for semaphore or not. This setting is
+automatically set to ``true`` when one of the child settings is configured.
 
-.. _fallback:
+.. _reference-semaphore-resources:
 
-fallbacks
+resources
 .........
 
-**type**: ``string|array`` **default**: value of `default_locale`_
+**type**: ``array``
 
-This option is used when the translation key for the current locale wasn't
-found.
+A map of semaphore stores to be created by the framework extension, with
+the name as key and DSN or service id as value:
 
-.. seealso::
+.. configuration-block::
 
-    For more details, see :doc:`/translation`.
+    .. code-block:: yaml
 
-.. _reference-framework-translator-logging:
+        # config/packages/semaphore.yaml
+        framework:
+            semaphore: '%env(SEMAPHORE_DSN)%'
 
-logging
-.......
+    .. code-block:: xml
+
+        <!-- config/packages/semaphore.xml -->
+        <?xml version="1.0" encoding="UTF-8" ?>
+        <container xmlns="http://symfony.com/schema/dic/services"
+            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+            xmlns:framework="http://symfony.com/schema/dic/symfony"
+            xsi:schemaLocation="http://symfony.com/schema/dic/services
+                https://symfony.com/schema/dic/services/services-1.0.xsd
+                http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
+
+            <framework:config>
+                <framework:semaphore>
+                    <framework:resource name="default">%env(SEMAPHORE_DSN)%</framework:resource>
+                </framework:semaphore>
+            </framework:config>
+        </container>
+
+    .. code-block:: php
+
+        // config/packages/semaphore.php
+        use Symfony\Config\FrameworkConfig;
+        use function Symfony\Component\DependencyInjection\Loader\Configurator\env;
 
-**default**: ``true`` when the debug mode is enabled, ``false`` otherwise.
+        return static function (FrameworkConfig $framework): void {
+            $framework->semaphore()
+                ->resource('default', [env('SEMAPHORE_DSN')]);
+        };
 
-When ``true``, a log entry is made whenever the translator cannot find a translation
-for a given key. The logs are made to the ``translation`` channel at the
-``debug`` level for keys where there is a translation in the fallback
-locale, and the ``warning`` level if there is no translation to use at all.
+.. _reference-semaphore-resources-name:
 
-.. _reference-framework-translator-formatter:
+name
+""""
 
-formatter
-.........
+**type**: ``prototype``
 
-**type**: ``string`` **default**: ``translator.formatter.default``
+Name of the semaphore you want to create.
 
-The ID of the service used to format translation messages. The service class
-must implement the :class:`Symfony\\Component\\Translation\\Formatter\\MessageFormatterInterface`.
+.. _configuration-framework-serializer:
 
-.. _reference-translator-paths:
+serializer
+~~~~~~~~~~
 
-paths
-.....
+.. _reference-serializer-circular_reference_handler:
 
-**type**: ``array`` **default**: ``[]``
+circular_reference_handler
+..........................
 
-This option allows to define an array of paths where the component will look
-for translation files. The later a path is added, the more priority it has
-(translations from later paths overwrite earlier ones). Translations from the
-:ref:`default_path <reference-translator-default_path>` have more priority than
-translations from all these paths.
+**type** ``string``
 
-.. _reference-translator-default_path:
+The service id that is used as the circular reference handler of the default
+serializer. The service has to implement the magic ``__invoke($object)``
+method.
 
-default_path
-............
+.. seealso::
 
-**type**: ``string`` **default**: ``%kernel.project_dir%/translations``
+    For more information, see
+    :ref:`component-serializer-handling-circular-references`.
 
-This option allows to define the path where the application translations files
-are stored.
+default_context
+...............
 
-.. _reference-translator-providers:
+**type**: ``array`` **default**: ``[]``
 
-providers
-.........
+A map with default context options that will be used with each ``serialize`` and ``deserialize``
+call. This can be used for example to set the json encoding behavior by setting ``json_encode_options``
+to a `json_encode flags bitmask`_.
 
-**type**: ``array`` **default**: ``[]``
+You can inspect the :ref:`serializer context builders <serializer-using-context-builders>`
+to discover the available settings.
 
-This option enables and configures :ref:`translation providers <translation-providers>`
-to push and pull your translations to/from third party translation services.
+.. _reference-serializer-enable_annotations:
 
-property_access
-~~~~~~~~~~~~~~~
+enable_attributes
+.................
 
-magic_call
-..........
+**type**: ``boolean`` **default**: ``true``
 
-**type**: ``boolean`` **default**: ``false``
+Enables support for `PHP attributes`_ in the serializer component.
 
-When enabled, the ``property_accessor`` service uses PHP's
-:ref:`magic __call() method <components-property-access-magic-call>` when
-its ``getValue()`` method is called.
+.. seealso::
 
-magic_get
-.........
+    See :ref:`the reference <reference-attributes-serializer>` for a list of supported annotations.
 
-**type**: ``boolean`` **default**: ``true``
+.. _reference-serializer-enabled:
 
-When enabled, the ``property_accessor`` service uses PHP's
-:ref:`magic __get() method <components-property-access-magic-get>` when
-its ``getValue()`` method is called.
+enabled
+.......
 
-magic_set
-.........
+**type**: ``boolean`` **default**: ``true`` or ``false`` depending on your installation
 
-**type**: ``boolean`` **default**: ``true``
+Whether to enable the ``serializer`` service or not in the service container.
 
-When enabled, the ``property_accessor`` service uses PHP's
-:ref:`magic __set() method <components-property-access-writing-to-objects>` when
-its ``setValue()`` method is called.
+.. _reference-serializer-mapping:
 
-throw_exception_on_invalid_index
-................................
+mapping
+.......
 
-**type**: ``boolean`` **default**: ``false``
+.. _reference-serializer-mapping-paths:
 
-When enabled, the ``property_accessor`` service throws an exception when you
-try to access an invalid index of an array.
+paths
+"""""
 
-throw_exception_on_invalid_property_path
-........................................
+**type**: ``array`` **default**: ``[]``
 
-**type**: ``boolean`` **default**: ``true``
+This option allows to define an array of paths with files or directories where
+the component will look for additional serialization files.
 
-When enabled, the ``property_accessor`` service throws an exception when you
-try to access an invalid property path of an object.
+.. _reference-serializer-name_converter:
 
-property_info
-~~~~~~~~~~~~~
+name_converter
+..............
 
-.. _reference-property-info-enabled:
+**type**: ``string``
 
-enabled
-.......
+The name converter to use.
+The :class:`Symfony\\Component\\Serializer\\NameConverter\\CamelCaseToSnakeCaseNameConverter`
+name converter can enabled by using the ``serializer.name_converter.camel_case_to_snake_case``
+value.
 
-**type**: ``boolean`` **default**: ``true`` or ``false`` depending on your installation
+.. seealso::
 
-.. _reference-validation:
+    For more information, see :ref:`serializer-name-conversion`.
 
-validation
-~~~~~~~~~~
+.. _config-framework-session:
 
-.. _reference-validation-auto-mapping:
+session
+~~~~~~~
 
-auto_mapping
-............
+cache_limiter
+.............
 
-**type**: ``array`` **default**: ``[]``
+**type**: ``string`` **default**: ``0``
 
-Defines the Doctrine entities that will be introspected to add
-:ref:`automatic validation constraints <automatic_object_validation>` to them:
+If set to ``0``, Symfony won't set any particular header related to the cache
+and it will rely on ``php.ini``'s `session.cache_limiter`_ directive.
+
+Unlike the other session options, ``cache_limiter`` is set as a regular
+:ref:`container parameter <configuration-parameters>`:
 
 .. configuration-block::
 
     .. code-block:: yaml
 
-        framework:
-            validation:
-                auto_mapping:
-                    # an empty array means that all entities that belong to that
-                    # namespace will add automatic validation
-                    'App\Entity\': []
-                    'Foo\': ['Foo\Some\Entity', 'Foo\Another\Entity']
+        # config/services.yaml
+        parameters:
+            session.storage.options:
+                cache_limiter: 0
 
     .. code-block:: xml
 
-        <!-- config/packages/framework.xml -->
+        <!-- config/services.xml -->
         <?xml version="1.0" encoding="UTF-8" ?>
         <container xmlns="http://symfony.com/schema/dic/services"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-            xmlns:framework="http://symfony.com/schema/dic/symfony"
             xsi:schemaLocation="http://symfony.com/schema/dic/services
-                https://symfony.com/schema/dic/services/services-1.0.xsd
-                http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
-
-            <framework:config>
-                <framework:validation>
-                    <framework:auto-mapping>
-                        <framework:service namespace="App\Entity\"/>
+                https://symfony.com/schema/dic/services/services-1.0.xsd">
 
-                        <framework:service namespace="Foo\">Foo\Some\Entity</framework:service>
-                        <framework:service namespace="Foo\">Foo\Another\Entity</framework:service>
-                    </framework:auto-mapping>
-                </framework:validation>
-            </framework:config>
+            <parameters>
+                <parameter key="session.storage.options" type="collection">
+                    <parameter key="cache_limiter">0</parameter>
+                </parameter>
+            </parameters>
         </container>
 
     .. code-block:: php
 
-        // config/packages/framework.php
-        use Symfony\Config\FrameworkConfig;
-
-        return static function (FrameworkConfig $framework): void {
-            $framework->validation()
-                ->autoMapping()
-                    ->paths([
-                        'App\\Entity\\' => [],
-                        'Foo\\' => ['Foo\\Some\\Entity', 'Foo\\Another\\Entity'],
-                    ]);
-        };
-
-.. _reference-validation-enabled:
+        // config/services.php
+        $container->setParameter('session.storage.options', [
+            'cache_limiter' => 0,
+        ]);
 
-enabled
-.......
+Be aware that if you configure it, you'll have to set other session-related options
+as parameters as well.
 
-**type**: ``boolean`` **default**: ``true`` or ``false`` depending on your installation
+cookie_domain
+.............
 
-Whether or not to enable validation support.
+**type**: ``string``
 
-This option will automatically be set to ``true`` when one of the child
-settings is configured.
+This determines the domain to set in the session cookie.
 
-.. _reference-validation-enable_annotations:
+If not set, ``php.ini``'s `session.cookie_domain`_ directive will be relied on.
 
-enable_attributes
-.................
+cookie_httponly
+...............
 
 **type**: ``boolean`` **default**: ``true``
 
-If this option is enabled, validation constraints can be defined using `PHP attributes`_.
-
-translation_domain
-..................
-
-**type**: ``string | false`` **default**: ``validators``
-
-The translation domain that is used when translating validation constraint
-error messages. Use false to disable translations.
-
-.. _reference-validation-not-compromised-password:
-
-not_compromised_password
-........................
-
-The :doc:`NotCompromisedPassword </reference/constraints/NotCompromisedPassword>`
-constraint makes HTTP requests to a public API to check if the given password
-has been compromised in a data breach.
-
-.. _reference-validation-not-compromised-password-enabled:
+This determines whether cookies should only be accessible through the HTTP
+protocol. This means that the cookie won't be accessible by scripting
+languages, such as JavaScript. This setting can effectively help to reduce
+identity theft through :ref:`XSS attacks <xss-attacks>`.
 
-enabled
-"""""""
+cookie_lifetime
+...............
 
-**type**: ``boolean`` **default**: ``true``
+**type**: ``integer``
 
-If you set this option to ``false``, no HTTP requests will be made and the given
-password will be considered valid. This is useful when you don't want or can't
-make HTTP requests, such as in ``dev`` and ``test`` environments or in
-continuous integration servers.
+This determines the lifetime of the session - in seconds.
+Setting this value to ``0`` means the cookie is valid for
+the length of the browser session.
 
-endpoint
-""""""""
+If not set, ``php.ini``'s `session.cookie_lifetime`_ directive will be relied on.
 
-**type**: ``string`` **default**: ``null``
+cookie_path
+...........
 
-By default, the :doc:`NotCompromisedPassword </reference/constraints/NotCompromisedPassword>`
-constraint uses the public API provided by `haveibeenpwned.com`_. This option
-allows to define a different, but compatible, API endpoint to make the password
-checks. It's useful for example when the Symfony application is run in an
-intranet without public access to the internet.
+**type**: ``string``
 
-static_method
-.............
+This determines the path to set in the session cookie.
 
-**type**: ``string | array`` **default**: ``['loadValidatorMetadata']``
+If not set, ``php.ini``'s `session.cookie_path`_ directive will be relied on.
 
-Defines the name of the static method which is called to load the validation
-metadata of the class. You can define an array of strings with the names of
-several methods. In that case, all of them will be called in that order to load
-the metadata.
+cookie_samesite
+...............
 
-.. _reference-validation-password-strength:
+**type**: ``string`` or ``null`` **default**: ``null``
 
-password_strength
-.................
+It controls the way cookies are sent when the HTTP request did not originate
+from the same domain that is associated with the cookies. Setting this option is
+recommended to mitigate `CSRF security attacks`_.
 
-The :doc:`PasswordStrength </reference/constraints/PasswordStrength>`
-constraint verifies the submitted string entropy is matching the minimum entropy score.
+By default, browsers send all cookies related to the domain of the HTTP request.
+This may be a problem for example when you visit a forum and some malicious
+comment includes a link like ``https://some-bank.com/?send_money_to=attacker&amount=1000``.
+If you were previously logged into your bank website, the browser will send all
+those cookies when making that HTTP request.
 
-.. _reference-validation-email_validation_mode:
+The possible values for this option are:
 
-email_validation_mode
-.....................
+* ``null``, use ``php.ini``'s `session.cookie_samesite`_ directive.
+* ``'none'`` (or the ``Symfony\Component\HttpFoundation\Cookie::SAMESITE_NONE`` constant), use it to allow
+  sending of cookies when the HTTP request originated from a different domain
+  (previously this was the default behavior of null, but in newer browsers ``'lax'``
+  would be applied when the header has not been set)
+* ``'strict'`` (or the ``Cookie::SAMESITE_STRICT`` constant), use it to never
+  send any cookie when the HTTP request did not originate from the same domain.
+* ``'lax'`` (or the ``Cookie::SAMESITE_LAX`` constant), use it to allow sending
+  cookies when the request originated from a different domain, but only when the
+  user consciously made the request (by clicking a link or submitting a form
+  with the ``GET`` method).
 
-**type**: ``string`` **default**: ``html5``
+cookie_secure
+.............
 
-Sets the default value for the
-:ref:`"mode" option of the Email validator <reference-constraint-email-mode>`.
+**type**: ``boolean`` or ``'auto'``
 
-.. _reference-validation-mapping:
+This determines whether cookies should only be sent over secure connections. In
+addition to ``true`` and ``false``, there's a special ``'auto'`` value that
+means ``true`` for HTTPS requests and ``false`` for HTTP requests.
 
-mapping
-.......
+If not set, ``php.ini``'s `session.cookie_secure`_ directive will be relied on.
 
-.. _reference-validation-mapping-paths:
+.. _reference-session-enabled:
 
-paths
-"""""
+enabled
+.......
 
-**type**: ``array`` **default**: ``['config/validation/']``
+**type**: ``boolean`` **default**: ``true``
 
-This option allows to define an array of paths with files or directories where
-the component will look for additional validation files:
+Whether to enable the session support in the framework.
 
 .. configuration-block::
 
@@ -2816,10 +3092,8 @@ the component will look for additional validation files:
 
         # config/packages/framework.yaml
         framework:
-            validation:
-                mapping:
-                    paths:
-                        - "%kernel.project_dir%/config/validation/"
+            session:
+                enabled: true
 
     .. code-block:: xml
 
@@ -2833,11 +3107,7 @@ the component will look for additional validation files:
                 http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
 
             <framework:config>
-                <framework:validation>
-                    <framework:mapping>
-                        <framework:path>%kernel.project_dir%/config/validation/</framework:path>
-                    </framework:mapping>
-                </framework:validation>
+                <framework:session enabled="true"/>
             </framework:config>
         </container>
 
@@ -2847,148 +3117,163 @@ the component will look for additional validation files:
         use Symfony\Config\FrameworkConfig;
 
         return static function (FrameworkConfig $framework): void {
-            $framework->validation()
-                ->mapping()
-                    ->paths(['%kernel.project_dir%/config/validation/']);
+            $framework->session()
+                ->enabled(true);
         };
 
-annotations
-~~~~~~~~~~~
-
-.. _reference-annotations-cache:
-
-cache
-.....
+gc_divisor
+..........
 
-**type**: ``string`` **default**: ``php_array``
+**type**: ``integer``
 
-This option can be one of the following values:
+See `gc_probability`_.
 
-php_array
-    Use a PHP array to cache annotations in memory
-file
-    Use the filesystem to cache annotations
-none
-    Disable the caching of annotations
+If not set, ``php.ini``'s `session.gc_divisor`_ directive will be relied on.
 
-file_cache_dir
+gc_maxlifetime
 ..............
 
-**type**: ``string`` **default**: ``%kernel.cache_dir%/annotations``
-
-The directory to store cache files for annotations, in case
-``annotations.cache`` is set to ``'file'``.
-
-debug
-.....
-
-**type**: ``boolean`` **default**: ``%kernel.debug%``
-
-Whether to enable debug mode for caching. If enabled, the cache will
-automatically update when the original file is changed (both with code and
-annotation changes). For performance reasons, it is recommended to disable
-debug mode in production, which will happen automatically if you use the
-default value.
-
-.. _configuration-framework-serializer:
-
-serializer
-~~~~~~~~~~
+**type**: ``integer``
 
-.. _reference-serializer-enabled:
+This determines the number of seconds after which data will be seen as "garbage"
+and potentially cleaned up. Garbage collection may occur during session
+start and depends on `gc_divisor`_ and `gc_probability`_.
 
-enabled
-.......
+If not set, ``php.ini``'s `session.gc_maxlifetime`_ directive will be relied on.
 
-**type**: ``boolean`` **default**: ``true`` or ``false`` depending on your installation
+gc_probability
+..............
 
-Whether to enable the ``serializer`` service or not in the service container.
+**type**: ``integer``
 
-.. _reference-serializer-enable_annotations:
+This defines the probability that the garbage collector (GC) process is
+started on every session initialization. The probability is calculated by
+using ``gc_probability`` / ``gc_divisor``, e.g. 1/100 means there is a 1%
+chance that the GC process will start on each request.
 
-enable_attributes
-.................
+If not set, Symfony will use the value of the `session.gc_probability`_ directive
+in the ``php.ini`` configuration file.
 
-**type**: ``boolean`` **default**: ``true``
+.. versionadded:: 7.2
 
-Enables support for `PHP attributes`_ in the serializer component.
+    Relying on ``php.ini``'s directive as default for ``gc_probability`` was
+    introduced in Symfony 7.2.
 
-.. seealso::
+.. _config-framework-session-handler-id:
 
-    See :ref:`the reference <reference-attributes-serializer>` for a list of supported annotations.
+handler_id
+..........
 
-.. _reference-serializer-name_converter:
+**type**: ``string`` | ``null`` **default**: ``null``
 
-name_converter
-..............
+If ``framework.session.save_path`` is not set, the default value of this option
+is ``null``, which means to use the session handler configured in php.ini. If the
+``framework.session.save_path`` option is set, then Symfony stores sessions using
+the native file session handler.
 
-**type**: ``string``
+It is possible to :ref:`store sessions in a database <session-database>`,
+and also to configure the session handler with a DSN:
 
-The name converter to use.
-The :class:`Symfony\\Component\\Serializer\\NameConverter\\CamelCaseToSnakeCaseNameConverter`
-name converter can enabled by using the ``serializer.name_converter.camel_case_to_snake_case``
-value.
+.. configuration-block::
 
-.. seealso::
+    .. code-block:: yaml
 
-    For more information, see :ref:`serializer-name-conversion`.
+        # config/packages/framework.yaml
+        framework:
+            session:
+                # a few possible examples
+                handler_id: 'redis://localhost'
+                handler_id: '%env(REDIS_URL)%'
+                handler_id: '%env(DATABASE_URL)%'
+                handler_id: 'file://%kernel.project_dir%/var/sessions'
 
-.. _reference-serializer-circular_reference_handler:
+    .. code-block:: xml
 
-circular_reference_handler
-..........................
+        <!-- config/packages/framework.xml -->
+        <?xml version="1.0" encoding="UTF-8" ?>
+        <container xmlns="http://symfony.com/schema/dic/services"
+            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+            xmlns:framework="http://symfony.com/schema/dic/symfony"
+            xsi:schemaLocation="http://symfony.com/schema/dic/services
+                https://symfony.com/schema/dic/services/services-1.0.xsd
+                http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
+            <framework:config>
+                <!-- a few possible examples -->
+                <framework:session enabled="true"
+                    handler-id="redis://localhost"
+                    handler-id="%env(REDIS_URL)%"
+                    handler-id="%env(DATABASE_URL)%"
+                    handler-id="file://%kernel.project_dir%/var/sessions"/>
+            </framework:config>
+        </container>
 
-**type** ``string``
+    .. code-block:: php
 
-The service id that is used as the circular reference handler of the default
-serializer. The service has to implement the magic ``__invoke($object)``
-method.
+        // config/packages/framework.php
+        use Symfony\Config\FrameworkConfig;
+        use function Symfony\Component\DependencyInjection\Loader\Configurator\env;
 
-.. seealso::
+        return static function (FrameworkConfig $framework): void {
+            // ...
 
-    For more information, see
-    :ref:`component-serializer-handling-circular-references`.
+            $framework->session()
+                // a few possible examples
+                ->handlerId('redis://localhost')
+                ->handlerId(env('REDIS_URL'))
+                ->handlerId(env('DATABASE_URL'))
+                ->handlerId('file://%kernel.project_dir%/var/sessions');
+        };
 
-.. _reference-serializer-mapping:
+.. note::
 
-mapping
-.......
+    Supported DSN protocols are the following:
 
-.. _reference-serializer-mapping-paths:
+    * ``file``
+    * ``redis``
+    * ``rediss`` (Redis over TLS)
+    * ``memcached`` (requires :doc:`symfony/cache </components/cache>`)
+    * ``pdo_oci`` (requires :doc:`doctrine/dbal </doctrine/dbal>`)
+    * ``mssql``
+    * ``mysql``
+    * ``mysql2``
+    * ``pgsql``
+    * ``postgres``
+    * ``postgresql``
+    * ``sqlsrv``
+    * ``sqlite``
+    * ``sqlite3``
 
-paths
-"""""
+.. _reference-session-metadata-update-threshold:
 
-**type**: ``array`` **default**: ``[]``
+metadata_update_threshold
+.........................
 
-This option allows to define an array of paths with files or directories where
-the component will look for additional serialization files.
+**type**: ``integer`` **default**: ``0``
 
-default_context
-...............
+This is how many seconds to wait between updating/writing the session metadata.
+This can be useful if, for some reason, you want to limit the frequency at which
+the session persists, instead of doing that on every request.
 
-**type**: ``array`` **default**: ``[]``
+.. _name:
 
-A map with default context options that will be used with each ``serialize`` and ``deserialize``
-call. This can be used for example to set the json encoding behavior by setting ``json_encode_options``
-to a `json_encode flags bitmask`_.
+name
+....
 
-You can inspect the :ref:`serializer context builders <serializer-using-context-builders>`
-to discover the available settings.
+**type**: ``string``
 
-php_errors
-~~~~~~~~~~
+This specifies the name of the session cookie.
 
-log
-...
+If not set, ``php.ini``'s `session.name`_ directive will be relied on.
 
-**type**: ``boolean`` | ``int`` **default**: ``true``
+save_path
+.........
 
-Use the application logger instead of the PHP logger for logging PHP errors.
-When an integer value is used, it also sets the log level. Those integer
-values must be the same used in the `error_reporting PHP option`_.
+**type**: ``string`` | ``null`` **default**: ``%kernel.cache_dir%/sessions``
 
-This option also accepts a map of PHP errors to log levels:
+This determines the argument to be passed to the save handler. If you choose
+the default file handler, this is the path where the session files are created.
+
+If ``null``, ``php.ini``'s `session.save_path`_ directive will be relied on:
 
 .. configuration-block::
 
@@ -2996,23 +3281,8 @@ This option also accepts a map of PHP errors to log levels:
 
         # config/packages/framework.yaml
         framework:
-            php_errors:
-                log:
-                    !php/const \E_DEPRECATED: !php/const Psr\Log\LogLevel::ERROR
-                    !php/const \E_USER_DEPRECATED: !php/const Psr\Log\LogLevel::ERROR
-                    !php/const \E_NOTICE: !php/const Psr\Log\LogLevel::ERROR
-                    !php/const \E_USER_NOTICE: !php/const Psr\Log\LogLevel::ERROR
-                    !php/const \E_STRICT: !php/const Psr\Log\LogLevel::ERROR
-                    !php/const \E_WARNING: !php/const Psr\Log\LogLevel::ERROR
-                    !php/const \E_USER_WARNING: !php/const Psr\Log\LogLevel::ERROR
-                    !php/const \E_COMPILE_WARNING: !php/const Psr\Log\LogLevel::ERROR
-                    !php/const \E_CORE_WARNING: !php/const Psr\Log\LogLevel::ERROR
-                    !php/const \E_USER_ERROR: !php/const Psr\Log\LogLevel::CRITICAL
-                    !php/const \E_RECOVERABLE_ERROR: !php/const Psr\Log\LogLevel::CRITICAL
-                    !php/const \E_COMPILE_ERROR: !php/const Psr\Log\LogLevel::CRITICAL
-                    !php/const \E_PARSE: !php/const Psr\Log\LogLevel::CRITICAL
-                    !php/const \E_ERROR: !php/const Psr\Log\LogLevel::CRITICAL
-                    !php/const \E_CORE_ERROR: !php/const Psr\Log\LogLevel::CRITICAL
+            session:
+                save_path: ~
 
     .. code-block:: xml
 
@@ -3026,322 +3296,267 @@ This option also accepts a map of PHP errors to log levels:
                 http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
 
             <framework:config>
-                <!-- in XML configuration you cannot use PHP constants as the value of
-                     the 'type' attribute, which makes this format way less readable.
-                     Consider using YAML or PHP for this configuration -->
-                <framework:log type="8" logLevel="error"/>
-                <framework:log type="2" logLevel="error"/>
-                <!-- ... -->
+                <framework:session save-path="null"/>
             </framework:config>
         </container>
 
     .. code-block:: php
 
         // config/packages/framework.php
-        use Psr\Log\LogLevel;
         use Symfony\Config\FrameworkConfig;
 
         return static function (FrameworkConfig $framework): void {
-            $framework->phpErrors()->log(\E_DEPRECATED, LogLevel::ERROR);
-            $framework->phpErrors()->log(\E_USER_DEPRECATED, LogLevel::ERROR);
-            // ...
+            $framework->session()
+                ->savePath(null);
         };
 
-throw
-.....
-
-**type**: ``boolean`` **default**: ``%kernel.debug%``
-
-Throw PHP errors as ``\ErrorException`` instances. The parameter
-``debug.error_handler.throw_at`` controls the threshold.
-
-.. _reference-cache:
-
-cache
-~~~~~
-
-.. _reference-cache-app:
-
-app
-...
-
-**type**: ``string`` **default**: ``cache.adapter.filesystem``
+sid_bits_per_character
+......................
 
-The cache adapter used by the ``cache.app`` service. The FrameworkBundle
-ships with multiple adapters: ``cache.adapter.apcu``, ``cache.adapter.system``,
-``cache.adapter.filesystem``, ``cache.adapter.psr6``, ``cache.adapter.redis``,
-``cache.adapter.memcached``, ``cache.adapter.pdo`` and
-``cache.adapter.doctrine_dbal``.
+**type**: ``integer``
 
-There's also a special adapter called ``cache.adapter.array`` which stores
-contents in memory using a PHP array and it's used to disable caching (mostly on
-the ``dev`` environment).
+This determines the number of bits in the encoded session ID character. The possible
+values are ``4`` (0-9, a-f), ``5`` (0-9, a-v), and ``6`` (0-9, a-z, A-Z, "-", ",").
+The more bits results in stronger session ID. ``5`` is recommended value for
+most environments.
 
-.. tip::
+If not set, ``php.ini``'s `session.sid_bits_per_character`_ directive will be relied on.
 
-    It might be tough to understand at the beginning, so to avoid confusion
-    remember that all pools perform the same actions but on different medium
-    given the adapter they are based on. Internally, a pool wraps the definition
-    of an adapter.
+.. deprecated:: 7.2
 
-.. _reference-cache-system:
+    The ``sid_bits_per_character`` option was deprecated in Symfony 7.2. No alternative
+    is provided as PHP 8.4 has deprecated the related option.
 
-system
-......
+sid_length
+..........
 
-**type**: ``string`` **default**: ``cache.adapter.system``
+**type**: ``integer``
 
-The cache adapter used by the ``cache.system`` service. It supports the same
-adapters available for the ``cache.app`` service.
+This determines the length of session ID string, which can be an integer between
+``22`` and ``256`` (both inclusive), ``32`` being the recommended value. Longer
+session IDs are harder to guess.
 
-directory
-.........
+If not set, ``php.ini``'s `session.sid_length`_ directive will be relied on.
 
-**type**: ``string`` **default**: ``%kernel.cache_dir%/pools``
+.. deprecated:: 7.2
 
-The path to the cache directory used by services inheriting from the
-``cache.adapter.filesystem`` adapter (including ``cache.app``).
+    The ``sid_length`` option was deprecated in Symfony 7.2. No alternative is
+    provided as PHP 8.4 has deprecated the related option.
 
-default_doctrine_provider
-.........................
+.. _storage_id:
 
-**type**: ``string``
+storage_factory_id
+..................
 
-The service name to use as your default Doctrine provider. The provider is
-available as the ``cache.default_doctrine_provider`` service.
+**type**: ``string`` **default**: ``session.storage.factory.native``
 
-default_psr6_provider
-.....................
+The service ID used for creating the ``SessionStorageInterface`` that stores
+the session. This service is available in the Symfony application via the
+``session.storage.factory`` service alias. The class has to implement
+:class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\SessionStorageFactoryInterface`.
+To see a list of all available storages, run:
 
-**type**: ``string``
+.. code-block:: terminal
 
-The service name to use as your default PSR-6 provider. It is available as
-the ``cache.default_psr6_provider`` service.
+    $ php bin/console debug:container session.storage.factory.
 
-default_redis_provider
-......................
+use_cookies
+...........
 
-**type**: ``string`` **default**: ``redis://localhost``
+**type**: ``boolean``
 
-The DSN to use by the Redis provider. The provider is available as the ``cache.default_redis_provider``
-service.
+This specifies if the session ID is stored on the client side using cookies or
+not.
 
-default_memcached_provider
-..........................
+If not set, ``php.ini``'s `session.use_cookies`_ directive will be relied on.
 
-**type**: ``string`` **default**: ``memcached://localhost``
+ssi
+~~~
 
-The DSN to use by the Memcached provider. The provider is available as the ``cache.default_memcached_provider``
-service.
+enabled
+.......
 
-default_pdo_provider
-....................
+**type**: ``boolean`` **default**: ``false``
 
-**type**: ``string`` **default**: ``doctrine.dbal.default_connection``
+Whether to enable or not SSI support in your application.
 
-The service id of the database connection, which should be either a PDO or a
-Doctrine DBAL instance. The provider is available as the ``cache.default_pdo_provider``
-service.
+.. _reference-framework-test:
 
-pools
-.....
+test
+~~~~
 
-**type**: ``array``
+**type**: ``boolean``
 
-A list of cache pools to be created by the framework extension.
+If this configuration setting is present (and not ``false``), then the services
+related to testing your application (e.g. ``test.client``) are loaded. This
+setting should be present in your ``test`` environment (usually via
+``config/packages/test/framework.yaml``).
 
 .. seealso::
 
-    For more information about how pools work, see :ref:`cache pools <component-cache-cache-pools>`.
-
-To configure a Redis cache pool with a default lifetime of 1 hour, do the following:
-
-.. configuration-block::
-
-    .. code-block:: yaml
-
-        # config/packages/framework.yaml
-        framework:
-            cache:
-                pools:
-                    cache.mycache:
-                        adapter: cache.adapter.redis
-                        default_lifetime: 3600
-
-    .. code-block:: xml
-
-        <!-- config/packages/framework.xml -->
-        <?xml version="1.0" encoding="UTF-8" ?>
-        <container xmlns="http://symfony.com/schema/dic/services"
-            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-            xmlns:framework="http://symfony.com/schema/dic/symfony"
-            xsi:schemaLocation="http://symfony.com/schema/dic/services
-                https://symfony.com/schema/dic/services/services-1.0.xsd
-                http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
+    For more information, see :doc:`/testing`.
 
-            <framework:config>
-                <framework:cache>
-                    <framework:pool
-                        name="cache.mycache"
-                        adapter="cache.adapter.redis"
-                        default-lifetime="3600"
-                    />
-                </framework:cache>
-                <!-- ... -->
-            </framework:config>
-        </container>
+translator
+~~~~~~~~~~
 
-    .. code-block:: php
+cache_dir
+.........
 
-        // config/packages/framework.php
-        use Symfony\Config\FrameworkConfig;
+**type**: ``string`` | ``null`` **default**: ``%kernel.cache_dir%/translations``
 
-        return static function (FrameworkConfig $framework): void {
-            $framework->cache()
-                ->pool('cache.mycache')
-                    ->adapters(['cache.adapter.redis'])
-                    ->defaultLifetime(3600);
-        };
+Defines the directory where the translation cache is stored. Use ``null`` to
+disable this cache.
 
-.. _reference-cache-pools-name:
+.. _reference-translator-default_path:
 
-name
-""""
+default_path
+............
 
-**type**: ``prototype``
+**type**: ``string`` **default**: ``%kernel.project_dir%/translations``
 
-Name of the pool you want to create.
+This option allows to define the path where the application translations files
+are stored.
 
-.. note::
+.. _reference-translator-enabled:
 
-    Your pool name must differ from ``cache.app`` or ``cache.system``.
+enabled
+.......
 
-adapter
-"""""""
+**type**: ``boolean`` **default**: ``true`` or ``false`` depending on your installation
 
-**type**: ``string`` **default**: ``cache.app``
+Whether or not to enable the ``translator`` service in the service container.
 
-The service name of the adapter to use. You can specify one of the default
-services that follow the pattern ``cache.adapter.[type]``. Alternatively you
-can specify another cache pool as base, which will make this pool inherit the
-settings from the base pool as defaults.
+.. _fallback:
 
-.. note::
+fallbacks
+.........
 
-    Your service needs to implement the ``Psr\Cache\CacheItemPoolInterface`` interface.
+**type**: ``string|array`` **default**: value of `default_locale`_
 
-public
-""""""
+This option is used when the translation key for the current locale wasn't
+found.
 
-**type**: ``boolean`` **default**: ``false``
+.. seealso::
 
-Whether your service should be public or not.
+    For more details, see :doc:`/translation`.
 
-tags
-""""
+.. _reference-framework-translator-formatter:
 
-**type**: ``boolean`` | ``string`` **default**: ``null``
+formatter
+.........
 
-Whether your service should be able to handle tags or not.
-Can also be the service id of another cache pool where tags will be stored.
+**type**: ``string`` **default**: ``translator.formatter.default``
 
-default_lifetime
-""""""""""""""""
+The ID of the service used to format translation messages. The service class
+must implement the :class:`Symfony\\Component\\Translation\\Formatter\\MessageFormatterInterface`.
 
-**type**: ``integer`` | ``string``
+.. _reference-framework-translator-logging:
 
-Default lifetime of your cache items. Give an integer value to set the default
-lifetime in seconds. A string value could be ISO 8601 time interval, like ``"PT5M"``
-or a PHP date expression that is accepted by ``strtotime()``, like ``"5 minutes"``.
+logging
+.......
 
-If no value is provided, the cache adapter will fallback to the default value on
-the actual cache storage.
+**default**: ``true`` when the debug mode is enabled, ``false`` otherwise.
 
-provider
-""""""""
+When ``true``, a log entry is made whenever the translator cannot find a translation
+for a given key. The logs are made to the ``translation`` channel at the
+``debug`` level for keys where there is a translation in the fallback
+locale, and the ``warning`` level if there is no translation to use at all.
 
-**type**: ``string``
+.. _reference-translator-paths:
 
-Overwrite the default service name or DSN respectively, if you do not want to
-use what is configured as ``default_X_provider`` under ``cache``. See the
-description of the default provider setting above for information on how to
-specify your specific provider.
+paths
+.....
 
-clearer
-"""""""
+**type**: ``array`` **default**: ``[]``
 
-**type**: ``string``
+This option allows to define an array of paths where the component will look
+for translation files. The later a path is added, the more priority it has
+(translations from later paths overwrite earlier ones). Translations from the
+:ref:`default_path <reference-translator-default_path>` have more priority than
+translations from all these paths.
 
-The cache clearer used to clear your PSR-6 cache.
+.. _reference-translator-providers:
 
-.. seealso::
+providers
+.........
 
-    For more information, see :class:`Symfony\\Component\\HttpKernel\\CacheClearer\\Psr6CacheClearer`.
+**type**: ``array`` **default**: ``[]``
 
-.. _reference-cache-prefix-seed:
+This option enables and configures :ref:`translation providers <translation-providers>`
+to push and pull your translations to/from third party translation services.
 
-prefix_seed
-...........
+trust_x_sendfile_type_header
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-**type**: ``string`` **default**: ``_%kernel.project_dir%.%kernel.container_class%``
+**type**: ``boolean`` **default**: ``%env(bool:default::SYMFONY_TRUST_X_SENDFILE_TYPE_HEADER)%``
 
-This value is used as part of the "namespace" generated for the
-cache item keys. A common practice is to use the unique name of the application
-(e.g. ``symfony.com``) because that prevents naming collisions when deploying
-multiple applications into the same path (on different servers) that share the
-same cache backend.
+.. versionadded:: 7.2
 
-It's also useful when using `blue/green deployment`_ strategies and more
-generally, when you need to abstract out the actual deployment directory (for
-example, when warming caches offline).
+    In Symfony 7.2, the default value of this option was changed from ``false`` to the
+    value stored in the ``SYMFONY_TRUST_X_SENDFILE_TYPE_HEADER`` environment variable.
 
-.. note::
+``X-Sendfile`` is a special HTTP header that tells web servers to replace the
+response contents by the file that is defined in that header. This improves
+performance because files are no longer served by your application but directly
+by the web server.
 
-    The ``prefix_seed`` option is used at compile time. This means
-    that any change made to this value after container's compilation
-    will have no effect.
+This configuration option determines whether to trust ``x-sendfile`` header for
+BinaryFileResponse. If enabled, Symfony calls the
+:method:`BinaryFileResponse::trustXSendfileTypeHeader <Symfony\\Component\\HttpFoundation\\BinaryFileResponse::trustXSendfileTypeHeader>`
+method automatically. It becomes the service container parameter named
+``kernel.trust_x_sendfile_type_header``.
 
-.. _reference-lock:
+.. _reference-framework-trusted-headers:
 
-lock
-~~~~
+trusted_headers
+~~~~~~~~~~~~~~~
 
-**type**: ``string`` | ``array``
+The ``trusted_headers`` option is needed to configure which client information
+should be trusted (e.g. their host) when running Symfony behind a load balancer
+or a reverse proxy. See :doc:`/deployment/proxies`.
 
-The default lock adapter. If not defined, the value is set to ``semaphore`` when
-available, or to ``flock`` otherwise. Store's DSN are also allowed.
+.. _configuration-framework-trusted-hosts:
 
-.. _reference-lock-enabled:
+trusted_hosts
+~~~~~~~~~~~~~
 
-enabled
-.......
+**type**: ``array`` | ``string`` **default**: ``['%env(default::SYMFONY_TRUSTED_HOSTS)%']``
 
-**type**: ``boolean`` **default**: ``true``
+.. versionadded:: 7.2
 
-Whether to enable the support for lock or not. This setting is
-automatically set to ``true`` when one of the child settings is configured.
+    In Symfony 7.2, the default value of this option was changed from ``[]`` to the
+    value stored in the ``SYMFONY_TRUSTED_HOSTS`` environment variable.
 
-.. _reference-lock-resources:
+A lot of different attacks have been discovered relying on inconsistencies
+in handling the ``Host`` header by various software (web servers, reverse
+proxies, web frameworks, etc.). Basically, every time the framework is
+generating an absolute URL (when sending an email to reset a password for
+instance), the host might have been manipulated by an attacker.
 
-resources
-.........
+.. seealso::
 
-**type**: ``array``
+    You can read `HTTP Host header attacks`_ for more information about
+    these kinds of attacks.
 
-A map of lock stores to be created by the framework extension, with
-the name as key and DSN as value:
+The Symfony :method:`Request::getHost() <Symfony\\Component\\HttpFoundation\\Request::getHost>`
+method might be vulnerable to some of these attacks because it depends on
+the configuration of your web server. One simple solution to avoid these
+attacks is to configure a list of hosts that your Symfony application can respond
+to. That's the purpose of this ``trusted_hosts`` option. If the incoming
+request's hostname doesn't match one of the regular expressions in this list,
+the application won't respond and the user will receive a 400 response.
 
 .. configuration-block::
 
     .. code-block:: yaml
 
-        # config/packages/lock.yaml
+        # config/packages/framework.yaml
         framework:
-            lock: '%env(LOCK_DSN)%'
+            trusted_hosts:  ['^example\.com$', '^example\.org$']
 
     .. code-block:: xml
 
-        <!-- config/packages/lock.xml -->
+        <!-- config/packages/framework.xml -->
         <?xml version="1.0" encoding="UTF-8" ?>
         <container xmlns="http://symfony.com/schema/dic/services"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
@@ -3351,73 +3566,76 @@ the name as key and DSN as value:
                 http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
 
             <framework:config>
-                <framework:lock>
-                    <framework:resource name="default">%env(LOCK_DSN)%</framework:resource>
-                </framework:lock>
+                <framework:trusted-host>^example\.com$</framework:trusted-host>
+                <framework:trusted-host>^example\.org$</framework:trusted-host>
+                <!-- ... -->
             </framework:config>
         </container>
 
     .. code-block:: php
 
-        // config/packages/lock.php
+        // config/packages/framework.php
         use Symfony\Config\FrameworkConfig;
 
         return static function (FrameworkConfig $framework): void {
-            $framework->lock()
-                ->resource('default', [env('LOCK_DSN')]);
+            $framework->trustedHosts(['^example\.com$', '^example\.org$']);
         };
 
-.. seealso::
-
-    For more details, see :doc:`/lock`.
-
-.. _reference-lock-resources-name:
+Hosts can also be configured to respond to any subdomain, via
+``^(.+\.)?example\.com$`` for instance.
 
-name
-""""
+In addition, you can also set the trusted hosts in the front controller
+using the ``Request::setTrustedHosts()`` method::
 
-**type**: ``prototype``
+    // public/index.php
+    Request::setTrustedHosts(['^(.+\.)?example\.com$', '^(.+\.)?example\.org$']);
 
-Name of the lock you want to create.
+The default value for this option is an empty array, meaning that the application
+can respond to any given host.
 
-semaphore
-~~~~~~~~~
+.. seealso::
 
-**type**: ``string`` | ``array``
+    Read more about this in the `Security Advisory Blog post`_.
 
-The default semaphore adapter. Store's DSN are also allowed.
+.. _reference-framework-trusted-proxies:
 
-.. _reference-semaphore-enabled:
+trusted_proxies
+~~~~~~~~~~~~~~~
 
-enabled
-.......
+The ``trusted_proxies`` option is needed to get precise information about the
+client (e.g. their IP address) when running Symfony behind a load balancer or a
+reverse proxy. See :doc:`/deployment/proxies`.
 
-**type**: ``boolean`` **default**: ``true``
+.. _reference-validation:
 
-Whether to enable the support for semaphore or not. This setting is
-automatically set to ``true`` when one of the child settings is configured.
+validation
+~~~~~~~~~~
 
-.. _reference-semaphore-resources:
+.. _reference-validation-auto-mapping:
 
-resources
-.........
+auto_mapping
+............
 
-**type**: ``array``
+**type**: ``array`` **default**: ``[]``
 
-A map of semaphore stores to be created by the framework extension, with
-the name as key and DSN as value:
+Defines the Doctrine entities that will be introspected to add
+:ref:`automatic validation constraints <automatic_object_validation>` to them:
 
 .. configuration-block::
 
     .. code-block:: yaml
 
-        # config/packages/semaphore.yaml
         framework:
-            semaphore: '%env(SEMAPHORE_DSN)%'
+            validation:
+                auto_mapping:
+                    # an empty array means that all entities that belong to that
+                    # namespace will add automatic validation
+                    'App\Entity\': []
+                    'Foo\': ['Foo\Some\Entity', 'Foo\Another\Entity']
 
     .. code-block:: xml
 
-        <!-- config/packages/semaphore.xml -->
+        <!-- config/packages/framework.xml -->
         <?xml version="1.0" encoding="UTF-8" ?>
         <container xmlns="http://symfony.com/schema/dic/services"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
@@ -3427,93 +3645,107 @@ the name as key and DSN as value:
                 http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
 
             <framework:config>
-                <framework:semaphore>
-                    <framework:resource name="default">%env(SEMAPHORE_DSN)%</framework:resource>
-                </framework:semaphore>
+                <framework:validation>
+                    <framework:auto-mapping>
+                        <framework:service namespace="App\Entity\"/>
+
+                        <framework:service namespace="Foo\">Foo\Some\Entity</framework:service>
+                        <framework:service namespace="Foo\">Foo\Another\Entity</framework:service>
+                    </framework:auto-mapping>
+                </framework:validation>
             </framework:config>
         </container>
 
     .. code-block:: php
 
-        // config/packages/semaphore.php
+        // config/packages/framework.php
         use Symfony\Config\FrameworkConfig;
 
         return static function (FrameworkConfig $framework): void {
-            $framework->semaphore()
-                ->resource('default', ['%env(SEMAPHORE_DSN)%']);
+            $framework->validation()
+                ->autoMapping()
+                    ->paths([
+                        'App\\Entity\\' => [],
+                        'Foo\\' => ['Foo\\Some\\Entity', 'Foo\\Another\\Entity'],
+                    ]);
         };
 
-.. _reference-semaphore-resources-name:
+.. _reference-validation-disable_translation:
 
-name
-""""
+disable_translation
+...................
 
-**type**: ``prototype``
+**type**: ``boolean`` **default**: ``false``
 
-Name of the semaphore you want to create.
+Validation error messages are automatically translated to the current application
+locale. Set this option to ``true`` to disable translation of validation messages.
+This is useful to avoid "missing translation" errors in applications that use
+only a single language.
 
-mailer
-~~~~~~
+.. versionadded:: 7.3
 
-.. _mailer-dsn:
+    The ``disable_translation`` option was introduced in Symfony 7.3.
 
-dsn
-...
+.. _reference-validation-email_validation_mode:
 
-**type**: ``string`` **default**: ``null``
+email_validation_mode
+.....................
 
-The DSN used by the mailer. When several DSN may be used, use
-``transports`` option (see below) instead.
+**type**: ``string`` **default**: ``html5``
+
+Sets the default value for the
+:ref:`"mode" option of the Email validator <reference-constraint-email-mode>`.
+
+.. _reference-validation-enable_annotations:
 
-transports
-..........
+enable_attributes
+.................
 
-**type**: ``array``
+**type**: ``boolean`` **default**: ``true``
 
-A :ref:`list of DSN <multiple-email-transports>` that can be used by the
-mailer. A transport name is the key and the dsn is the value.
+If this option is enabled, validation constraints can be defined using `PHP attributes`_.
 
-message_bus
-...........
+.. _reference-validation-enabled:
 
-**type**: ``string`` **default**: ``null`` or default bus if Messenger component is installed
+enabled
+.......
 
-Service identifier of the message bus to use when using the
-:doc:`Messenger component </messenger>` (e.g. ``messenger.default_bus``).
+**type**: ``boolean`` **default**: ``true`` or ``false`` depending on your installation
 
-envelope
-........
+Whether or not to enable validation support.
 
-sender
-""""""
+This option will automatically be set to ``true`` when one of the child
+settings is configured.
 
-**type**: ``string``
+.. _reference-validation-mapping:
 
-The "envelope sender" which is used as the value of ``MAIL FROM`` during the
-`SMTP session`_. This value overrides any other sender set in the code.
+mapping
+.......
 
-recipients
-""""""""""
+.. _reference-validation-mapping-paths:
 
-**type**: ``array``
+paths
+"""""
 
-The "envelope recipient" which is used as the value of ``RCPT TO`` during the
-the `SMTP session`_. This value overrides any other recipient set in the code.
+**type**: ``array`` **default**: ``['config/validation/']``
+
+This option allows to define an array of paths with files or directories where
+the component will look for additional validation files:
 
 .. configuration-block::
 
     .. code-block:: yaml
 
-        # config/packages/mailer.yaml
+        # config/packages/framework.yaml
         framework:
-            mailer:
-                dsn: 'smtp://localhost:25'
-                envelope:
-                    recipients: ['admin@symfony.com', 'lead@symfony.com']
+            validation:
+                mapping:
+                    paths:
+                        - "%kernel.project_dir%/config/validation/"
 
     .. code-block:: xml
 
-        <!-- config/packages/mailer.xml -->
+        <!-- config/packages/framework.xml -->
         <?xml version="1.0" encoding="UTF-8" ?>
         <container xmlns="http://symfony.com/schema/dic/services"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
@@ -3521,63 +3753,77 @@ the `SMTP session`_. This value overrides any other recipient set in the code.
             xsi:schemaLocation="http://symfony.com/schema/dic/services
                 https://symfony.com/schema/dic/services/services-1.0.xsd
                 http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
+
             <framework:config>
-                <framework:mailer dsn="smtp://localhost:25">
-                    <framework:envelope>
-                        <framework:recipient>admin@symfony.com</framework:recipient>
-                        <framework:recipient>lead@symfony.com</framework:recipient>
-                    </framework:envelope>
-                </framework:mailer>
+                <framework:validation>
+                    <framework:mapping>
+                        <framework:path>%kernel.project_dir%/config/validation/</framework:path>
+                    </framework:mapping>
+                </framework:validation>
             </framework:config>
         </container>
 
     .. code-block:: php
 
-        // config/packages/mailer.php
-        namespace Symfony\Component\DependencyInjection\Loader\Configurator;
+        // config/packages/framework.php
+        use Symfony\Config\FrameworkConfig;
 
-        return static function (ContainerConfigurator $container): void {
-            $container->extension('framework', [
-                'mailer' => [
-                    'dsn' => 'smtp://localhost:25',
-                    'envelope' => [
-                        'recipients' => [
-                            'admin@symfony.com',
-                            'lead@symfony.com',
-                        ],
-                    ],
-                ],
-            ]);
+        return static function (FrameworkConfig $framework): void {
+            $framework->validation()
+                ->mapping()
+                    ->paths(['%kernel.project_dir%/config/validation/']);
         };
 
-.. _mailer-headers:
+.. _reference-validation-not-compromised-password:
 
-headers
-.......
+not_compromised_password
+........................
 
-**type**: ``array``
+The :doc:`NotCompromisedPassword </reference/constraints/NotCompromisedPassword>`
+constraint makes HTTP requests to a public API to check if the given password
+has been compromised in a data breach.
 
-Headers to add to emails. The key (``name`` attribute in xml format) is the
-header name and value the header value.
+static_method
+.............
 
-.. seealso::
+**type**: ``string | array`` **default**: ``['loadValidatorMetadata']``
 
-    For more information, see :ref:`Configuring Emails Globally <mailer-configure-email-globally>`
+Defines the name of the static method which is called to load the validation
+metadata of the class. You can define an array of strings with the names of
+several methods. In that case, all of them will be called in that order to load
+the metadata.
 
-messenger
-~~~~~~~~~
+translation_domain
+..................
+
+**type**: ``string | false`` **default**: ``validators``
+
+The translation domain that is used when translating validation constraint
+error messages. Use false to disable translations.
+
+
+.. _reference-validation-not-compromised-password-enabled:
 
 enabled
-.......
+"""""""
 
 **type**: ``boolean`` **default**: ``true``
 
-Whether to enable or not Messenger.
+If you set this option to ``false``, no HTTP requests will be made and the given
+password will be considered valid. This is useful when you don't want or can't
+make HTTP requests, such as in ``dev`` and ``test`` environments or in
+continuous integration servers.
 
-.. seealso::
+endpoint
+""""""""
 
-    For more details, see the :doc:`Messenger component </messenger>`
-    documentation.
+**type**: ``string`` **default**: ``null``
+
+By default, the :doc:`NotCompromisedPassword </reference/constraints/NotCompromisedPassword>`
+constraint uses the public API provided by `haveibeenpwned.com`_. This option
+allows to define a different, but compatible, API endpoint to make the password
+checks. It's useful for example when the Symfony application is run in an
+intranet without public access to the internet.
 
 web_link
 ~~~~~~~~
@@ -3749,125 +3995,6 @@ Defines the kind of workflow that is going to be created, which can be either
 a normal workflow or a state machine. Read :doc:`this article </workflow/workflow-and-state-machine>`
 to know their differences.
 
-.. _framework_exceptions:
-
-exceptions
-~~~~~~~~~~
-
-**type**: ``array``
-
-Defines the :ref:`log level </logging>` and HTTP status code applied to the
-exceptions that match the given exception class:
-
-.. configuration-block::
-
-    .. code-block:: yaml
-
-        # config/packages/exceptions.yaml
-        framework:
-            exceptions:
-                Symfony\Component\HttpKernel\Exception\BadRequestHttpException:
-                    log_level: 'debug'
-                    status_code: 422
-
-    .. code-block:: xml
-
-        <!-- config/packages/exceptions.xml -->
-        <?xml version="1.0" encoding="UTF-8" ?>
-        <container xmlns="http://symfony.com/schema/dic/services"
-            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-            xmlns:framework="http://symfony.com/schema/dic/symfony"
-            xsi:schemaLocation="http://symfony.com/schema/dic/services
-                https://symfony.com/schema/dic/services/services-1.0.xsd
-                http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
-
-            <framework:config>
-                <framework:exception
-                    class="Symfony\Component\HttpKernel\Exception\BadRequestHttpException"
-                    log-level="debug"
-                    status-code="422"
-                />
-                <!-- ... -->
-            </framework:config>
-        </container>
-
-    .. code-block:: php
-
-        // config/packages/exceptions.php
-        use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
-        use Symfony\Config\FrameworkConfig;
-
-        return static function (FrameworkConfig $framework): void {
-            $framework->exception(BadRequestHttpException::class)
-                ->logLevel('debug')
-                ->statusCode(422)
-            ;
-        };
-
-The order in which you configure exceptions is important because Symfony will
-use the configuration of the first exception that matches ``instanceof``:
-
-.. code-block:: yaml
-
-        # config/packages/exceptions.yaml
-        framework:
-            exceptions:
-                Exception:
-                    log_level: 'debug'
-                    status_code: 404
-                # The following configuration will never be used because \RuntimeException extends \Exception
-                RuntimeException:
-                    log_level: 'debug'
-                    status_code: 422
-
-You can map a status code and a set of headers to an exception thanks
-to the ``#[WithHttpStatus]`` attribute on the exception class::
-
-    namespace App\Exception;
-
-    use Symfony\Component\HttpKernel\Attribute\WithHttpStatus;
-
-    #[WithHttpStatus(422, [
-       'Retry-After' => 10,
-       'X-Custom-Header' => 'header-value',
-    ])]
-    class CustomException extends \Exception
-    {
-    }
-
-It is also possible to map a log level on a custom exception class using
-the ``#[WithLogLevel]`` attribute::
-
-    namespace App\Exception;
-
-    use Psr\Log\LogLevel;
-    use Symfony\Component\HttpKernel\Attribute\WithLogLevel;
-
-    #[WithLogLevel(LogLevel::WARNING)]
-    class CustomException extends \Exception
-    {
-    }
-
-The attributes can also be added to interfaces directly::
-
-    namespace App\Exception;
-
-    use Symfony\Component\HttpKernel\Attribute\WithHttpStatus;
-
-    #[WithHttpStatus(422)]
-    interface CustomExceptionInterface
-    {
-    }
-
-    class CustomException extends \Exception implements CustomExceptionInterface
-    {
-    }
-
-.. versionadded:: 7.1
-
-    Support to use ``#[WithHttpStatus]`` and ``#[WithLogLevel]`` attributes
-    on interfaces was introduced in Symfony 7.1.
-
 .. _`HTTP Host header attacks`: https://www.skeletonscribe.net/2013/05/practical-http-host-header-attacks.html
 .. _`Security Advisory Blog post`: https://symfony.com/blog/security-releases-symfony-2-0-24-2-1-12-2-2-5-and-2-3-3-released#cve-2013-4752-request-gethost-poisoning
 .. _`phpstorm-url-handler`: https://github.com/sanduhrs/phpstorm-url-handler
@@ -3890,6 +4017,7 @@ The attributes can also be added to interfaces directly::
 .. _`session.cookie_samesite`: https://www.php.net/manual/en/session.configuration.php#ini.session.cookie-samesite
 .. _`session.cookie_secure`: https://www.php.net/manual/en/session.configuration.php#ini.session.cookie-secure
 .. _`session.gc_divisor`: https://www.php.net/manual/en/session.configuration.php#ini.session.gc-divisor
+.. _`session.gc_probability`: https://www.php.net/manual/en/session.configuration.php#ini.session.gc-probability
 .. _`session.gc_maxlifetime`: https://www.php.net/manual/en/session.configuration.php#ini.session.gc-maxlifetime
 .. _`session.sid_length`: https://www.php.net/manual/en/session.configuration.php#ini.session.sid-length
 .. _`session.sid_bits_per_character`: https://www.php.net/manual/en/session.configuration.php#ini.session.sid-bits-per-character
diff --git a/reference/configuration/security.rst b/reference/configuration/security.rst
index 757dc7313cd..ef7247e330e 100644
--- a/reference/configuration/security.rst
+++ b/reference/configuration/security.rst
@@ -19,14 +19,12 @@ key in your application configuration.
     namespace and the related XSD schema is available at:
     ``https://symfony.com/schema/dic/services/services-1.0.xsd``
 
-Configuration
--------------
-
 **Basic Options**:
 
 * `access_denied_url`_
 * `erase_credentials`_
-* `hide_user_not_found`_
+* `expose_security_errors`_
+* `hide_user_not_found`_ (deprecated)
 * `session_fixation_strategy`_
 
 **Advanced Options**:
@@ -41,96 +39,71 @@ separate articles:
 * `role_hierarchy`_
 
 access_denied_url
-~~~~~~~~~~~~~~~~~
+-----------------
 
 **type**: ``string`` **default**: ``null``
 
 Defines the URL where the user is redirected after a ``403`` HTTP error (unless
 you define a custom access denial handler). Example: ``/no-permission``
 
-delete_cookies
-~~~~~~~~~~~~~~
+erase_credentials
+-----------------
 
-**type**: ``array`` **default**: ``[]``
+**type**: ``boolean`` **default**: ``true``
 
-Lists the names (and other optional features) of the cookies to delete when the
-user logs out::
+If ``true``, the ``eraseCredentials()`` method of the user object is called
+after authentication::
 
-.. configuration-block::
+    use Symfony\Component\Security\Core\User\UserInterface;
 
-    .. code-block:: yaml
+    class User implements UserInterface
+    {
+        // ...
 
-        # config/packages/security.yaml
-        security:
-            # ...
+        public function eraseCredentials(): void
+        {
+            // If you store any temporary, sensitive data on the user, clear it here
+            // $this->plainPassword = null;
+        }
+    }
 
-            firewalls:
-                main:
-                    # ...
-                    logout:
-                        delete_cookies:
-                            cookie1-name: null
-                            cookie2-name:
-                                path: '/'
-                            cookie3-name:
-                                path: null
-                                domain: example.com
+.. deprecated:: 7.3
 
-    .. code-block:: xml
+   Since Symfony 7.3, ``eraseCredentials()`` methods are deprecated and are
+   not called if they have the ``#[\Deprecated]`` attribute.
 
-        <!-- config/packages/security.xml -->
-        <?xml version="1.0" encoding="UTF-8" ?>
-        <srv:container xmlns="http://symfony.com/schema/dic/security"
-            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-            xmlns:srv="http://symfony.com/schema/dic/services"
-            xsi:schemaLocation="http://symfony.com/schema/dic/services
-                https://symfony.com/schema/dic/services/services-1.0.xsd">
+expose_security_errors
+----------------------
 
-            <config>
-                <!-- ... -->
+**type**: ``string`` **default**: ``'none'``
 
-                <firewall name="main">
-                    <!-- ... -->
-                    <logout path="...">
-                        <delete-cookie name="cookie1-name"/>
-                        <delete-cookie name="cookie2-name" path="/"/>
-                        <delete-cookie name="cookie3-name" domain="example.com"/>
-                    </logout>
-                </firewall>
-            </config>
-        </srv:container>
+.. versionadded:: 7.3
 
-    .. code-block:: php
+    The ``expose_security_errors`` option was introduced in Symfony 7.3
 
-        // config/packages/security.php
+User enumeration is a common security issue where attackers infer valid usernames
+based on error messages. For example, a message like "This user does not exist"
+shown by your login form reveals whether a username exists.
 
-        // ...
+This option lets you hide some or all errors related to user accounts
+(e.g. blocked or expired accounts) to prevent this issue. Instead, these
+errors will trigger a generic ``BadCredentialsException``. The value of this
+option can be one of the following:
 
-        return static function (SecurityConfig $securityConfig): void {
-            // ...
+* ``'none'``: hides all user-related security exceptions;
+* ``'account_status'``: shows account-related exceptions (e.g. blocked or expired
+  accounts) but only for users who provided the correct password;
+* ``'all'``: shows all security-related exceptions.
 
-            $securityConfig->firewall('main')
-                ->logout()
-                    ->deleteCookie('cookie1-name')
-                    ->deleteCookie('cookie2-name')
-                        ->path('/')
-                    ->deleteCookie('cookie3-name')
-                        ->path(null)
-                        ->domain('example.com');
-        };
-
-erase_credentials
-~~~~~~~~~~~~~~~~~
+hide_user_not_found
+-------------------
 
 **type**: ``boolean`` **default**: ``true``
 
-If ``true``, the ``eraseCredentials()`` method of the user object is called
-after authentication.
+.. deprecated:: 7.3
 
-hide_user_not_found
-~~~~~~~~~~~~~~~~~~~
-
-**type**: ``boolean`` **default**: ``true``
+    The ``hide_user_not_found`` option was deprecated in favor of the
+    ``expose_security_errors`` option in Symfony 7.3.
 
 If ``true``, when a user is not found a generic exception of type
 :class:`Symfony\\Component\\Security\\Core\\Exception\\BadCredentialsException`
@@ -141,7 +114,7 @@ If ``false``, the exception thrown is of type
 and it includes the given not found user identifier.
 
 session_fixation_strategy
-~~~~~~~~~~~~~~~~~~~~~~~~~
+-------------------------
 
 **type**: ``string`` **default**: ``SessionAuthenticationStrategy::MIGRATE``
 
@@ -430,7 +403,7 @@ delete_cookies
 **type**: ``array`` **default**: ``[]``
 
 Lists the names (and other optional features) of the cookies to delete when the
-user logs out::
+user logs out:
 
 .. configuration-block::
 
@@ -1063,6 +1036,58 @@ the session must not be used when authenticating users:
             // ...
         };
 
+.. _reference-security-lazy:
+
+lazy
+~~~~
+
+Firewalls can configure a ``lazy`` boolean option to load the user and start the
+session only if the application actually accesses the User object, (e.g. calling
+``is_granted()`` in a template or ``isGranted()`` in a controller or service):
+
+.. configuration-block::
+
+    .. code-block:: yaml
+
+        # config/packages/security.yaml
+        security:
+            # ...
+
+            firewalls:
+                main:
+                    # ...
+                    lazy: true
+
+    .. code-block:: xml
+
+        <!-- config/packages/security.xml -->
+        <?xml version="1.0" encoding="UTF-8" ?>
+        <srv:container xmlns="http://symfony.com/schema/dic/security"
+            xmlns:srv="http://symfony.com/schema/dic/services"
+            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+            xsi:schemaLocation="http://symfony.com/schema/dic/services
+                https://symfony.com/schema/dic/services/services-1.0.xsd
+                http://symfony.com/schema/dic/security
+                https://symfony.com/schema/dic/security/security-1.0.xsd">
+
+            <config>
+                <firewall name="main" lazy="true">
+                    <!-- ... -->
+                </firewall>
+            </config>
+        </srv:container>
+
+    .. code-block:: php
+
+        // config/packages/security.php
+        use Symfony\Config\SecurityConfig;
+
+        return static function (SecurityConfig $security): void {
+            $security->firewall('main')
+                ->lazy(true);
+            // ...
+        };
+
 User Checkers
 ~~~~~~~~~~~~~
 
diff --git a/reference/configuration/twig.rst b/reference/configuration/twig.rst
index 596d70d8a2b..360309fef8f 100644
--- a/reference/configuration/twig.rst
+++ b/reference/configuration/twig.rst
@@ -19,9 +19,6 @@ under the ``twig`` key in your application configuration.
     namespace and the related XSD schema is available at:
     ``https://symfony.com/schema/dic/twig/twig-1.0.xsd``
 
-Configuration
--------------
-
 auto_reload
 ~~~~~~~~~~~
 
@@ -74,16 +71,27 @@ application harder to maintain.
 cache
 ~~~~~
 
-**type**: ``string`` | ``false`` **default**: ``%kernel.cache_dir%/twig``
+**type**: ``string`` | ``boolean`` **default**: ``true``
 
 Before using the Twig templates to render some contents, they are compiled into
 regular PHP code. Compilation is a costly process, so the result is cached in
 the directory defined by this configuration option.
 
+You can either specify a custom path where the cache should be stored (as a
+string) or use ``true`` to let Symfony decide the default path. When set to
+``true``, the cache is stored in ``%kernel.cache_dir%/twig`` by default. However,
+if ``auto_reload`` is disabled and ``%kernel.build_dir%`` differs from
+``%kernel.cache_dir%``, the cache will be stored in ``%kernel.build_dir%/twig`` instead.
+
 Set this option to ``false`` to disable Twig template compilation. However, this
-is not recommended; not even in the ``dev`` environment, because the
-``auto_reload`` option ensures that cached templates which have changed get
-compiled again.
+is not recommended, not even in the ``dev`` environment, because the ``auto_reload``
+option ensures that cached templates which have changed get compiled again.
+
+.. versionadded:: 7.3
+
+    Support for using ``true`` as a value was introduced in Symfony 7.3. It also
+    became the default value for this option, replacing the explicit path
+    ``%kernel.cache_dir%/twig``.
 
 charset
 ~~~~~~~
@@ -282,7 +290,7 @@ mailer
 html_to_text_converter
 ......................
 
-**type**: ``string`` **default**: ````
+**type**: ``string`` **default**: ``null``
 
 The service implementing
 :class:`Symfony\\Component\\Mime\\HtmlToTextConverter\\HtmlToTextConverterInterface`
diff --git a/reference/configuration/web_profiler.rst b/reference/configuration/web_profiler.rst
index 93c65621999..c3b57d37c55 100644
--- a/reference/configuration/web_profiler.rst
+++ b/reference/configuration/web_profiler.rst
@@ -24,9 +24,6 @@ under the ``web_profiler`` key in your application configuration.
 
     The web debug toolbar is not available for responses of type ``StreamedResponse``.
 
-Configuration
--------------
-
 excluded_ajax_paths
 ~~~~~~~~~~~~~~~~~~~
 
@@ -56,8 +53,21 @@ on the given link to perform the redirect.
 toolbar
 ~~~~~~~
 
+enabled
+.......
 **type**: ``boolean`` **default**: ``false``
 
 It enables and disables the toolbar entirely. Usually you set this to ``true``
 in the ``dev`` and ``test`` environments and to ``false`` in the ``prod``
 environment.
+
+ajax_replace
+............
+**type**: ``boolean`` **default**: ``false``
+
+If you set this option to ``true``, the toolbar is replaced on AJAX requests.
+This only works in combination with an enabled toolbar.
+
+.. versionadded:: 7.3
+
+    The ``ajax_replace`` configuration option was introduced in Symfony 7.3.
diff --git a/reference/constraints/All.rst b/reference/constraints/All.rst
index 3aa05b1d2d0..43ff4d6ac9d 100644
--- a/reference/constraints/All.rst
+++ b/reference/constraints/All.rst
@@ -79,12 +79,12 @@ entry in that array:
         {
             public static function loadValidatorMetadata(ClassMetadata $metadata): void
             {
-                $metadata->addPropertyConstraint('favoriteColors', new Assert\All([
-                    'constraints' => [
+                $metadata->addPropertyConstraint('favoriteColors', new Assert\All(
+                    constraints: [
                         new Assert\NotBlank(),
-                        new Assert\Length(['min' => 5]),
+                        new Assert\Length(min: 5),
                     ],
-                ]));
+                ));
             }
         }
 
@@ -97,7 +97,7 @@ Options
 ``constraints``
 ~~~~~~~~~~~~~~~
 
-**type**: ``array`` [:ref:`default option <validation-default-option>`]
+**type**: ``array``
 
 This required option is the array of validation constraints that you want
 to apply to each element of the underlying array.
diff --git a/reference/constraints/AtLeastOneOf.rst b/reference/constraints/AtLeastOneOf.rst
index 0a6ab618aa5..fecbe617f5a 100644
--- a/reference/constraints/AtLeastOneOf.rst
+++ b/reference/constraints/AtLeastOneOf.rst
@@ -115,23 +115,23 @@ The following constraints ensure that:
         {
             public static function loadValidatorMetadata(ClassMetadata $metadata): void
             {
-                $metadata->addPropertyConstraint('password', new Assert\AtLeastOneOf([
-                    'constraints' => [
-                        new Assert\Regex(['pattern' => '/#/']),
-                        new Assert\Length(['min' => 10]),
+                $metadata->addPropertyConstraint('password', new Assert\AtLeastOneOf(
+                    constraints: [
+                        new Assert\Regex(pattern: '/#/'),
+                        new Assert\Length(min: 10),
                     ],
-                ]));
+                ));
 
-                $metadata->addPropertyConstraint('grades', new Assert\AtLeastOneOf([
-                    'constraints' => [
-                        new Assert\Count(['min' => 3]),
-                        new Assert\All([
-                            'constraints' => [
+                $metadata->addPropertyConstraint('grades', new Assert\AtLeastOneOf(
+                    constraints: [
+                        new Assert\Count(min: 3),
+                        new Assert\All(
+                            constraints: [
                                 new Assert\GreaterThanOrEqual(5),
                             ],
-                        ]),
+                        ),
                     ],
-                ]));
+                ));
             }
         }
 
@@ -141,7 +141,7 @@ Options
 constraints
 ~~~~~~~~~~~
 
-**type**: ``array`` [:ref:`default option <validation-default-option>`]
+**type**: ``array``
 
 This required option is the array of validation constraints from which at least one of
 has to be satisfied in order for the validation to succeed.
diff --git a/reference/constraints/Bic.rst b/reference/constraints/Bic.rst
index 69ce35248f3..6cde4a11bac 100644
--- a/reference/constraints/Bic.rst
+++ b/reference/constraints/Bic.rst
@@ -121,4 +121,19 @@ Parameter        Description
 
 .. include:: /reference/constraints/_payload-option.rst.inc
 
+``mode``
+~~~~~~~~
+
+**type**: ``string`` **default**: ``Bic::VALIDATION_MODE_STRICT``
+
+This option defines how the BIC is validated. The possible values are available
+as constants in the :class:`Symfony\\Component\\Validator\\Constraints\\Bic` class:
+
+* ``Bic::VALIDATION_MODE_STRICT`` validates the given value without any modification;
+* ``Bic::VALIDATION_MODE_CASE_INSENSITIVE`` converts the given value to uppercase before validating it.
+
+.. versionadded:: 7.2
+
+    The ``mode`` option was introduced in Symfony 7.2.
+
 .. _`Business Identifier Code (BIC)`: https://en.wikipedia.org/wiki/Business_Identifier_Code
diff --git a/reference/constraints/Callback.rst b/reference/constraints/Callback.rst
index f4c78a9642a..017b9435cff 100644
--- a/reference/constraints/Callback.rst
+++ b/reference/constraints/Callback.rst
@@ -259,7 +259,7 @@ Options
 ``callback``
 ~~~~~~~~~~~~
 
-**type**: ``string``, ``array`` or ``Closure`` [:ref:`default option <validation-default-option>`]
+**type**: ``string``, ``array`` or ``Closure``
 
 The callback option accepts three different formats for specifying the
 callback method:
diff --git a/reference/constraints/CardScheme.rst b/reference/constraints/CardScheme.rst
index 6e98e6fab98..a2ed9c568c3 100644
--- a/reference/constraints/CardScheme.rst
+++ b/reference/constraints/CardScheme.rst
@@ -77,12 +77,12 @@ on an object that will contain a credit card number.
         {
             public static function loadValidatorMetadata(ClassMetadata $metadata): void
             {
-                $metadata->addPropertyConstraint('cardNumber', new Assert\CardScheme([
-                    'schemes' => [
+                $metadata->addPropertyConstraint('cardNumber', new Assert\CardScheme(
+                    schemes: [
                         Assert\CardScheme::VISA,
                     ],
-                    'message' => 'Your credit card number is invalid.',
-                ]));
+                    message: 'Your credit card number is invalid.',
+                ));
             }
         }
 
@@ -114,7 +114,7 @@ Parameter        Description
 ``schemes``
 ~~~~~~~~~~~
 
-**type**: ``mixed`` [:ref:`default option <validation-default-option>`]
+**type**: ``mixed``
 
 This option is required and represents the name of the number scheme used
 to validate the credit card number, it can either be a string or an array.
diff --git a/reference/constraints/Choice.rst b/reference/constraints/Choice.rst
index 5a9c365be37..72e1ae6ecf7 100644
--- a/reference/constraints/Choice.rst
+++ b/reference/constraints/Choice.rst
@@ -100,10 +100,10 @@ If your valid choice list is simple, you can pass them in directly via the
                     new Assert\Choice(['New York', 'Berlin', 'Tokyo'])
                 );
 
-                $metadata->addPropertyConstraint('genre', new Assert\Choice([
-                    'choices' => ['fiction', 'non-fiction'],
-                    'message' => 'Choose a valid genre.',
-                ]));
+                $metadata->addPropertyConstraint('genre', new Assert\Choice(
+                    choices: ['fiction', 'non-fiction'],
+                    message: 'Choose a valid genre.',
+                ));
             }
         }
 
@@ -182,9 +182,9 @@ constraint.
 
             public static function loadValidatorMetadata(ClassMetadata $metadata): void
             {
-                $metadata->addPropertyConstraint('genre', new Assert\Choice([
-                    'callback' => 'getGenres',
-                ]));
+                $metadata->addPropertyConstraint('genre', new Assert\Choice(
+                    callback: 'getGenres',
+                ));
             }
         }
 
@@ -250,9 +250,9 @@ you can pass the class name and the method as an array.
 
             public static function loadValidatorMetadata(ClassMetadata $metadata): void
             {
-                $metadata->addPropertyConstraint('genre', new Assert\Choice([
-                    'callback' => [Genre::class, 'getGenres'],
-                ]));
+                $metadata->addPropertyConstraint('genre', new Assert\Choice(
+                    callback: [Genre::class, 'getGenres'],
+                ));
             }
         }
 
@@ -271,7 +271,7 @@ to return the choices array. See
 ``choices``
 ~~~~~~~~~~~
 
-**type**: ``array`` [:ref:`default option <validation-default-option>`]
+**type**: ``array``
 
 A required option (unless `callback`_ is specified) - this is the array
 of options that should be considered in the valid set. The input value
@@ -304,6 +304,7 @@ Parameter          Description
 =================  ============================================================
 ``{{ choices }}``  A comma-separated list of available choices
 ``{{ value }}``    The current (invalid) value
+``{{ limit }}``    The maximum number of selectable choices
 =================  ============================================================
 
 match
@@ -358,6 +359,7 @@ Parameter          Description
 =================  ============================================================
 ``{{ choices }}``  A comma-separated list of available choices
 ``{{ value }}``    The current (invalid) value
+``{{ limit }}``    The minimum number of selectable choices
 =================  ============================================================
 
 ``multiple``
@@ -381,11 +383,11 @@ is not in the array of valid choices.
 
 You can use the following parameters in this message:
 
-===============  ==============================================================
-Parameter        Description
-===============  ==============================================================
-``{{ value }}``  The current (invalid) value
-``{{ label }}``  Corresponding form field label
-===============  ==============================================================
+=================  ============================================================
+Parameter          Description
+=================  ============================================================
+``{{ choices }}``  A comma-separated list of available choices
+``{{ value }}``    The current (invalid) value
+=================  ============================================================
 
 .. include:: /reference/constraints/_payload-option.rst.inc
diff --git a/reference/constraints/Collection.rst b/reference/constraints/Collection.rst
index 2d16d201b17..c35a0103581 100644
--- a/reference/constraints/Collection.rst
+++ b/reference/constraints/Collection.rst
@@ -139,8 +139,8 @@ following:
 
             public static function loadValidatorMetadata(ClassMetadata $metadata): void
             {
-                $metadata->addPropertyConstraint('profileData', new Assert\Collection([
-                    'fields' => [
+                $metadata->addPropertyConstraint('profileData', new Assert\Collection(
+                    fields: [
                         'personal_email' => new Assert\Email(),
                         'short_bio' => [
                             new Assert\NotBlank(),
@@ -150,8 +150,8 @@ following:
                             ]),
                         ],
                     ],
-                    'allowMissingFields' => true,
-                ]));
+                    allowMissingFields: true,
+                ));
             }
         }
 
@@ -267,15 +267,15 @@ you can do the following:
 
             public static function loadValidatorMetadata(ClassMetadata $metadata): void
             {
-                $metadata->addPropertyConstraint('profileData', new Assert\Collection([
-                    'fields' => [
+                $metadata->addPropertyConstraint('profileData', new Assert\Collection(
+                    fields: [
                         'personal_email'  => new Assert\Required([
                             new Assert\NotBlank(),
                             new Assert\Email(),
                         ]),
                         'alternate_email' => new Assert\Optional(new Assert\Email()),
                     ],
-                ]));
+                ));
             }
         }
 
@@ -291,28 +291,28 @@ groups. Take the following example::
 
     use Symfony\Component\Validator\Constraints as Assert;
 
-    $constraint = new Assert\Collection([
-        'fields' => [
+    $constraint = new Assert\Collection(
+        fields: [
             'name' => new Assert\NotBlank(['groups' => 'basic']),
             'email' => new Assert\NotBlank(['groups' => 'contact']),
         ],
-    ]);
+    );
 
 This will result in the following configuration::
 
-    $constraint = new Assert\Collection([
-        'fields' => [
-            'name' => new Assert\Required([
-                'constraints' => new Assert\NotBlank(['groups' => 'basic']),
-                'groups' => ['basic', 'strict'],
-            ]),
-            'email' => new Assert\Required([
-                "constraints" => new Assert\NotBlank(['groups' => 'contact']),
-                'groups' => ['basic', 'strict'],
-            ]),
+    $constraint = new Assert\Collection(
+        fields: [
+            'name' => new Assert\Required(
+                constraints: new Assert\NotBlank(groups: ['basic']),
+                groups: ['basic', 'strict'],
+            ),
+            'email' => new Assert\Required(
+                constraints: new Assert\NotBlank(groups: ['contact']),
+                groups: ['basic', 'strict'],
+            ),
         ],
-        'groups' => ['basic', 'strict'],
-    ]);
+        groups: ['basic', 'strict'],
+    );
 
 The default ``allowMissingFields`` option requires the fields in all groups.
 So when validating in ``contact`` group, ``$name`` can be empty but the key is
@@ -360,7 +360,7 @@ Parameter        Description
 ``fields``
 ~~~~~~~~~~
 
-**type**: ``array`` [:ref:`default option <validation-default-option>`]
+**type**: ``array``
 
 This option is required and is an associative array defining all of the
 keys in the collection and, for each key, exactly which validator(s) should
diff --git a/reference/constraints/Compound.rst b/reference/constraints/Compound.rst
index fc8081f5917..4d2c7743176 100644
--- a/reference/constraints/Compound.rst
+++ b/reference/constraints/Compound.rst
@@ -35,9 +35,9 @@ you can create your own named set or requirements to be reused consistently ever
                 return [
                     new Assert\NotBlank(),
                     new Assert\Type('string'),
-                    new Assert\Length(['min' => 12]),
+                    new Assert\Length(min: 12),
                     new Assert\NotCompromisedPassword(),
-                    new Assert\PasswordStrength(['minScore' => 4]),
+                    new Assert\PasswordStrength(minScore: 4),
                 ];
             }
         }
@@ -102,6 +102,50 @@ You can now use it anywhere you need it:
             }
         }
 
+Validation groups and payload can be passed via constructor:
+
+.. configuration-block::
+
+    .. code-block:: php-attributes
+
+        // src/Entity/User.php
+        namespace App\Entity\User;
+
+        use App\Validator\Constraints as Assert;
+
+        class User
+        {
+            #[Assert\PasswordRequirements(
+                groups: ['registration'],
+                payload: ['severity' => 'error'],
+            )]
+            public string $plainPassword;
+        }
+
+    .. code-block:: php
+
+        // src/Entity/User.php
+        namespace App\Entity\User;
+
+        use App\Validator\Constraints as Assert;
+        use Symfony\Component\Validator\Mapping\ClassMetadata;
+
+        class User
+        {
+            public static function loadValidatorMetadata(ClassMetadata $metadata): void
+            {
+                $metadata->addPropertyConstraint('plainPassword', new Assert\PasswordRequirements(
+                    groups: ['registration'],
+                    payload: ['severity' => 'error'],
+                ));
+            }
+        }
+
+.. versionadded:: 7.2
+
+    Support for passing validation groups and the payload to the constructor
+    of the ``Compound`` class was introduced in Symfony 7.2.
+
 Options
 -------
 
diff --git a/reference/constraints/Count.rst b/reference/constraints/Count.rst
index 0bf40aca8e9..d33c54c0812 100644
--- a/reference/constraints/Count.rst
+++ b/reference/constraints/Count.rst
@@ -82,12 +82,12 @@ you might add the following:
 
             public static function loadValidatorMetadata(ClassMetadata $metadata): void
             {
-                $metadata->addPropertyConstraint('emails', new Assert\Count([
-                    'min' => 1,
-                    'max' => 5,
-                    'minMessage' => 'You must specify at least one email',
-                    'maxMessage' => 'You cannot specify more than {{ limit }} emails',
-                ]));
+                $metadata->addPropertyConstraint('emails', new Assert\Count(
+                    min: 1,
+                    max: 5,
+                    minMessage: 'You must specify at least one email',
+                    maxMessage: 'You cannot specify more than {{ limit }} emails',
+                ));
             }
         }
 
diff --git a/reference/constraints/CssColor.rst b/reference/constraints/CssColor.rst
index 88a4eb4be9f..b9c78ec25ac 100644
--- a/reference/constraints/CssColor.rst
+++ b/reference/constraints/CssColor.rst
@@ -2,7 +2,7 @@ CssColor
 ========
 
 Validates that a value is a valid CSS color. The underlying value is
-casted to a string before being validated.
+cast to a string before being validated.
 
 ==========  ===================================================================
 Applies to  :ref:`property or method <validation-property-target>`
@@ -110,15 +110,15 @@ the named CSS colors:
             {
                 $metadata->addPropertyConstraint('defaultColor', new Assert\CssColor());
 
-                $metadata->addPropertyConstraint('accentColor', new Assert\CssColor([
-                    'formats' => Assert\CssColor::HEX_LONG,
-                    'message' => 'The accent color must be a 6-character hexadecimal color.',
-                ]));
+                $metadata->addPropertyConstraint('accentColor', new Assert\CssColor(
+                    formats: Assert\CssColor::HEX_LONG,
+                    message: 'The accent color must be a 6-character hexadecimal color.',
+                ));
 
-                $metadata->addPropertyConstraint('currentColor', new Assert\CssColor([
-                    'formats' => [Assert\CssColor::BASIC_NAMED_COLORS, Assert\CssColor::EXTENDED_NAMED_COLORS],
-                    'message' => 'The color "{{ value }}" is not a valid CSS color name.',
-                ]));
+                $metadata->addPropertyConstraint('currentColor', new Assert\CssColor(
+                    formats: [Assert\CssColor::BASIC_NAMED_COLORS, Assert\CssColor::EXTENDED_NAMED_COLORS],
+                    message: 'The color "{{ value }}" is not a valid CSS color name.',
+                ));
             }
         }
 
diff --git a/reference/constraints/DateTime.rst b/reference/constraints/DateTime.rst
index f6bcce7e5f5..ffcfbf55dda 100644
--- a/reference/constraints/DateTime.rst
+++ b/reference/constraints/DateTime.rst
@@ -99,11 +99,16 @@ This message is shown if the underlying data is not a valid datetime.
 
 You can use the following parameters in this message:
 
-===============  ==============================================================
-Parameter        Description
-===============  ==============================================================
-``{{ value }}``  The current (invalid) value
-``{{ label }}``  Corresponding form field label
-===============  ==============================================================
+================  ==============================================================
+Parameter         Description
+================  ==============================================================
+``{{ value }}``   The current (invalid) value
+``{{ label }}``   Corresponding form field label
+``{{ format }}``  The date format defined in ``format``
+================  ==============================================================
+
+.. versionadded:: 7.3
+
+    The ``{{ format }}`` parameter was introduced in Symfony 7.3.
 
 .. include:: /reference/constraints/_payload-option.rst.inc
diff --git a/reference/constraints/DivisibleBy.rst b/reference/constraints/DivisibleBy.rst
index dd90ad9a0fd..23b36023cff 100644
--- a/reference/constraints/DivisibleBy.rst
+++ b/reference/constraints/DivisibleBy.rst
@@ -92,9 +92,9 @@ The following constraints ensure that:
             {
                 $metadata->addPropertyConstraint('weight', new Assert\DivisibleBy(0.25));
 
-                $metadata->addPropertyConstraint('quantity', new Assert\DivisibleBy([
-                    'value' => 5,
-                ]));
+                $metadata->addPropertyConstraint('quantity', new Assert\DivisibleBy(
+                    value: 5,
+                ));
             }
         }
 
diff --git a/reference/constraints/Email.rst b/reference/constraints/Email.rst
index 516d6d07dca..41012e5e935 100644
--- a/reference/constraints/Email.rst
+++ b/reference/constraints/Email.rst
@@ -70,9 +70,9 @@ Basic Usage
 
             public static function loadValidatorMetadata(ClassMetadata $metadata): void
             {
-                $metadata->addPropertyConstraint('email', new Assert\Email([
-                    'message' => 'The email "{{ value }}" is not a valid email.',
-                ]));
+                $metadata->addPropertyConstraint('email', new Assert\Email(
+                    message: 'The email "{{ value }}" is not a valid email.',
+                ));
             }
         }
 
diff --git a/reference/constraints/EqualTo.rst b/reference/constraints/EqualTo.rst
index d5d78f60a0f..fdc402b1a97 100644
--- a/reference/constraints/EqualTo.rst
+++ b/reference/constraints/EqualTo.rst
@@ -91,9 +91,9 @@ and that the ``age`` is ``20``, you could do the following:
             {
                 $metadata->addPropertyConstraint('firstName', new Assert\EqualTo('Mary'));
 
-                $metadata->addPropertyConstraint('age', new Assert\EqualTo([
-                    'value' => 20,
-                ]));
+                $metadata->addPropertyConstraint('age', new Assert\EqualTo(
+                    value: 20,
+                ));
             }
         }
 
diff --git a/reference/constraints/Expression.rst b/reference/constraints/Expression.rst
index bf015d17573..518c5c1f160 100644
--- a/reference/constraints/Expression.rst
+++ b/reference/constraints/Expression.rst
@@ -111,10 +111,10 @@ One way to accomplish this is with the Expression constraint:
         {
             public static function loadValidatorMetadata(ClassMetadata $metadata): void
             {
-                $metadata->addConstraint(new Assert\Expression([
-                    'expression' => 'this.getCategory() in ["php", "symfony"] or !this.isTechnicalPost()',
-                    'message' => 'If this is a tech post, the category should be either php or symfony!',
-                ]));
+                $metadata->addConstraint(new Assert\Expression(
+                    expression: 'this.getCategory() in ["php", "symfony"] or !this.isTechnicalPost()',
+                    message: 'If this is a tech post, the category should be either php or symfony!',
+                ));
             }
 
             // ...
@@ -200,10 +200,10 @@ assert that the expression must return ``true`` for validation to fail.
             {
                 public static function loadValidatorMetadata(ClassMetadata $metadata): void
                 {
-                    $metadata->addPropertyConstraint('isTechnicalPost', new Assert\Expression([
-                        'expression' => 'this.getCategory() in ["php", "symfony"] or value == false',
-                        'message' => 'If this is a tech post, the category should be either php or symfony!',
-                    ]));
+                    $metadata->addPropertyConstraint('isTechnicalPost', new Assert\Expression(
+                        expression: 'this.getCategory() in ["php", "symfony"] or value == false',
+                        message: 'If this is a tech post, the category should be either php or symfony!',
+                    ));
                 }
 
                 // ...
@@ -227,7 +227,7 @@ Options
 ``expression``
 ~~~~~~~~~~~~~~
 
-**type**: ``string`` [:ref:`default option <validation-default-option>`]
+**type**: ``string``
 
 The expression that will be evaluated. If the expression evaluates to a false
 value (using ``==``, not ``===``), validation will fail. Learn more about the
@@ -343,10 +343,10 @@ type (numeric, boolean, strings, null, etc.)
         {
             public static function loadValidatorMetadata(ClassMetadata $metadata): void
             {
-                $metadata->addPropertyConstraint('metric', new Assert\Expression([
-                    'expression' => 'value + error_margin < threshold',
-                    'values' => ['error_margin' => 0.25, 'threshold' => 1.5],
-                ]));
+                $metadata->addPropertyConstraint('metric', new Assert\Expression(
+                    expression: 'value + error_margin < threshold',
+                    values: ['error_margin' => 0.25, 'threshold' => 1.5],
+                ));
             }
 
             // ...
diff --git a/reference/constraints/ExpressionSyntax.rst b/reference/constraints/ExpressionSyntax.rst
index c1d086790c1..37e0ad7de4a 100644
--- a/reference/constraints/ExpressionSyntax.rst
+++ b/reference/constraints/ExpressionSyntax.rst
@@ -90,9 +90,9 @@ The following constraints ensure that:
             {
                 $metadata->addPropertyConstraint('promotion', new Assert\ExpressionSyntax());
 
-                $metadata->addPropertyConstraint('shippingOptions', new Assert\ExpressionSyntax([
-                    'allowedVariables' => ['user', 'shipping_centers'],
-                ]));
+                $metadata->addPropertyConstraint('shippingOptions', new Assert\ExpressionSyntax(
+                    allowedVariables: ['user', 'shipping_centers'],
+                ));
             }
         }
 
diff --git a/reference/constraints/File.rst b/reference/constraints/File.rst
index 6d9b72d17b8..62efa6cc08e 100644
--- a/reference/constraints/File.rst
+++ b/reference/constraints/File.rst
@@ -119,13 +119,13 @@ below a certain file size and a valid PDF, add the following:
 
             public static function loadValidatorMetadata(ClassMetadata $metadata): void
             {
-                $metadata->addPropertyConstraint('bioFile', new Assert\File([
-                    'maxSize' => '1024k',
-                    'extensions' => [
+                $metadata->addPropertyConstraint('bioFile', new Assert\File(
+                    maxSize: '1024k',
+                    extensions: [
                         'pdf',
                     ],
-                    'extensionsMessage' => 'Please upload a valid PDF',
-                ]));
+                    extensionsMessage: 'Please upload a valid PDF',
+                ));
             }
         }
 
@@ -274,6 +274,31 @@ You can find a list of existing mime types on the `IANA website`_.
 If set, the validator will check that the filename of the underlying file
 doesn't exceed a certain length.
 
+``filenameCountUnit``
+~~~~~~~~~~~~~~~~~~~~~
+
+**type**: ``string`` **default**: ``File::FILENAME_COUNT_BYTES``
+
+The character count unit to use for the filename max length check.
+By default :phpfunction:`strlen` is used, which counts the length of the string in bytes.
+
+Can be one of the following constants of the
+:class:`Symfony\\Component\\Validator\\Constraints\\File` class:
+
+* ``FILENAME_COUNT_BYTES``: Uses :phpfunction:`strlen` counting the length of the
+  string in bytes.
+* ``FILENAME_COUNT_CODEPOINTS``: Uses :phpfunction:`mb_strlen` counting the length
+  of the string in Unicode code points. Simple (multibyte) Unicode characters count
+  as 1 character, while for example ZWJ sequences of composed emojis count as
+  multiple characters.
+* ``FILENAME_COUNT_GRAPHEMES``: Uses :phpfunction:`grapheme_strlen` counting the
+  length of the string in graphemes, i.e. even emojis and ZWJ sequences of composed
+  emojis count as 1 character.
+
+.. versionadded:: 7.3
+
+    The ``filenameCountUnit`` option was introduced in Symfony 7.3.
+
 ``filenameTooLongMessage``
 ~~~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -290,6 +315,35 @@ Parameter                       Description
 ``{{ filename_max_length }}``   Maximum number of characters allowed
 ==============================  ==============================================================
 
+``filenameCharset``
+~~~~~~~~~~~~~~~~~~~
+
+**type**: ``string``  **default**: ``null``
+
+The charset to be used when computing value's filename max length with the
+:phpfunction:`mb_check_encoding` and :phpfunction:`mb_strlen`
+PHP functions.
+
+``filenameCharsetMessage``
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+**type**: ``string`` **default**: ``This filename does not match the expected charset.``
+
+The message that will be shown if the value is not using the given `filenameCharsetMessage`_.
+
+You can use the following parameters in this message:
+
+=================  ============================================================
+Parameter          Description
+=================  ============================================================
+``{{ charset }}``  The expected charset
+``{{ name }}``     The current (invalid) value
+=================  ============================================================
+
+.. versionadded:: 7.3
+
+    The ``filenameCharset`` and ``filenameCharsetMessage`` options were introduced in Symfony 7.3.
+
 ``extensionsMessage``
 ~~~~~~~~~~~~~~~~~~~~~
 
diff --git a/reference/constraints/GreaterThan.rst b/reference/constraints/GreaterThan.rst
index 4f2e34bcbfa..d1b79028acd 100644
--- a/reference/constraints/GreaterThan.rst
+++ b/reference/constraints/GreaterThan.rst
@@ -89,9 +89,9 @@ The following constraints ensure that:
             {
                 $metadata->addPropertyConstraint('siblings', new Assert\GreaterThan(5));
 
-                $metadata->addPropertyConstraint('age', new Assert\GreaterThan([
-                    'value' => 18,
-                ]));
+                $metadata->addPropertyConstraint('age', new Assert\GreaterThan(
+                    value: 18,
+                ));
             }
         }
 
diff --git a/reference/constraints/GreaterThanOrEqual.rst b/reference/constraints/GreaterThanOrEqual.rst
index e5a71e5f788..63c2ade6197 100644
--- a/reference/constraints/GreaterThanOrEqual.rst
+++ b/reference/constraints/GreaterThanOrEqual.rst
@@ -88,9 +88,9 @@ The following constraints ensure that:
             {
                 $metadata->addPropertyConstraint('siblings', new Assert\GreaterThanOrEqual(5));
 
-                $metadata->addPropertyConstraint('age', new Assert\GreaterThanOrEqual([
-                    'value' => 18,
-                ]));
+                $metadata->addPropertyConstraint('age', new Assert\GreaterThanOrEqual(
+                    value: 18,
+                ));
             }
         }
 
diff --git a/reference/constraints/Hostname.rst b/reference/constraints/Hostname.rst
index 95b10d1736e..58ac0364669 100644
--- a/reference/constraints/Hostname.rst
+++ b/reference/constraints/Hostname.rst
@@ -72,9 +72,9 @@ will contain a host name.
 
             public static function loadValidatorMetadata(ClassMetadata $metadata): void
             {
-                $metadata->addPropertyConstraint('name', new Assert\Hostname([
-                    'message' => 'The server name must be a valid hostname.',
-                ]));
+                $metadata->addPropertyConstraint('name', new Assert\Hostname(
+                    message: 'The server name must be a valid hostname.',
+                ));
             }
         }
 
diff --git a/reference/constraints/Iban.rst b/reference/constraints/Iban.rst
index 3cf800200e2..fdc955c81b0 100644
--- a/reference/constraints/Iban.rst
+++ b/reference/constraints/Iban.rst
@@ -77,14 +77,23 @@ will contain an International Bank Account Number.
 
             public static function loadValidatorMetadata(ClassMetadata $metadata): void
             {
-                $metadata->addPropertyConstraint('bankAccountNumber', new Assert\Iban([
-                    'message' => 'This is not a valid International Bank Account Number (IBAN).',
-                ]));
+                $metadata->addPropertyConstraint('bankAccountNumber', new Assert\Iban(
+                    message: 'This is not a valid International Bank Account Number (IBAN).',
+                ));
             }
         }
 
 .. include:: /reference/constraints/_empty-values-are-valid.rst.inc
 
+.. note::
+
+    For convenience, the IBAN validator accepts values with various types of
+    whitespace (e.g., regular, non-breaking, and narrow non-breaking spaces),
+    which are automatically removed before validation. However, this flexibility
+    can cause issues when storing IBANs or sending them to APIs that expect a
+    strict format. To ensure compatibility, normalize IBANs by removing
+    whitespace and converting them to uppercase before storing or processing.
+
 Options
 -------
 
diff --git a/reference/constraints/IdenticalTo.rst b/reference/constraints/IdenticalTo.rst
index 5b6d853dc0b..f8844f90a72 100644
--- a/reference/constraints/IdenticalTo.rst
+++ b/reference/constraints/IdenticalTo.rst
@@ -94,9 +94,9 @@ The following constraints ensure that:
             {
                 $metadata->addPropertyConstraint('firstName', new Assert\IdenticalTo('Mary'));
 
-                $metadata->addPropertyConstraint('age', new Assert\IdenticalTo([
-                    'value' => 20,
-                ]));
+                $metadata->addPropertyConstraint('age', new Assert\IdenticalTo(
+                    value: 20,
+                ));
             }
         }
 
diff --git a/reference/constraints/Image.rst b/reference/constraints/Image.rst
index 042c6041423..5dd270c44f8 100644
--- a/reference/constraints/Image.rst
+++ b/reference/constraints/Image.rst
@@ -116,12 +116,12 @@ that it is between a certain size, add the following:
 
             public static function loadValidatorMetadata(ClassMetadata $metadata): void
             {
-                $metadata->addPropertyConstraint('headshot', new Assert\Image([
-                    'minWidth' => 200,
-                    'maxWidth' => 400,
-                    'minHeight' => 200,
-                    'maxHeight' => 400,
-                ]));
+                $metadata->addPropertyConstraint('headshot', new Assert\Image(
+                    minWidth: 200,
+                    maxWidth: 400,
+                    minHeight: 200,
+                    maxHeight: 400,
+                ));
             }
         }
 
@@ -187,10 +187,10 @@ following code:
 
             public static function loadValidatorMetadata(ClassMetadata $metadata): void
             {
-                $metadata->addPropertyConstraint('headshot', new Assert\Image([
-                    'allowLandscape' => false,
-                    'allowPortrait' => false,
-                ]));
+                $metadata->addPropertyConstraint('headshot', new Assert\Image(
+                    allowLandscape: false,
+                    allowPortrait: false,
+                ));
             }
         }
 
@@ -210,10 +210,9 @@ add several other options.
 
 If this option is false, the image cannot be landscape oriented.
 
-.. note::
+.. versionadded:: 7.3
 
-    This option does not apply to SVG files. If you use it with SVG files,
-    you'll see the error message defined in the ``sizeNotDetectedMessage`` option.
+    The ``allowLandscape`` option support for SVG files was introduced in Symfony 7.3.
 
 ``allowLandscapeMessage``
 ~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -239,10 +238,9 @@ Parameter         Description
 
 If this option is false, the image cannot be portrait oriented.
 
-.. note::
+.. versionadded:: 7.3
 
-    This option does not apply to SVG files. If you use it with SVG files,
-    you'll see the error message defined in the ``sizeNotDetectedMessage`` option.
+    The ``allowPortrait`` option support for SVG files was introduced in Symfony 7.3.
 
 ``allowPortraitMessage``
 ~~~~~~~~~~~~~~~~~~~~~~~~
@@ -270,10 +268,9 @@ If this option is false, the image cannot be a square. If you want to force
 a square image, then leave this option as its default ``true`` value
 and set `allowLandscape`_ and `allowPortrait`_ both to ``false``.
 
-.. note::
+.. versionadded:: 7.3
 
-    This option does not apply to SVG files. If you use it with SVG files,
-    you'll see the error message defined in the ``sizeNotDetectedMessage`` option.
+    The ``allowSquare`` option support for SVG files was introduced in Symfony 7.3.
 
 ``allowSquareMessage``
 ~~~~~~~~~~~~~~~~~~~~~~
@@ -373,10 +370,9 @@ Parameter             Description
 If set, the aspect ratio (``width / height``) of the image file must be less
 than or equal to this value.
 
-.. note::
+.. versionadded:: 7.3
 
-    This option does not apply to SVG files. If you use it with SVG files,
-    you'll see the error message defined in the ``sizeNotDetectedMessage`` option.
+    The ``maxRatio`` option support for SVG files was introduced in Symfony 7.3.
 
 ``maxRatioMessage``
 ~~~~~~~~~~~~~~~~~~~
@@ -497,10 +493,9 @@ Parameter             Description
 If set, the aspect ratio (``width / height``) of the image file must be greater
 than or equal to this value.
 
-.. note::
+.. versionadded:: 7.3
 
-    This option does not apply to SVG files. If you use it with SVG files,
-    you'll see the error message defined in the ``sizeNotDetectedMessage`` option.
+    The ``minRatio`` option support for SVG files was introduced in Symfony 7.3.
 
 ``minRatioMessage``
 ~~~~~~~~~~~~~~~~~~~
@@ -555,11 +550,5 @@ options has been set.
 
 This message has no parameters.
 
-.. note::
-
-    Detecting the size of SVG images is not supported. This error message will
-    be displayed if you use any of the following options: ``allowLandscape``,
-    ``allowPortrait``, ``allowSquare``, ``maxRatio``, and ``minRatio``.
-
 .. _`IANA website`: https://www.iana.org/assignments/media-types/media-types.xhtml
 .. _`PHP GD extension`: https://www.php.net/manual/en/book.image.php
diff --git a/reference/constraints/IsFalse.rst b/reference/constraints/IsFalse.rst
index 0b9ebe77491..3d0a1665944 100644
--- a/reference/constraints/IsFalse.rst
+++ b/reference/constraints/IsFalse.rst
@@ -93,9 +93,9 @@ method returns **false**:
 
             public static function loadValidatorMetadata(ClassMetadata $metadata): void
             {
-                $metadata->addGetterConstraint('stateInvalid', new Assert\IsFalse([
-                    'message' => "You've entered an invalid state.",
-                ]));
+                $metadata->addGetterConstraint('stateInvalid', new Assert\IsFalse(
+                    message: "You've entered an invalid state.",
+                ));
             }
 
             public function isStateInvalid(): bool
diff --git a/reference/constraints/IsTrue.rst b/reference/constraints/IsTrue.rst
index 678371f6e69..b50ba4f3e8b 100644
--- a/reference/constraints/IsTrue.rst
+++ b/reference/constraints/IsTrue.rst
@@ -97,9 +97,9 @@ Then you can validate this method with ``IsTrue`` as follows:
 
             public static function loadValidatorMetadata(ClassMetadata $metadata): void
             {
-                $metadata->addGetterConstraint('tokenValid', new IsTrue([
-                    'message' => 'The token is invalid.',
-                ]));
+                $metadata->addGetterConstraint('tokenValid', new IsTrue(
+                    message: 'The token is invalid.',
+                ));
             }
 
             public function isTokenValid(): bool
diff --git a/reference/constraints/Isbn.rst b/reference/constraints/Isbn.rst
index 954bff233d5..52d10565fe5 100644
--- a/reference/constraints/Isbn.rst
+++ b/reference/constraints/Isbn.rst
@@ -76,10 +76,10 @@ on an object that will contain an ISBN.
 
             public static function loadValidatorMetadata(ClassMetadata $metadata): void
             {
-                $metadata->addPropertyConstraint('isbn', new Assert\Isbn([
-                    'type' => Assert\Isbn::ISBN_10,
-                    'message' => 'This value is not valid.',
-                ]));
+                $metadata->addPropertyConstraint('isbn', new Assert\Isbn(
+                    type: Assert\Isbn::ISBN_10,
+                    message: 'This value is not valid.',
+                ));
             }
         }
 
diff --git a/reference/constraints/Json.rst b/reference/constraints/Json.rst
index 28e15976f3c..337b2dc6a1e 100644
--- a/reference/constraints/Json.rst
+++ b/reference/constraints/Json.rst
@@ -69,9 +69,9 @@ The ``Json`` constraint can be applied to a property or a "getter" method:
         {
             public static function loadValidatorMetadata(ClassMetadata $metadata): void
             {
-                $metadata->addPropertyConstraint('chapters', new Assert\Json([
-                    'message' => 'You\'ve entered an invalid Json.',
-                ]));
+                $metadata->addPropertyConstraint('chapters', new Assert\Json(
+                    message: 'You\'ve entered an invalid Json.',
+                ));
             }
         }
 
diff --git a/reference/constraints/Length.rst b/reference/constraints/Length.rst
index 9a4478f509b..c1a8575070b 100644
--- a/reference/constraints/Length.rst
+++ b/reference/constraints/Length.rst
@@ -85,12 +85,12 @@ and ``50``, you might add the following:
 
             public static function loadValidatorMetadata(ClassMetadata $metadata): void
             {
-                $metadata->addPropertyConstraint('firstName', new Assert\Length([
-                    'min' => 2,
-                    'max' => 50,
-                    'minMessage' => 'Your first name must be at least {{ limit }} characters long',
-                    'maxMessage' => 'Your first name cannot be longer than {{ limit }} characters',
-                ]));
+                $metadata->addPropertyConstraint('firstName', new Assert\Length(
+                    min: 2,
+                    max: 50,
+                    minMessage: 'Your first name must be at least {{ limit }} characters long',
+                    maxMessage: 'Your first name cannot be longer than {{ limit }} characters',
+                ));
             }
         }
 
diff --git a/reference/constraints/LessThan.rst b/reference/constraints/LessThan.rst
index 964bfbb3527..3d23bcda445 100644
--- a/reference/constraints/LessThan.rst
+++ b/reference/constraints/LessThan.rst
@@ -89,9 +89,9 @@ The following constraints ensure that:
             {
                 $metadata->addPropertyConstraint('siblings', new Assert\LessThan(5));
 
-                $metadata->addPropertyConstraint('age', new Assert\LessThan([
-                    'value' => 80,
-                ]));
+                $metadata->addPropertyConstraint('age', new Assert\LessThan(
+                    value: 80,
+                ));
             }
         }
 
diff --git a/reference/constraints/LessThanOrEqual.rst b/reference/constraints/LessThanOrEqual.rst
index 9420c3e4376..ac66c62d7d0 100644
--- a/reference/constraints/LessThanOrEqual.rst
+++ b/reference/constraints/LessThanOrEqual.rst
@@ -88,9 +88,9 @@ The following constraints ensure that:
             {
                 $metadata->addPropertyConstraint('siblings', new Assert\LessThanOrEqual(5));
 
-                $metadata->addPropertyConstraint('age', new Assert\LessThanOrEqual([
-                    'value' => 80,
-                ]));
+                $metadata->addPropertyConstraint('age', new Assert\LessThanOrEqual(
+                    value: 80,
+                ));
             }
         }
 
diff --git a/reference/constraints/Locale.rst b/reference/constraints/Locale.rst
index 49edd473d05..4bba45ae12b 100644
--- a/reference/constraints/Locale.rst
+++ b/reference/constraints/Locale.rst
@@ -78,9 +78,9 @@ Basic Usage
 
             public static function loadValidatorMetadata(ClassMetadata $metadata): void
             {
-                $metadata->addPropertyConstraint('locale', new Assert\Locale([
-                    'canonicalize' => true,
-                ]));
+                $metadata->addPropertyConstraint('locale', new Assert\Locale(
+                    canonicalize: true,
+                ));
             }
         }
 
diff --git a/reference/constraints/Luhn.rst b/reference/constraints/Luhn.rst
index 8f5ef34c4ba..0c835204091 100644
--- a/reference/constraints/Luhn.rst
+++ b/reference/constraints/Luhn.rst
@@ -72,9 +72,9 @@ will contain a credit card number.
 
             public static function loadValidatorMetadata(ClassMetadata $metadata): void
             {
-                $metadata->addPropertyConstraint('cardNumber', new Assert\Luhn([
-                    'message' => 'Please check your credit card number',
-                ]));
+                $metadata->addPropertyConstraint('cardNumber', new Assert\Luhn(
+                    message: 'Please check your credit card number',
+                ));
             }
         }
 
diff --git a/reference/constraints/NotEqualTo.rst b/reference/constraints/NotEqualTo.rst
index b8ee4cac32f..dd3f633b4a1 100644
--- a/reference/constraints/NotEqualTo.rst
+++ b/reference/constraints/NotEqualTo.rst
@@ -93,9 +93,9 @@ the following:
             {
                 $metadata->addPropertyConstraint('firstName', new Assert\NotEqualTo('Mary'));
 
-                $metadata->addPropertyConstraint('age', new Assert\NotEqualTo([
-                    'value' => 15,
-                ]));
+                $metadata->addPropertyConstraint('age', new Assert\NotEqualTo(
+                    value: 15,
+                ));
             }
         }
 
diff --git a/reference/constraints/NotIdenticalTo.rst b/reference/constraints/NotIdenticalTo.rst
index 9ea93dc4b86..b2c20027292 100644
--- a/reference/constraints/NotIdenticalTo.rst
+++ b/reference/constraints/NotIdenticalTo.rst
@@ -94,9 +94,9 @@ The following constraints ensure that:
             {
                 $metadata->addPropertyConstraint('age', new Assert\NotIdenticalTo('Mary'));
 
-                $metadata->addPropertyConstraint('age', new Assert\NotIdenticalTo([
-                    'value' => 15,
-                ]));
+                $metadata->addPropertyConstraint('age', new Assert\NotIdenticalTo(
+                    value: 15,
+                ));
             }
         }
 
diff --git a/reference/constraints/PasswordStrength.rst b/reference/constraints/PasswordStrength.rst
index 989ffddd100..0b242cacf08 100644
--- a/reference/constraints/PasswordStrength.rst
+++ b/reference/constraints/PasswordStrength.rst
@@ -101,9 +101,9 @@ or by a custom password strength estimator.
 
     class User
     {
-        #[Assert\PasswordStrength([
-            'minScore' => PasswordStrength::STRENGTH_VERY_STRONG, // Very strong password required
-        ])]
+        #[Assert\PasswordStrength(
+            minScore: PasswordStrength::STRENGTH_VERY_STRONG, // Very strong password required
+        )]
         protected $rawPassword;
     }
 
@@ -123,8 +123,92 @@ The default message supplied when the password does not reach the minimum requir
 
     class User
     {
-        #[Assert\PasswordStrength([
-            'message' => 'Your password is too easy to guess. Company\'s security policy requires to use a stronger password.'
-        ])]
+        #[Assert\PasswordStrength(
+            message: 'Your password is too easy to guess. Company\'s security policy requires to use a stronger password.'
+        )]
         protected $rawPassword;
     }
+
+Customizing the Password Strength Estimation
+--------------------------------------------
+
+.. versionadded:: 7.2
+
+    The feature to customize the password strength estimation was introduced in Symfony 7.2.
+
+By default, this constraint calculates the strength of a password based on its
+length and the number of unique characters used. You can get the calculated
+password strength (e.g. to display it in the user interface) using the following
+static function::
+
+    use Symfony\Component\Validator\Constraints\PasswordStrengthValidator;
+
+    $passwordEstimatedStrength = PasswordStrengthValidator::estimateStrength($password);
+
+If you need to override the default password strength estimation algorithm, you
+can pass a ``Closure`` to the :class:`Symfony\\Component\\Validator\\Constraints\\PasswordStrengthValidator`
+constructor (e.g. using the :doc:`service closures </service_container/service_closures>`).
+
+First, create a custom password strength estimation algorithm within a dedicated
+callable class::
+
+    namespace App\Validator;
+
+    class CustomPasswordStrengthEstimator
+    {
+        /**
+         * @return PasswordStrength::STRENGTH_*
+         */
+        public function __invoke(string $password): int
+        {
+            // Your custom password strength estimation algorithm
+        }
+    }
+
+Then, configure the :class:`Symfony\\Component\\Validator\\Constraints\\PasswordStrengthValidator`
+service to use your own estimator:
+
+.. configuration-block::
+
+    .. code-block:: yaml
+
+        # config/services.yaml
+        services:
+            custom_password_strength_estimator:
+                class: App\Validator\CustomPasswordStrengthEstimator
+
+            Symfony\Component\Validator\Constraints\PasswordStrengthValidator:
+                arguments: [!closure '@custom_password_strength_estimator']
+
+    .. code-block:: xml
+
+        <!-- config/services.xml -->
+        <?xml version="1.0" encoding="UTF-8" ?>
+        <container xmlns="http://symfony.com/schema/dic/services"
+            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+            xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd">
+
+            <services>
+                <service id="custom_password_strength_estimator" class="App\Validator\CustomPasswordStrengthEstimator"/>
+
+                <service id="Symfony\Component\Validator\Constraints\PasswordStrengthValidator">
+                    <argument type="closure" id="custom_password_strength_estimator"/>
+                </service>
+            </services>
+        </container>
+
+    .. code-block:: php
+
+        // config/services.php
+        namespace Symfony\Component\DependencyInjection\Loader\Configurator;
+
+        use Symfony\Component\Validator\Constraints\PasswordStrengthValidator;
+
+        return function (ContainerConfigurator $container): void {
+            $services = $container->services();
+
+            $services->set('custom_password_strength_estimator', CustomPasswordStrengthEstimator::class);
+
+            $services->set(PasswordStrengthValidator::class)
+                ->args([closure('custom_password_strength_estimator')]);
+        };
diff --git a/reference/constraints/Range.rst b/reference/constraints/Range.rst
index edd199c48b9..46a9e3799b3 100644
--- a/reference/constraints/Range.rst
+++ b/reference/constraints/Range.rst
@@ -78,11 +78,11 @@ you might add the following:
 
             public static function loadValidatorMetadata(ClassMetadata $metadata): void
             {
-                $metadata->addPropertyConstraint('height', new Assert\Range([
-                    'min' => 120,
-                    'max' => 180,
-                    'notInRangeMessage' => 'You must be between {{ min }}cm and {{ max }}cm tall to enter',
-                ]));
+                $metadata->addPropertyConstraint('height', new Assert\Range(
+                    min: 120,
+                    max: 180,
+                    notInRangeMessage: 'You must be between {{ min }}cm and {{ max }}cm tall to enter',
+                ));
             }
         }
 
@@ -154,10 +154,10 @@ date must lie within the current year like this:
 
             public static function loadValidatorMetadata(ClassMetadata $metadata): void
             {
-                $metadata->addPropertyConstraint('startDate', new Assert\Range([
-                    'min' => 'first day of January',
-                    'max' => 'first day of January next year',
-                ]));
+                $metadata->addPropertyConstraint('startDate', new Assert\Range(
+                    min: 'first day of January',
+                    max: 'first day of January next year',
+                ));
             }
         }
 
@@ -224,10 +224,10 @@ dates. If you want to fix the timezone, append it to the date string:
 
             public static function loadValidatorMetadata(ClassMetadata $metadata): void
             {
-                $metadata->addPropertyConstraint('startDate', new Assert\Range([
-                    'min' => 'first day of January UTC',
-                    'max' => 'first day of January next year UTC',
-                ]));
+                $metadata->addPropertyConstraint('startDate', new Assert\Range(
+                    min: 'first day of January UTC',
+                    max: 'first day of January next year UTC',
+                ));
             }
         }
 
@@ -294,10 +294,10 @@ can check that a delivery date starts within the next five hours like this:
 
             public static function loadValidatorMetadata(ClassMetadata $metadata): void
             {
-                $metadata->addPropertyConstraint('deliveryDate', new Assert\Range([
-                    'min' => 'now',
-                    'max' => '+5 hours',
-                ]));
+                $metadata->addPropertyConstraint('deliveryDate', new Assert\Range(
+                    min: 'now',
+                    max: '+5 hours',
+                ));
             }
         }
 
diff --git a/reference/constraints/Regex.rst b/reference/constraints/Regex.rst
index 2e11a8d04fc..e3b4d4711b2 100644
--- a/reference/constraints/Regex.rst
+++ b/reference/constraints/Regex.rst
@@ -71,9 +71,9 @@ more word characters at the beginning of your string:
 
             public static function loadValidatorMetadata(ClassMetadata $metadata): void
             {
-                $metadata->addPropertyConstraint('description', new Assert\Regex([
-                    'pattern' => '/^\w+/',
-                ]));
+                $metadata->addPropertyConstraint('description', new Assert\Regex(
+                    pattern: '/^\w+/',
+                ));
             }
         }
 
@@ -145,11 +145,11 @@ it a custom message:
 
             public static function loadValidatorMetadata(ClassMetadata $metadata): void
             {
-                $metadata->addPropertyConstraint('firstName', new Assert\Regex([
-                    'pattern' => '/\d/',
-                    'match' => false,
-                    'message' => 'Your name cannot contain a number',
-                ]));
+                $metadata->addPropertyConstraint('firstName', new Assert\Regex(
+                    pattern: '/\d/',
+                    match: false,
+                    message: 'Your name cannot contain a number',
+                ));
             }
         }
 
@@ -236,10 +236,10 @@ need to specify the HTML5 compatible pattern in the ``htmlPattern`` option:
 
             public static function loadValidatorMetadata(ClassMetadata $metadata): void
             {
-                $metadata->addPropertyConstraint('name', new Assert\Regex([
-                    'pattern' => '/^[a-z]+$/i',
-                    'htmlPattern' => '[a-zA-Z]+',
-                ]));
+                $metadata->addPropertyConstraint('name', new Assert\Regex(
+                    pattern: '/^[a-z]+$/i',
+                    htmlPattern: '[a-zA-Z]+',
+                ));
             }
         }
 
@@ -275,7 +275,7 @@ Parameter          Description
 ``pattern``
 ~~~~~~~~~~~
 
-**type**: ``string`` [:ref:`default option <validation-default-option>`]
+**type**: ``string``
 
 This required option is the regular expression pattern that the input will
 be matched against. By default, this validator will fail if the input string
diff --git a/reference/constraints/Sequentially.rst b/reference/constraints/Sequentially.rst
index 7620997f0a3..078be338cdf 100644
--- a/reference/constraints/Sequentially.rst
+++ b/reference/constraints/Sequentially.rst
@@ -110,7 +110,7 @@ You can validate each of these constraints sequentially to solve these issues:
                 $metadata->addPropertyConstraint('address', new Assert\Sequentially([
                     new Assert\NotNull(),
                     new Assert\Type('string'),
-                    new Assert\Length(['min' => 10]),
+                    new Assert\Length(min: 10),
                     new Assert\Regex(self::ADDRESS_REGEX),
                     new AcmeAssert\Geolocalizable(),
                 ]));
@@ -123,7 +123,7 @@ Options
 ``constraints``
 ~~~~~~~~~~~~~~~
 
-**type**: ``array`` [:ref:`default option <validation-default-option>`]
+**type**: ``array``
 
 This required option is the array of validation constraints that you want
 to apply sequentially.
diff --git a/reference/constraints/Twig.rst b/reference/constraints/Twig.rst
new file mode 100644
index 00000000000..e38b4507d7a
--- /dev/null
+++ b/reference/constraints/Twig.rst
@@ -0,0 +1,130 @@
+Twig Constraint
+===============
+
+.. versionadded:: 7.3
+
+    The ``Twig`` constraint was introduced in Symfony 7.3.
+
+Validates that a given string contains valid :ref:`Twig syntax <twig-language>`.
+This is particularly useful when template content is user-generated or
+configurable, and you want to ensure it can be rendered by the Twig engine.
+
+.. note::
+
+    Using this constraint requires having the ``symfony/twig-bridge`` package
+    installed in your application (e.g. by running ``composer require symfony/twig-bridge``).
+
+==========  ===================================================================
+Applies to  :ref:`property or method <validation-property-target>`
+Class       :class:`Symfony\\Bridge\\Twig\\Validator\\Constraints\\Twig`
+Validator   :class:`Symfony\\Bridge\\Twig\\Validator\\Constraints\\TwigValidator`
+==========  ===================================================================
+
+Basic Usage
+-----------
+
+Apply the ``Twig`` constraint to validate the contents of any property or the
+returned value of any method::
+
+    use Symfony\Bridge\Twig\Validator\Constraints\Twig;
+
+    class Template
+    {
+        #[Twig]
+        private string $templateCode;
+    }
+
+.. configuration-block::
+
+    .. code-block:: php-attributes
+
+        // src/Entity/Page.php
+        namespace App\Entity;
+
+        use Symfony\Bridge\Twig\Validator\Constraints\Twig;
+
+        class Page
+        {
+            #[Twig]
+            private string $templateCode;
+        }
+
+    .. code-block:: yaml
+
+        # config/validator/validation.yaml
+        App\Entity\Page:
+            properties:
+                templateCode:
+                    - Symfony\Bridge\Twig\Validator\Constraints\Twig: ~
+
+    .. code-block:: xml
+
+        <!-- config/validator/validation.xml -->
+        <?xml version="1.0" encoding="UTF-8" ?>
+        <constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping"
+            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+            xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping https://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd">
+
+            <class name="App\Entity\Page">
+                <property name="templateCode">
+                    <constraint name="Symfony\Bridge\Twig\Validator\Constraints\Twig"/>
+                </property>
+            </class>
+        </constraint-mapping>
+
+    .. code-block:: php
+
+        // src/Entity/Page.php
+        namespace App\Entity;
+
+        use Symfony\Bridge\Twig\Validator\Constraints\Twig;
+        use Symfony\Component\Validator\Mapping\ClassMetadata;
+
+        class Page
+        {
+            // ...
+
+            public static function loadValidatorMetadata(ClassMetadata $metadata): void
+            {
+                $metadata->addPropertyConstraint('templateCode', new Twig());
+            }
+        }
+
+Constraint Options
+------------------
+
+``message``
+~~~~~~~~~~~
+
+**type**: ``message`` **default**: ``This value is not a valid Twig template.``
+
+This is the message displayed when the given string does *not* contain valid Twig syntax::
+
+    // ...
+
+    class Page
+    {
+        #[Twig(message: 'Check this Twig code; it contains errors.')]
+        private string $templateCode;
+    }
+
+This message has no parameters.
+
+``skipDeprecations``
+~~~~~~~~~~~~~~~~~~~~
+
+**type**: ``boolean`` **default**: ``true``
+
+If ``true``, Twig deprecation warnings are ignored during validation. Set it to
+``false`` to trigger validation errors when the given Twig code contains any deprecations::
+
+    // ...
+
+    class Page
+    {
+        #[Twig(skipDeprecations: false)]
+        private string $templateCode;
+    }
+
+This can be helpful when enforcing stricter template rules or preparing for major
+Twig version upgrades.
diff --git a/reference/constraints/Type.rst b/reference/constraints/Type.rst
index b99e8ce1c54..b49536dff8b 100644
--- a/reference/constraints/Type.rst
+++ b/reference/constraints/Type.rst
@@ -127,14 +127,14 @@ The following example checks if ``emailAddress`` is an instance of ``Symfony\Com
 
                 $metadata->addPropertyConstraint('firstName', new Assert\Type('string'));
 
-                $metadata->addPropertyConstraint('age', new Assert\Type([
-                    'type' => 'integer',
-                    'message' => 'The value {{ value }} is not a valid {{ type }}.',
-                ]));
-
-                $metadata->addPropertyConstraint('accessCode', new Assert\Type([
-                    'type' => ['alpha', 'digit'],
-                ]));
+                $metadata->addPropertyConstraint('age', new Assert\Type(
+                    type: 'integer',
+                    message: 'The value {{ value }} is not a valid {{ type }}.',
+                ));
+
+                $metadata->addPropertyConstraint('accessCode', new Assert\Type(
+                    type: ['alpha', 'digit'],
+                ));
             }
         }
 
@@ -169,7 +169,7 @@ Parameter        Description
 ``type``
 ~~~~~~~~
 
-**type**: ``string`` or ``array`` [:ref:`default option <validation-default-option>`]
+**type**: ``string`` or ``array``
 
 This required option defines the type or collection of types allowed for the
 given value. Each type is either the FQCN (fully qualified class name) of some
diff --git a/reference/constraints/Ulid.rst b/reference/constraints/Ulid.rst
index ed7dfe7ed96..4094bab98f5 100644
--- a/reference/constraints/Ulid.rst
+++ b/reference/constraints/Ulid.rst
@@ -73,6 +73,21 @@ Basic Usage
 Options
 -------
 
+``format``
+~~~~~~~~~~
+
+**type**: ``string`` **default**: ``Ulid::FORMAT_BASE_32``
+
+The format of the ULID to validate. The following formats are available:
+
+* ``Ulid::FORMAT_BASE_32``: The ULID is encoded in `base32`_ (default)
+* ``Ulid::FORMAT_BASE_58``: The ULID is encoded in `base58`_
+* ``Ulid::FORMAT_RFC4122``: The ULID is encoded in the `RFC 4122 format`_
+
+.. versionadded:: 7.2
+
+    The ``format`` option was introduced in Symfony 7.2.
+
 .. include:: /reference/constraints/_groups-option.rst.inc
 
 ``message``
@@ -96,3 +111,6 @@ Parameter        Description
 .. include:: /reference/constraints/_payload-option.rst.inc
 
 .. _`Universally Unique Lexicographically Sortable Identifier (ULID)`: https://github.com/ulid/spec
+.. _`base32`: https://en.wikipedia.org/wiki/Base32
+.. _`base58`: https://en.wikipedia.org/wiki/Binary-to-text_encoding#Base58
+.. _`RFC 4122 format`: https://datatracker.ietf.org/doc/html/rfc4122
diff --git a/reference/constraints/Unique.rst b/reference/constraints/Unique.rst
index 8954f455086..9ce84139cd5 100644
--- a/reference/constraints/Unique.rst
+++ b/reference/constraints/Unique.rst
@@ -162,14 +162,30 @@ collection::
 
             public static function loadValidatorMetadata(ClassMetadata $metadata): void
             {
-                $metadata->addPropertyConstraint('coordinates', new Assert\Unique([
-                    'fields' => ['latitude', 'longitude'],
-                ]));
+                $metadata->addPropertyConstraint('coordinates', new Assert\Unique(
+                    fields: ['latitude', 'longitude'],
+                ));
             }
         }
 
 .. include:: /reference/constraints/_groups-option.rst.inc
 
+``errorPath``
+~~~~~~~~~~~~~
+
+**type**: ``string`` **default**: ``null``
+
+.. versionadded:: 7.2
+
+    The ``errorPath`` option was introduced in Symfony 7.2.
+
+If a validation error occurs, the error message is, by default, bound to the
+first element in the collection. Use this option to bind the error message to a
+specific field within the first item of the collection.
+
+The value of this option must use any :doc:`valid PropertyAccess syntax </components/property_access>`
+(e.g. ``'point_of_interest'``, ``'user.email'``).
+
 ``message``
 ~~~~~~~~~~~
 
@@ -200,4 +216,17 @@ trailing whitespace during validation.
 
 .. include:: /reference/constraints/_payload-option.rst.inc
 
+``stopOnFirstError``
+~~~~~~~~~~~~~~~~~~~~
+
+**type**: ``boolean`` **default**: ``true``
+
+By default, this constraint stops at the first violation. If this option is set
+to ``false``, validation continues on all elements and returns all detected
+:class:`Symfony\\Component\\Validator\\ConstraintViolation` objects.
+
+.. versionadded:: 7.3
+
+    The ``stopOnFirstError`` option was introduced in Symfony 7.3.
+
 .. _`PHP callable`: https://www.php.net/callable
diff --git a/reference/constraints/UniqueEntity.rst b/reference/constraints/UniqueEntity.rst
index d4fbfeb8666..0ab2c0a8cbd 100644
--- a/reference/constraints/UniqueEntity.rst
+++ b/reference/constraints/UniqueEntity.rst
@@ -95,9 +95,9 @@ between all of the rows in your user table:
 
             public static function loadValidatorMetadata(ClassMetadata $metadata): void
             {
-                $metadata->addConstraint(new UniqueEntity([
-                    'fields' => 'email',
-                ]));
+                $metadata->addConstraint(new UniqueEntity(
+                    fields: 'email',
+                ));
 
                 $metadata->addPropertyConstraint('email', new Assert\Email());
             }
@@ -260,7 +260,7 @@ Now, the message would be bound to the ``port`` field with this configuration.
 ``fields``
 ~~~~~~~~~~
 
-**type**: ``array`` | ``string`` [:ref:`default option <validation-default-option>`]
+**type**: ``array`` | ``string``
 
 This required option is the field (or list of fields) on which this entity
 should be unique. For example, if you specified both the ``email`` and ``name``
@@ -277,7 +277,7 @@ each with a single field.
 ``ignoreNull``
 ~~~~~~~~~~~~~~
 
-**type**: ``boolean`` | ``string`` | ``array`` **default**: ``true``
+**type**: ``boolean``, ``string`` or ``array`` **default**: ``true``
 
 If this option is set to ``true``, then the constraint will allow multiple
 entities to have a ``null`` value for a field without failing validation.
@@ -346,10 +346,10 @@ this option to specify one or more fields to only ignore ``null`` values on them
         {
             public static function loadValidatorMetadata(ClassMetadata $metadata)
             {
-                $metadata->addConstraint(new UniqueEntity([
-                    'fields' => ['email', 'phoneNumber'],
-                    'ignoreNull' => 'phoneNumber',
-                ]));
+                $metadata->addConstraint(new UniqueEntity(
+                    fields: ['email', 'phoneNumber'],
+                    ignoreNull: 'phoneNumber',
+                ));
 
                 // ...
             }
diff --git a/reference/constraints/Url.rst b/reference/constraints/Url.rst
index 74f0d750dfd..c3fac520f96 100644
--- a/reference/constraints/Url.rst
+++ b/reference/constraints/Url.rst
@@ -152,9 +152,9 @@ Parameter        Description
 
             public static function loadValidatorMetadata(ClassMetadata $metadata): void
             {
-                $metadata->addPropertyConstraint('bioUrl', new Assert\Url([
-                    'message' => 'The url "{{ value }}" is not a valid url.',
-                ]));
+                $metadata->addPropertyConstraint('bioUrl', new Assert\Url(
+                    message: 'The url "{{ value }}" is not a valid url.',
+                ));
             }
         }
 
@@ -231,9 +231,9 @@ the ``ftp://`` type URLs to be valid, redefine the ``protocols`` array, listing
 
             public static function loadValidatorMetadata(ClassMetadata $metadata): void
             {
-                $metadata->addPropertyConstraint('bioUrl', new Assert\Url([
-                    'protocols' => ['http', 'https', 'ftp'],
-                ]));
+                $metadata->addPropertyConstraint('bioUrl', new Assert\Url(
+                    protocols: ['http', 'https', 'ftp'],
+                ));
             }
         }
 
@@ -302,9 +302,9 @@ also relative URLs that contain no protocol (e.g. ``//example.com``).
 
             public static function loadValidatorMetadata(ClassMetadata $metadata): void
             {
-                $metadata->addPropertyConstraint('bioUrl', new Assert\Url([
-                    'relativeProtocol' => true,
-                ]));
+                $metadata->addPropertyConstraint('bioUrl', new Assert\Url(
+                    relativeProtocol: true,
+                ));
             }
         }
 
@@ -323,7 +323,7 @@ also relative URLs that contain no protocol (e.g. ``//example.com``).
     and will default to ``true`` in Symfony 8.0.
 
 By default, URLs like ``https://aaa`` or ``https://foobar`` are considered valid
-because they are tecnically correct according to the `URL spec`_. If you set this option
+because they are technically correct according to the `URL spec`_. If you set this option
 to ``true``, the host part of the URL will have to include a TLD (top-level domain
 name): e.g. ``https://example.com`` will be valid but ``https://example`` won't.
 
@@ -414,10 +414,10 @@ Parameter        Description
 
             public static function loadValidatorMetadata(ClassMetadata $metadata): void
             {
-                $metadata->addPropertyConstraint('homepageUrl', new Assert\Url([
-                    'requireTld' => true,
-                    'tldMessage' => 'Add at least one TLD to the {{ value }} URL.',
-                ]));
+                $metadata->addPropertyConstraint('homepageUrl', new Assert\Url(
+                    requireTld: true,
+                    tldMessage: 'Add at least one TLD to the {{ value }} URL.',
+                ));
             }
         }
 
diff --git a/reference/constraints/Valid.rst b/reference/constraints/Valid.rst
index 1f99b666419..61a2c1d992c 100644
--- a/reference/constraints/Valid.rst
+++ b/reference/constraints/Valid.rst
@@ -149,7 +149,7 @@ stores an ``Address`` instance in the ``$address`` property::
             {
                 $metadata->addPropertyConstraint('street', new Assert\NotBlank());
                 $metadata->addPropertyConstraint('zipCode', new Assert\NotBlank());
-                $metadata->addPropertyConstraint('zipCode', new Assert\Length(['max' => 5]));
+                $metadata->addPropertyConstraint('zipCode', new Assert\Length(max: 5));
             }
         }
 
@@ -166,7 +166,7 @@ stores an ``Address`` instance in the ``$address`` property::
             public static function loadValidatorMetadata(ClassMetadata $metadata): void
             {
                 $metadata->addPropertyConstraint('firstName', new Assert\NotBlank());
-                $metadata->addPropertyConstraint('firstName', new Assert\Length(['min' => 4]));
+                $metadata->addPropertyConstraint('firstName', new Assert\Length(min: 4));
                 $metadata->addPropertyConstraint('lastName', new Assert\NotBlank());
             }
         }
diff --git a/reference/constraints/Week.rst b/reference/constraints/Week.rst
new file mode 100644
index 00000000000..b3c1b0ca122
--- /dev/null
+++ b/reference/constraints/Week.rst
@@ -0,0 +1,172 @@
+Week
+====
+
+.. versionadded:: 7.2
+
+    The ``Week`` constraint was introduced in Symfony 7.2.
+
+Validates that a given string (or an object implementing the ``Stringable`` PHP
+interface) represents a valid week number according to the `ISO-8601`_ standard
+(e.g. ``2025-W01``).
+
+==========  =======================================================================
+Applies to  :ref:`property or method <validation-property-target>`
+Class       :class:`Symfony\\Component\\Validator\\Constraints\\Week`
+Validator   :class:`Symfony\\Component\\Validator\\Constraints\\WeekValidator`
+==========  =======================================================================
+
+Basic Usage
+-----------
+
+If you wanted to ensure that the ``startWeek`` property of an ``OnlineCourse``
+class is between the first and the twentieth week of the year 2022, you could do
+the following:
+
+.. configuration-block::
+
+    .. code-block:: php-attributes
+
+        // src/Entity/OnlineCourse.php
+        namespace App\Entity;
+
+        use Symfony\Component\Validator\Constraints as Assert;
+
+        class OnlineCourse
+        {
+            #[Assert\Week(min: '2022-W01', max: '2022-W20')]
+            protected string $startWeek;
+        }
+
+    .. code-block:: yaml
+
+        # config/validator/validation.yaml
+        App\Entity\OnlineCourse:
+            properties:
+                startWeek:
+                    - Week:
+                        min: '2022-W01'
+                        max: '2022-W20'
+
+    .. code-block:: xml
+
+        <!-- config/validator/validation.xml -->
+        <?xml version="1.0" encoding="UTF-8" ?>
+        <constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping"
+            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+            xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping https://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd">
+
+            <class name="App\Entity\OnlineCourse">
+                <property name="startWeek">
+                    <constraint name="Week">
+                        <option name="min">2022-W01</option>
+                        <option name="max">2022-W20</option>
+                    </constraint>
+                </property>
+            </class>
+        </constraint-mapping>
+
+    .. code-block:: php
+
+        // src/Entity/OnlineCourse.php
+        namespace App\Entity;
+
+        use Symfony\Component\Validator\Constraints as Assert;
+        use Symfony\Component\Validator\Mapping\ClassMetadata;
+
+        class OnlineCourse
+        {
+            // ...
+
+            public static function loadValidatorMetadata(ClassMetadata $metadata): void
+            {
+                $metadata->addPropertyConstraint('startWeek', new Assert\Week(
+                    min: '2022-W01',
+                    max: '2022-W20',
+                ));
+            }
+        }
+
+This constraint not only checks that the value matches the week number pattern,
+but it also verifies that the specified week actually exists in the calendar.
+According to the ISO-8601 standard, years can have either 52 or 53 weeks. For example,
+``2022-W53`` is not valid because 2022 only had 52 weeks; but ``2020-W53`` is
+valid because 2020 had 53 weeks.
+
+Options
+-------
+
+``min``
+~~~~~~~
+
+**type**: ``string`` **default**: ``null``
+
+The minimum week number that the value must match.
+
+``max``
+~~~~~~~
+
+**type**: ``string`` **default**: ``null``
+
+The maximum week number that the value must match.
+
+.. include:: /reference/constraints/_groups-option.rst.inc
+
+``invalidFormatMessage``
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+**type**: ``string`` **default**: ``This value does not represent a valid week in the ISO 8601 format.``
+
+This is the message that will be shown if the value does not match the ISO 8601
+week format.
+
+``invalidWeekNumberMessage``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+**type**: ``string`` **default**: ``The week "{{ value }}" is not a valid week.``
+
+This is the message that will be shown if the value does not match a valid week
+number.
+
+You can use the following parameters in this message:
+
+================  ==================================================
+Parameter         Description
+================  ==================================================
+``{{ value }}``   The value that was passed to the constraint
+================  ==================================================
+
+``tooLowMessage``
+~~~~~~~~~~~~~~~~~
+
+**type**: ``string`` **default**: ``The value should not be before week "{{ min }}".``
+
+This is the message that will be shown if the value is lower than the minimum
+week number.
+
+You can use the following parameters in this message:
+
+================  ==================================================
+Parameter         Description
+================  ==================================================
+``{{ min }}``     The minimum week number
+================  ==================================================
+
+``tooHighMessage``
+~~~~~~~~~~~~~~~~~~
+
+**type**: ``string`` **default**: ``The value should not be after week "{{ max }}".``
+
+This is the message that will be shown if the value is higher than the maximum
+week number.
+
+You can use the following parameters in this message:
+
+================  ==================================================
+Parameter         Description
+================  ==================================================
+``{{ max }}``     The maximum week number
+================  ==================================================
+
+.. include:: /reference/constraints/_payload-option.rst.inc
+
+.. _`ISO-8601`: https://en.wikipedia.org/wiki/ISO_8601
diff --git a/reference/constraints/When.rst b/reference/constraints/When.rst
index e1e8ac895ce..4b2e8eb7590 100644
--- a/reference/constraints/When.rst
+++ b/reference/constraints/When.rst
@@ -9,6 +9,7 @@ Applies to  :ref:`class <validation-class-target>`
             or :ref:`property/method <validation-property-target>`
 Options     - `expression`_
             - `constraints`_
+            _ `otherwise`_
             - `groups`_
             - `payload`_
             - `values`_
@@ -47,7 +48,7 @@ properties::
 To validate the object, you have some requirements:
 
 A) If ``type`` is ``percent``, then ``value`` must be less than or equal 100;
-B) If ``type`` is ``absolute``, then ``value`` can be anything;
+B) If ``type`` is not ``percent``, then ``value`` must be less than 9999;
 C) No matter the value of ``type``, the ``value`` must be greater than 0.
 
 One way to accomplish this is with the When constraint:
@@ -69,6 +70,9 @@ One way to accomplish this is with the When constraint:
                 constraints: [
                     new Assert\LessThanOrEqual(100, message: 'The value should be between 1 and 100!')
                 ],
+                otherwise: [
+                    new Assert\LessThan(9999, message: 'The value should be less than 9999!')
+                ],
             )]
             private ?int $value;
 
@@ -88,6 +92,10 @@ One way to accomplish this is with the When constraint:
                             - LessThanOrEqual:
                                 value: 100
                                 message: "The value should be between 1 and 100!"
+                        otherwise:
+                            - LessThan:
+                                value: 9999
+                                message: "The value should be less than 9999!"
 
     .. code-block:: xml
 
@@ -109,6 +117,12 @@ One way to accomplish this is with the When constraint:
                                 <option name="message">The value should be between 1 and 100!</option>
                             </constraint>
                         </option>
+                        <option name="otherwise">
+                            <constraint name="LessThan">
+                                <option name="value">9999</option>
+                                <option name="message">The value should be less than 9999!</option>
+                            </constraint>
+                        </option>
                     </constraint>
                 </property>
             </class>
@@ -127,15 +141,21 @@ One way to accomplish this is with the When constraint:
             public static function loadValidatorMetadata(ClassMetadata $metadata): void
             {
                 $metadata->addPropertyConstraint('value', new Assert\GreaterThan(0));
-                $metadata->addPropertyConstraint('value', new Assert\When([
-                    'expression' => 'this.getType() == "percent"',
-                    'constraints' => [
-                        new Assert\LessThanOrEqual([
-                            'value' => 100,
-                            'message' => 'The value should be between 1 and 100!',
-                        ]),
+                $metadata->addPropertyConstraint('value', new Assert\When(
+                    expression: 'this.getType() == "percent"',
+                    constraints: [
+                        new Assert\LessThanOrEqual(
+                            value: 100,
+                            message: 'The value should be between 1 and 100!',
+                        ),
                     ],
-                ]));
+                    otherwise: [
+                        new Assert\LessThan(
+                            value: 9999,
+                            message: 'The value should be less than 9999!',
+                        ),
+                    ],
+                ));
             }
 
             // ...
@@ -154,26 +174,37 @@ Options
 ``expression``
 ~~~~~~~~~~~~~~
 
-**type**: ``string``
-
-The condition written with the expression language syntax that will be evaluated.
-If the expression evaluates to a falsey value (i.e. using ``==``, not ``===``),
-validation of constraints won't be triggered.
+**type**: ``string|Closure``
 
-To learn more about the expression language syntax, see
-:doc:`/reference/formats/expression_language`.
+The condition evaluated to decide if the constraint is applied or not. It can be
+defined as a closure or a string using the :doc:`expression language syntax </reference/formats/expression_language>`.
+If the result is a falsey value (``false``, ``null``, ``0``, an empty string or
+an empty array) the constraints defined in the ``constraints`` option won't be
+applied but the constraints defined in ``otherwise`` option (if provided) will be applied.
 
-Depending on how you use the constraint, you have access to 1 or 2 variables
-in your expression:
+**When using an expression**, you access to the following variables:
 
 ``this``
     The object being validated (e.g. an instance of Discount).
 ``value``
-    The value of the property being validated (only available when
-    the constraint is applied to a property).
+    Either the object being validated (when the constraint is applied to a class),
+    the value of the property being validated (when applied to a property),
+    or the :doc:`raw value </validation/raw_values>`.
+``context``
+    The :class:`Symfony\\Component\\Validator\\Context\\ExecutionContextInterface`
+    object that provides information such as the currently validated class, the
+    name of the currently validated property, the list of violations, etc.
 
-The ``value`` variable can be used when you want to execute more complex
-validation based on its value:
+.. versionadded:: 7.2
+
+    The ``context`` variable in expressions was introduced in Symfony 7.2.
+
+**When using a closure**, the first argument is the object being validated.
+
+.. versionadded:: 7.3
+
+    The support for closures in the ``expression`` option was introduced in Symfony 7.3
+    and requires PHP 8.5.
 
 .. configuration-block::
 
@@ -187,11 +218,21 @@ validation based on its value:
 
         class Discount
         {
+            // either using an expression...
             #[Assert\When(
                 expression: 'value == "percent"',
                 constraints: [new Assert\Callback('doComplexValidation')],
             )]
+
+            // ... or using a closure
+            #[Assert\When(
+                expression: static function (Discount $discount) {
+                    return $discount->getType() === 'percent';
+                },
+                constraints: [new Assert\Callback('doComplexValidation')],
+            )]
             private ?string $type;
+
             // ...
 
             public function doComplexValidation(ExecutionContextInterface $context, $payload): void
@@ -248,12 +289,12 @@ validation based on its value:
 
             public static function loadValidatorMetadata(ClassMetadata $metadata): void
             {
-                $metadata->addPropertyConstraint('type', new Assert\When([
-                    'expression' => 'value == "percent"',
-                    'constraints' => [
+                $metadata->addPropertyConstraint('type', new Assert\When(
+                    expression: 'value == "percent"',
+                    constraints: [
                         new Assert\Callback('doComplexValidation'),
                     ],
-                ]));
+                ));
             }
 
             public function doComplexValidation(ExecutionContextInterface $context, $payload): void
@@ -271,6 +312,17 @@ You can also pass custom variables using the `values`_ option.
 
 One or multiple constraints that are applied if the expression returns true.
 
+``otherwise``
+~~~~~~~~~~~~~
+
+**type**: ``array|Constraint``
+
+One or multiple constraints that are applied if the expression returns false.
+
+.. versionadded:: 7.3
+
+    The ``otherwise`` option was introduced in Symfony 7.3.
+
 .. include:: /reference/constraints/_groups-option.rst.inc
 
 .. include:: /reference/constraints/_payload-option.rst.inc
diff --git a/reference/constraints/WordCount.rst b/reference/constraints/WordCount.rst
new file mode 100644
index 00000000000..392f8a5bcb7
--- /dev/null
+++ b/reference/constraints/WordCount.rst
@@ -0,0 +1,150 @@
+WordCount
+=========
+
+.. versionadded:: 7.2
+
+    The ``WordCount`` constraint was introduced in Symfony 7.2.
+
+Validates that a string (or an object implementing the ``Stringable`` PHP interface)
+contains a given number of words. Internally, this constraint uses the
+:phpclass:`IntlBreakIterator` class to count the words depending on your locale.
+
+==========  =======================================================================
+Applies to  :ref:`property or method <validation-property-target>`
+Class       :class:`Symfony\\Component\\Validator\\Constraints\\WordCount`
+Validator   :class:`Symfony\\Component\\Validator\\Constraints\\WordCountValidator`
+==========  =======================================================================
+
+Basic Usage
+-----------
+
+If you wanted to ensure that the ``content`` property of a ``BlogPostDTO``
+class contains between 100 and 200 words, you could do the following:
+
+.. configuration-block::
+
+    .. code-block:: php-attributes
+
+        // src/Entity/BlogPostDTO.php
+        namespace App\Entity;
+
+        use Symfony\Component\Validator\Constraints as Assert;
+
+        class BlogPostDTO
+        {
+            #[Assert\WordCount(min: 100, max: 200)]
+            protected string $content;
+        }
+
+    .. code-block:: yaml
+
+        # config/validator/validation.yaml
+        App\Entity\BlogPostDTO:
+            properties:
+                content:
+                    - WordCount:
+                        min: 100
+                        max: 200
+
+    .. code-block:: xml
+
+        <!-- config/validator/validation.xml -->
+        <?xml version="1.0" encoding="UTF-8" ?>
+        <constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping"
+            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+            xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping https://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd">
+
+            <class name="App\Entity\BlogPostDTO">
+                <property name="content">
+                    <constraint name="WordCount">
+                        <option name="min">100</option>
+                        <option name="max">200</option>
+                    </constraint>
+                </property>
+            </class>
+        </constraint-mapping>
+
+    .. code-block:: php
+
+        // src/Entity/BlogPostDTO.php
+        namespace App\Entity;
+
+        use Symfony\Component\Validator\Constraints as Assert;
+        use Symfony\Component\Validator\Mapping\ClassMetadata;
+
+        class BlogPostDTO
+        {
+            // ...
+
+            public static function loadValidatorMetadata(ClassMetadata $metadata): void
+            {
+                $metadata->addPropertyConstraint('content', new Assert\WordCount(
+                    min: 100,
+                    max: 200,
+                ));
+            }
+        }
+
+Options
+-------
+
+``min``
+~~~~~~~
+
+**type**: ``integer`` **default**: ``null``
+
+The minimum number of words that the value must contain.
+
+``max``
+~~~~~~~
+
+**type**: ``integer`` **default**: ``null``
+
+The maximum number of words that the value must contain.
+
+``locale``
+~~~~~~~~~~
+
+**type**: ``string`` **default**: ``null``
+
+The locale to use for counting the words by using the :phpclass:`IntlBreakIterator`
+class. The default value (``null``) means that the constraint uses the current
+user locale.
+
+.. include:: /reference/constraints/_groups-option.rst.inc
+
+``minMessage``
+~~~~~~~~~~~~~~
+
+**type**: ``string`` **default**: ``This value is too short. It should contain at least one word.|This value is too short. It should contain at least {{ min }} words.``
+
+This is the message that will be shown if the value does not contain at least
+the minimum number of words.
+
+You can use the following parameters in this message:
+
+================  ==================================================
+Parameter         Description
+================  ==================================================
+``{{ min }}``     The minimum number of words
+``{{ count }}``   The actual number of words
+================  ==================================================
+
+``maxMessage``
+~~~~~~~~~~~~~~
+
+**type**: ``string`` **default**: ``This value is too long. It should contain one word.|This value is too long. It should contain {{ max }} words or less.``
+
+This is the message that will be shown if the value contains more than the
+maximum number of words.
+
+You can use the following parameters in this message:
+
+================  ==================================================
+Parameter         Description
+================  ==================================================
+``{{ max }}``     The maximum number of words
+``{{ count }}``   The actual number of words
+================  ==================================================
+
+.. include:: /reference/constraints/_payload-option.rst.inc
diff --git a/reference/constraints/Yaml.rst b/reference/constraints/Yaml.rst
new file mode 100644
index 00000000000..0d1564f4f8a
--- /dev/null
+++ b/reference/constraints/Yaml.rst
@@ -0,0 +1,152 @@
+Yaml
+====
+
+Validates that a value has valid `YAML`_ syntax.
+
+.. versionadded:: 7.2
+
+    The ``Yaml`` constraint was introduced in Symfony 7.2.
+
+==========  ===================================================================
+Applies to  :ref:`property or method <validation-property-target>`
+Class       :class:`Symfony\\Component\\Validator\\Constraints\\Yaml`
+Validator   :class:`Symfony\\Component\\Validator\\Constraints\\YamlValidator`
+==========  ===================================================================
+
+Basic Usage
+-----------
+
+The ``Yaml`` constraint can be applied to a property or a "getter" method:
+
+.. configuration-block::
+
+    .. code-block:: php-attributes
+
+        // src/Entity/Report.php
+        namespace App\Entity;
+
+        use Symfony\Component\Validator\Constraints as Assert;
+
+        class Report
+        {
+            #[Assert\Yaml(
+                message: "Your configuration doesn't have valid YAML syntax."
+            )]
+            private string $customConfiguration;
+        }
+
+    .. code-block:: yaml
+
+        # config/validator/validation.yaml
+        App\Entity\Report:
+            properties:
+                customConfiguration:
+                    - Yaml:
+                        message: Your configuration doesn't have valid YAML syntax.
+
+    .. code-block:: xml
+
+        <!-- config/validator/validation.xml -->
+        <?xml version="1.0" encoding="UTF-8" ?>
+        <constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping"
+            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+            xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping https://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd">
+
+            <class name="App\Entity\Report">
+                <property name="customConfiguration">
+                    <constraint name="Yaml">
+                        <option name="message">Your configuration doesn't have valid YAML syntax.</option>
+                    </constraint>
+                </property>
+            </class>
+        </constraint-mapping>
+
+    .. code-block:: php
+
+        // src/Entity/Report.php
+        namespace App\Entity;
+
+        use Symfony\Component\Validator\Constraints as Assert;
+        use Symfony\Component\Validator\Mapping\ClassMetadata;
+
+        class Report
+        {
+            public static function loadValidatorMetadata(ClassMetadata $metadata): void
+            {
+                $metadata->addPropertyConstraint('customConfiguration', new Assert\Yaml(
+                    message: 'Your configuration doesn\'t have valid YAML syntax.',
+                ));
+            }
+        }
+
+Options
+-------
+
+``flags``
+~~~~~~~~~
+
+**type**: ``integer`` **default**: ``0``
+
+This option enables optional features of the YAML parser when validating contents.
+Its value is a combination of one or more of the :ref:`flags defined by the Yaml component <yaml-flags>`:
+
+.. configuration-block::
+
+    .. code-block:: php-attributes
+
+        // src/Entity/Report.php
+        namespace App\Entity;
+
+        use Symfony\Component\Validator\Constraints as Assert;
+        use Symfony\Component\Yaml\Yaml;
+
+        class Report
+        {
+            #[Assert\Yaml(
+                message: "Your configuration doesn't have valid YAML syntax.",
+                flags: Yaml::PARSE_CONSTANT | Yaml::PARSE_CUSTOM_TAGS | Yaml::PARSE_DATETIME,
+            )]
+            private string $customConfiguration;
+        }
+
+    .. code-block:: php
+
+        // src/Entity/Report.php
+        namespace App\Entity;
+
+        use Symfony\Component\Validator\Constraints as Assert;
+        use Symfony\Component\Validator\Mapping\ClassMetadata;
+        use Symfony\Component\Yaml\Yaml;
+
+        class Report
+        {
+            public static function loadValidatorMetadata(ClassMetadata $metadata): void
+            {
+                $metadata->addPropertyConstraint('customConfiguration', new Assert\Yaml(
+                    message: 'Your configuration doesn\'t have valid YAML syntax.',
+                    flags: Yaml::PARSE_CONSTANT | Yaml::PARSE_CUSTOM_TAGS | Yaml::PARSE_DATETIME,
+                ));
+            }
+        }
+
+``message``
+~~~~~~~~~~~
+
+**type**: ``string`` **default**: ``This value is not valid YAML.``
+
+This message shown if the underlying data is not a valid YAML value.
+
+You can use the following parameters in this message:
+
+===============  ==============================================================
+Parameter        Description
+===============  ==============================================================
+``{{ error }}``  The full error message from the YAML parser
+``{{ line }}``   The line where the YAML syntax error happened
+===============  ==============================================================
+
+.. include:: /reference/constraints/_groups-option.rst.inc
+
+.. include:: /reference/constraints/_payload-option.rst.inc
+
+.. _`YAML`: https://en.wikipedia.org/wiki/YAML
diff --git a/reference/constraints/_comparison-value-option.rst.inc b/reference/constraints/_comparison-value-option.rst.inc
index c8abdfb5af0..91ab28a2e94 100644
--- a/reference/constraints/_comparison-value-option.rst.inc
+++ b/reference/constraints/_comparison-value-option.rst.inc
@@ -1,7 +1,7 @@
 ``value``
 ~~~~~~~~~
 
-**type**: ``mixed`` [:ref:`default option <validation-default-option>`]
+**type**: ``mixed``
 
 This option is required. It defines the comparison value. It can be a
 string, number or object.
diff --git a/reference/constraints/map.rst.inc b/reference/constraints/map.rst.inc
index 706742dfde5..06680e42207 100644
--- a/reference/constraints/map.rst.inc
+++ b/reference/constraints/map.rst.inc
@@ -33,10 +33,13 @@ String Constraints
 * :doc:`NotCompromisedPassword </reference/constraints/NotCompromisedPassword>`
 * :doc:`PasswordStrength </reference/constraints/PasswordStrength>`
 * :doc:`Regex </reference/constraints/Regex>`
+* :doc:`Twig </reference/constraints/Twig>`
 * :doc:`Ulid </reference/constraints/Ulid>`
 * :doc:`Url </reference/constraints/Url>`
 * :doc:`UserPassword </reference/constraints/UserPassword>`
 * :doc:`Uuid </reference/constraints/Uuid>`
+* :doc:`WordCount </reference/constraints/WordCount>`
+* :doc:`Yaml </reference/constraints/Yaml>`
 
 Comparison Constraints
 ~~~~~~~~~~~~~~~~~~~~~~
@@ -70,6 +73,7 @@ Date Constraints
 * :doc:`DateTime </reference/constraints/DateTime>`
 * :doc:`Time </reference/constraints/Time>`
 * :doc:`Timezone </reference/constraints/Timezone>`
+* :doc:`Week </reference/constraints/Week>`
 
 Choice Constraints
 ~~~~~~~~~~~~~~~~~~
diff --git a/reference/formats/expression_language.rst b/reference/formats/expression_language.rst
index a23440c5715..02d36ba318f 100644
--- a/reference/formats/expression_language.rst
+++ b/reference/formats/expression_language.rst
@@ -20,6 +20,11 @@ The component supports:
 * **booleans** - ``true`` and ``false``
 * **null** - ``null``
 * **exponential** - also known as scientific (e.g. ``1.99E+3`` or ``1e-2``)
+* **comments** - using ``/*`` and ``*/`` (e.g. ``/* this is a comment */``)
+
+.. versionadded:: 7.2
+
+    The support for comments inside expressions was introduced in Symfony 7.2.
 
 .. warning::
 
@@ -124,11 +129,10 @@ returns the right-hand side. Expressions can chain multiple coalescing operators
 * ``foo[3] ?? 'no'``
 * ``foo.baz ?? foo['baz'] ?? 'no'``
 
-.. note::
+.. versionadded:: 7.2
 
-    The main difference with the `null-coalescing operator in PHP`_ is that
-    ExpressionLanguage will throw an exception when trying to access a
-    non-existent variable.
+    Starting from Symfony 7.2, no exception is thrown when trying to access a
+    non-existent variable. This is the same behavior as the `null-coalescing operator in PHP`_.
 
 .. _component-expression-functions:
 
@@ -167,7 +171,7 @@ This also works with class constants::
     }
 
     var_dump($expressionLanguage->evaluate(
-        'constant("App\\\SomeNamespace\\\Foo::API_ENDPOINT")'
+        'constant("App\\\\SomeNamespace\\\\Foo::API_ENDPOINT")'
     ));
 
 This will print out ``/api``.
@@ -185,7 +189,7 @@ This function will return the case of an enumeration::
     }
 
     var_dump(App\Enum\Foo::Bar === $expressionLanguage->evaluate(
-        'enum("App\\\SomeNamespace\\\Foo::Bar")'
+        'enum("App\\\\SomeNamespace\\\\Foo::Bar")'
     ));
 
 This will print out ``true``.
@@ -280,6 +284,14 @@ Bitwise Operators
 * ``&`` (and)
 * ``|`` (or)
 * ``^`` (xor)
+* ``~`` (not)
+* ``<<`` (left shift)
+* ``>>`` (right shift)
+
+.. versionadded:: 7.2
+
+    Support for the ``~``, ``<<`` and ``>>`` bitwise operators was introduced
+    in Symfony 7.2.
 
 Comparison Operators
 ~~~~~~~~~~~~~~~~~~~~
@@ -333,6 +345,11 @@ Logical Operators
 * ``not`` or ``!``
 * ``and`` or ``&&``
 * ``or`` or ``||``
+* ``xor``
+
+.. versionadded:: 7.2
+
+    Support for the ``xor`` logical operator was introduced in Symfony 7.2.
 
 For example::
 
@@ -445,38 +462,40 @@ parentheses in your expressions (e.g. ``(1 + 2) * 4`` or ``1 + (2 * 4)``.
 The following table summarizes the operators and their associativity from the
 **highest to the lowest precedence**:
 
-+----------------------------------------------------------+---------------+
-| Operators                                                | Associativity |
-+==========================================================+===============+
-| ``-`` , ``+`` (unary operators that add the number sign) | none          |
-+----------------------------------------------------------+---------------+
-| ``**``                                                   | right         |
-+----------------------------------------------------------+---------------+
-| ``*``, ``/``, ``%``                                      | left          |
-+----------------------------------------------------------+---------------+
-| ``not``, ``!``                                           | none          |
-+----------------------------------------------------------+---------------+
-| ``~``                                                    | left          |
-+----------------------------------------------------------+---------------+
-| ``+``, ``-``                                             | left          |
-+----------------------------------------------------------+---------------+
-| ``..``                                                   | left          |
-+----------------------------------------------------------+---------------+
-| ``==``, ``===``, ``!=``, ``!==``,                        | left          |
-| ``<``, ``>``, ``>=``, ``<=``,                            |               |
-| ``not in``, ``in``, ``contains``,                        |               |
-| ``starts with``, ``ends with``, ``matches``              |               |
-+----------------------------------------------------------+---------------+
-| ``&``                                                    | left          |
-+----------------------------------------------------------+---------------+
-| ``^``                                                    | left          |
-+----------------------------------------------------------+---------------+
-| ``|``                                                    | left          |
-+----------------------------------------------------------+---------------+
-| ``and``, ``&&``                                          | left          |
-+----------------------------------------------------------+---------------+
-| ``or``, ``||``                                           | left          |
-+----------------------------------------------------------+---------------+
++-----------------------------------------------------------------+---------------+
+| Operators                                                       | Associativity |
++=================================================================+===============+
+| ``-`` , ``+``, ``~`` (unary operators that add the number sign) | none          |
++-----------------------------------------------------------------+---------------+
+| ``**``                                                          | right         |
++-----------------------------------------------------------------+---------------+
+| ``*``, ``/``, ``%``                                             | left          |
++-----------------------------------------------------------------+---------------+
+| ``not``, ``!``                                                  | none          |
++-----------------------------------------------------------------+---------------+
+| ``~``                                                           | left          |
++-----------------------------------------------------------------+---------------+
+| ``+``, ``-``                                                    | left          |
++-----------------------------------------------------------------+---------------+
+| ``..``, ``<<``, ``>>``                                          | left          |
++-----------------------------------------------------------------+---------------+
+| ``==``, ``===``, ``!=``, ``!==``,                               | left          |
+| ``<``, ``>``, ``>=``, ``<=``,                                   |               |
+| ``not in``, ``in``, ``contains``,                               |               |
+| ``starts with``, ``ends with``, ``matches``                     |               |
++-----------------------------------------------------------------+---------------+
+| ``&``                                                           | left          |
++-----------------------------------------------------------------+---------------+
+| ``^``                                                           | left          |
++-----------------------------------------------------------------+---------------+
+| ``|``                                                           | left          |
++-----------------------------------------------------------------+---------------+
+| ``and``, ``&&``                                                 | left          |
++-----------------------------------------------------------------+---------------+
+| ``xor``                                                         | left          |
++-----------------------------------------------------------------+---------------+
+| ``or``, ``||``                                                  | left          |
++-----------------------------------------------------------------+---------------+
 
 Built-in Objects and Variables
 ------------------------------
diff --git a/reference/formats/xliff.rst b/reference/formats/xliff.rst
index acb9af36014..b5dc99b4186 100644
--- a/reference/formats/xliff.rst
+++ b/reference/formats/xliff.rst
@@ -29,7 +29,7 @@ loaded/dumped inside a Symfony application:
                     <note category="approved">true</note>
                     <note category="section" priority="1">user login</note>
                 </notes>
-                <segment>
+                <segment state="translated" subState="Some custom value">
                     <source>original-content</source>
                     <target>translated-content</target>
                 </segment>
@@ -37,4 +37,8 @@ loaded/dumped inside a Symfony application:
         </file>
     </xliff>
 
+.. versionadded:: 7.2
+
+    The support of attributes in the ``<segment>`` element was introduced in Symfony 7.2.
+
 .. _XLIFF: https://docs.oasis-open.org/xliff/xliff-core/v2.1/xliff-core-v2.1.html
diff --git a/reference/forms/types/choice.rst b/reference/forms/types/choice.rst
index 5ec8aac0ff8..9f61fb768bd 100644
--- a/reference/forms/types/choice.rst
+++ b/reference/forms/types/choice.rst
@@ -178,6 +178,8 @@ correct types will be assigned to the model.
 
 .. include:: /reference/forms/types/options/choice_loader.rst.inc
 
+.. include:: /reference/forms/types/options/choice_lazy.rst.inc
+
 .. include:: /reference/forms/types/options/choice_name.rst.inc
 
 .. include:: /reference/forms/types/options/choice_translation_domain_enabled.rst.inc
diff --git a/reference/forms/types/date.rst b/reference/forms/types/date.rst
index f28aae474b9..210fff5dd0d 100644
--- a/reference/forms/types/date.rst
+++ b/reference/forms/types/date.rst
@@ -153,6 +153,20 @@ values for the year, month and day fields::
 
 .. include:: /reference/forms/types/options/view_timezone.rst.inc
 
+``calendar``
+~~~~~~~~~~~~
+
+**type**: ``integer`` or ``\IntlCalendar`` **default**: ``null``
+
+The calendar to use for formatting and parsing the date. The value should be
+an ``integer`` from :phpclass:`IntlDateFormatter` calendar constants or an instance
+of the :phpclass:`IntlCalendar` to use. By default, the Gregorian calendar
+with the application default locale is used.
+
+.. versionadded:: 7.2
+
+    The ``calendar`` option was introduced in Symfony 7.2.
+
 .. include:: /reference/forms/types/options/date_widget.rst.inc
 
 .. include:: /reference/forms/types/options/years.rst.inc
diff --git a/reference/forms/types/enum.rst b/reference/forms/types/enum.rst
index 875c0808108..b786fb68125 100644
--- a/reference/forms/types/enum.rst
+++ b/reference/forms/types/enum.rst
@@ -94,6 +94,22 @@ Inherited Options
 
 These options inherit from the :doc:`ChoiceType </reference/forms/types/choice>`:
 
+.. include:: /reference/forms/types/options/choice_attr.rst.inc
+
+.. include:: /reference/forms/types/options/choice_filter.rst.inc
+
+.. include:: /reference/forms/types/options/choice_label.rst.inc
+
+.. include:: /reference/forms/types/options/choice_loader.rst.inc
+
+.. include:: /reference/forms/types/options/choice_name.rst.inc
+
+.. include:: /reference/forms/types/options/choice_translation_domain_enabled.rst.inc
+
+.. include:: /reference/forms/types/options/choice_translation_parameters.rst.inc
+
+.. include:: /reference/forms/types/options/choice_value.rst.inc
+
 .. include:: /reference/forms/types/options/error_bubbling.rst.inc
 
 .. include:: /reference/forms/types/options/error_mapping.rst.inc
diff --git a/reference/forms/types/money.rst b/reference/forms/types/money.rst
index a02b695abd4..967fe9e4ce4 100644
--- a/reference/forms/types/money.rst
+++ b/reference/forms/types/money.rst
@@ -83,6 +83,9 @@ input
 By default, the money value is converted to a ``float`` PHP type. If you need the
 value to be converted into an integer (e.g. because some library needs money
 values stored in cents as integers) set this option to ``integer``.
+You can also set this option to ``string``, it can be useful if the underlying
+data is a string for precision reasons (for example, Doctrine uses strings for
+the decimal type).
 
 .. versionadded:: 7.1
 
diff --git a/reference/forms/types/options/choice_lazy.rst.inc b/reference/forms/types/options/choice_lazy.rst.inc
new file mode 100644
index 00000000000..08fbe953e41
--- /dev/null
+++ b/reference/forms/types/options/choice_lazy.rst.inc
@@ -0,0 +1,31 @@
+``choice_lazy``
+~~~~~~~~~~~~~~~
+
+**type**: ``boolean`` **default**: ``false``
+
+.. versionadded:: 7.2
+
+    The ``choice_lazy`` option was introduced in Symfony 7.2.
+
+The ``choice_lazy`` option is particularly useful when dealing with a large set
+of choices, where loading them all at once could cause performance issues or
+delays::
+
+    use App\Entity\User;
+    use Symfony\Bridge\Doctrine\Form\Type\EntityType;
+
+    $builder->add('user', EntityType::class, [
+        'class' => User::class,
+        'choice_lazy' => true,
+    ]);
+
+When set to ``true`` and used alongside the ``choice_loader`` option, the form
+will only load and render the choices that are preset as default values or
+submitted. This defers the loading of the full list of choices, helping to
+improve your form's performance.
+
+.. warning::
+
+    Keep in mind that when using ``choice_lazy``, you are responsible for
+    providing the user interface for selecting choices, typically through a
+    JavaScript plugin capable of dynamically loading choices.
diff --git a/reference/forms/types/options/choice_value.rst.inc b/reference/forms/types/options/choice_value.rst.inc
index 4b3668074a9..137ca8a6df0 100644
--- a/reference/forms/types/options/choice_value.rst.inc
+++ b/reference/forms/types/options/choice_value.rst.inc
@@ -9,7 +9,7 @@ You don't normally need to worry about this, but it might be handy when processi
 an API request (since you can configure the value that will be sent in the API request).
 
 This can be a callable or a property path. By default, the choices are used if they
-can be casted to strings. Otherwise an incrementing integer is used (starting at ``0``).
+can be cast to strings. Otherwise an incrementing integer is used (starting at ``0``).
 
 If you pass a callable, it will receive one argument: the choice itself. When using
 the :doc:`/reference/forms/types/entity`, the argument will be the entity object
diff --git a/reference/forms/types/options/validation_groups.rst.inc b/reference/forms/types/options/validation_groups.rst.inc
index 1f5c9a597a3..6957bf203a3 100644
--- a/reference/forms/types/options/validation_groups.rst.inc
+++ b/reference/forms/types/options/validation_groups.rst.inc
@@ -1,59 +1,14 @@
 ``validation_groups``
 ~~~~~~~~~~~~~~~~~~~~~
 
-**type**: ``array``, ``string``, ``callable``, :class:`Symfony\\Component\\Validator\\Constraints\\GroupSequence` or ``null`` **default**: ``null``
+**type**: ``array``, ``string``, ``callable``, :class:`Symfony\\Component\\Validator\\Constraints\\GroupSequence`, or ``null`` **default**: ``null``
 
-This option is only valid on the root form and is used to specify which
-groups will be used by the validator.
+This option is only valid on the root form. It specifies which validation groups
+will be used by the validator.
 
-For ``null`` the validator will just use the ``Default`` group.
-
-If you specify the groups as an array or string they will be used by the
-validator as they are::
-
-    public function configureOptions(OptionsResolver $resolver): void
-    {
-        $resolver->setDefaults([
-            'validation_groups' => 'Registration',
-        ]);
-    }
-
-This is equivalent to passing the group as array::
-
-    'validation_groups' => ['Registration'],
-
-The form's data will be :doc:`validated against all given groups </form/validation_groups>`.
-
-If the validation groups depend on the form's data a callable may be passed to
-the option. Symfony will then pass the form when calling it::
-
-    use Symfony\Component\Form\FormInterface;
-    use Symfony\Component\OptionsResolver\OptionsResolver;
-
-    // ...
-    public function configureOptions(OptionsResolver $resolver): void
-    {
-        $resolver->setDefaults([
-            'validation_groups' => function (FormInterface $form): array {
-                $entity = $form->getData();
-
-                return $entity->isUser() ? ['User'] : ['Company'];
-            },
-        ]);
-    }
-
-.. seealso::
-
-    You can read more about this in :doc:`/form/data_based_validation`.
-
-.. note::
-
-    When your form contains multiple submit buttons, you can change the
-    validation group depending on :doc:`which button is used </form/button_based_validation>`
-    to submit the form.
-
-    If you need advanced logic to determine the validation groups have
-    a look at :doc:`/form/validation_group_service_resolver`.
+If set to ``null``, the validator will use only the ``Default`` group. For the
+other possible values, see the main article about
+:doc:`using validation groups in Symfony forms </form/validation_groups>`
 
 In some cases, you want to validate your groups step by step. To do this, you
 can pass a :class:`Symfony\\Component\\Validator\\Constraints\\GroupSequence`
diff --git a/reference/forms/types/submit.rst b/reference/forms/types/submit.rst
index 70fa429685a..5d863fbe8b4 100644
--- a/reference/forms/types/submit.rst
+++ b/reference/forms/types/submit.rst
@@ -102,28 +102,8 @@ validation_groups
 **type**: ``array`` **default**: ``null``
 
 When your form contains multiple submit buttons, you can change the validation
-group based on the button which was used to submit the form. Imagine a registration
-form wizard with buttons to go to the previous or the next step::
-
-    use Symfony\Component\Form\Extension\Core\Type\SubmitType;
-    // ...
-
-    $form = $this->createFormBuilder($user)
-        ->add('previousStep', SubmitType::class, [
-            'validation_groups' => false,
-        ])
-        ->add('nextStep', SubmitType::class, [
-            'validation_groups' => ['Registration'],
-        ])
-        ->getForm();
-
-The special ``false`` ensures that no validation is performed when the previous
-step button is clicked. When the second button is clicked, all constraints
-from the "Registration" are validated.
-
-.. seealso::
-
-    You can read more about this in :doc:`/form/data_based_validation`.
+group based on the clicked button. Read the article about
+:doc:`using validation groups in Symfony forms </form/validation_groups>`.
 
 Form Variables
 --------------
diff --git a/reference/twig_reference.rst b/reference/twig_reference.rst
index d8b802dd73e..633d4c7f0c6 100644
--- a/reference/twig_reference.rst
+++ b/reference/twig_reference.rst
@@ -96,6 +96,11 @@ Returns an instance of ``ControllerReference`` to be used with functions
 like :ref:`render() <reference-twig-function-render>` and
 :ref:`render_esi() <reference-twig-function-render-esi>`.
 
+.. code-block:: twig
+
+    {{ render(controller('App\\Controller\\BlogController:latest', {max: 3})) }}
+    {# output: the content returned by the controller method; e.g. a rendered Twig template #}
+
 .. _reference-twig-function-asset:
 
 asset
@@ -110,6 +115,22 @@ asset
 ``packageName`` *(optional)*
     **type**: ``string`` | ``null`` **default**: ``null``
 
+.. code-block:: yaml
+
+    # config/packages/framework.yaml
+    framework:
+        # ...
+        assets:
+            packages:
+                foo_package:
+                    base_path: /avatars
+
+.. code-block:: twig
+
+    {# the image lives at "public/avatars/avatar.png" #}
+    {{ asset(path = 'avatar.png', packageName = 'foo_package') }}
+    {# output: /avatars/avatar.png #}
+
 Returns the public path of the given asset path (which can be a CSS file, a
 JavaScript file, an image path, etc.). This function takes into account where
 the application is installed (e.g. in case the project is accessed in a host
@@ -155,25 +176,52 @@ csrf_token
 Renders a CSRF token. Use this function if you want :doc:`CSRF protection </security/csrf>`
 in a regular HTML form not managed by the Symfony Form component.
 
+.. code-block:: twig
+
+    {{ csrf_token('my_form') }}
+    {# output: a random alphanumeric string like:
+       a.YOosAd0fhT7BEuUCFbROzrvgkW8kpEmBDQ_DKRMUi2o.Va8ZQKt5_2qoa7dLW-02_PLYwDBx9nnWOluUHUFCwC5Zo0VuuVfQCqtngg #}
+
 is_granted
 ~~~~~~~~~~
 
 .. code-block:: twig
 
-    {{ is_granted(role, object = null, field = null) }}
+    {{ is_granted(role, object = null) }}
 
 ``role``
     **type**: ``string``
 ``object`` *(optional)*
     **type**: ``object``
-``field`` *(optional)*
-    **type**: ``string``
 
 Returns ``true`` if the current user has the given role.
 
 Optionally, an object can be passed to be used by the voter. More information
 can be found in :ref:`security-template`.
 
+is_granted_for_user
+~~~~~~~~~~~~~~~~~~~
+
+.. versionadded:: 7.3
+
+    The ``is_granted_for_user()`` function was introduced in Symfony 7.3.
+
+.. code-block:: twig
+
+    {{ is_granted_for_user(user, attribute, subject = null) }}
+
+``user``
+    **type**: ``object``
+``attribute``
+    **type**: ``string``
+``subject`` *(optional)*
+    **type**: ``object``
+
+Returns ``true`` if the user is authorized for the specified attribute.
+
+Optionally, an object can be passed to be used by the voter. More information
+can be found in :ref:`security-template`.
+
 logout_path
 ~~~~~~~~~~~
 
@@ -187,6 +235,30 @@ logout_path
 Generates a relative logout URL for the given firewall. If no key is provided,
 the URL is generated for the current firewall the user is logged into.
 
+.. code-block:: yaml
+
+    # config/packages/security.yaml
+    security:
+        # ...
+
+        firewalls:
+            main:
+                # ...
+                logout:
+                    path: '/logout'
+            othername:
+                # ...
+                logout:
+                    path: '/other/logout'
+
+.. code-block:: twig
+
+    {{ logout_path(key = 'main') }}
+    {# output: /logout #}
+
+    {{ logout_path(key = 'othername') }}
+    {# output: /other/logout #}
+
 logout_url
 ~~~~~~~~~~
 
@@ -200,6 +272,30 @@ logout_url
 Equal to the `logout_path`_ function, but it'll generate an absolute URL
 instead of a relative one.
 
+.. code-block:: yaml
+
+    # config/packages/security.yaml
+    security:
+        # ...
+
+        firewalls:
+            main:
+                # ...
+                logout:
+                    path: '/logout'
+            othername:
+                # ...
+                logout:
+                    path: '/other/logout'
+
+.. code-block:: twig
+
+    {{ logout_url(key = 'main') }}
+    {# output: http://example.org/logout #}
+
+    {{ logout_url(key = 'othername') }}
+    {# output: http://example.org/other/logout #}
+
 path
 ~~~~
 
@@ -217,6 +313,16 @@ path
 Returns the relative URL (without the scheme and host) for the given route.
 If ``relative`` is enabled, it'll create a path relative to the current path.
 
+.. code-block:: twig
+
+    {# consider that the app defines an 'app_blog' route with the path '/blog/{page}' #}
+
+    {{ path(name = 'app_blog', parameters = {page: 3}, relative = false) }}
+    {# output: /blog/3 #}
+
+    {{ path(name = 'app_blog', parameters = {page: 3}, relative = true) }}
+    {# output: blog/3 #}
+
 .. seealso::
 
     Read more about :doc:`Symfony routing </routing>` and about
@@ -239,6 +345,16 @@ url
 Returns the absolute URL (with scheme and host) for the given route. If
 ``schemeRelative`` is enabled, it'll create a scheme-relative URL.
 
+.. code-block:: twig
+
+    {# consider that the app defines an 'app_blog' route with the path '/blog/{page}' #}
+
+    {{ url(name = 'app_blog', parameters = {page: 3}, schemeRelative = false) }}
+    {# output: http://example.org/blog/3 #}
+
+    {{ url(name = 'app_blog', parameters = {page: 3}, schemeRelative = true) }}
+    {# output: //example.org/blog/3 #}
+
 .. seealso::
 
     Read more about :doc:`Symfony routing </routing>` and about
@@ -290,6 +406,11 @@ expression
 Creates an :class:`Symfony\\Component\\ExpressionLanguage\\Expression` related
 to the :doc:`ExpressionLanguage component </components/expression_language>`.
 
+.. code-block:: twig
+
+    {{ expression(1 + 2) }}
+    {# output: 3 #}
+
 impersonation_path
 ~~~~~~~~~~~~~~~~~~
 
@@ -365,6 +486,42 @@ t
 Creates a ``Translatable`` object that can be passed to the
 :ref:`trans filter <reference-twig-filter-trans>`.
 
+.. configuration-block::
+
+    .. code-block:: yaml
+
+        # translations/blog.en.yaml
+        message: Hello %name%
+
+    .. code-block:: xml
+
+        <!-- translations/blog.en.xlf -->
+        <?xml version="1.0" encoding="UTF-8" ?>
+        <xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
+            <file source-language="en" datatype="plaintext" original="file.ext">
+                <body>
+                    <trans-unit id="message">
+                        <source>message</source>
+                        <target>Hello %name%</target>
+                    </trans-unit>
+                </body>
+            </file>
+        </xliff>
+
+    .. code-block:: php
+
+        // translations/blog.en.php
+        return [
+            'message' => "Hello %name%",
+        ];
+
+Using the filter will be rendered as:
+
+.. code-block:: twig
+
+    {{ t(message = 'message', parameters = {'%name%': 'John'}, domain = 'blog')|trans }}
+    {# output: Hello John #}
+
 importmap
 ~~~~~~~~~
 
@@ -387,6 +544,7 @@ explained in the article about :doc:`customizing form rendering </form/form_cust
 * :ref:`form_row() <reference-forms-twig-row>`
 * :ref:`form_rest() <reference-forms-twig-rest>`
 * :ref:`field_name() <reference-forms-twig-field-helpers>`
+* :ref:`field_id() <reference-forms-twig-field-helpers>`
 * :ref:`field_value() <reference-forms-twig-field-helpers>`
 * :ref:`field_label() <reference-forms-twig-field-helpers>`
 * :ref:`field_help() <reference-forms-twig-field-helpers>`
@@ -444,6 +602,42 @@ trans
 Translates the text into the current language. More information in
 :ref:`Translation Filters <translation-filters>`.
 
+.. configuration-block::
+
+    .. code-block:: yaml
+
+        # translations/messages.en.yaml
+        message: Hello %name%
+
+    .. code-block:: xml
+
+        <!-- translations/messages.en.xlf -->
+        <?xml version="1.0" encoding="UTF-8" ?>
+        <xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
+            <file source-language="en" datatype="plaintext" original="file.ext">
+                <body>
+                    <trans-unit id="message">
+                        <source>message</source>
+                        <target>Hello %name%</target>
+                    </trans-unit>
+                </body>
+            </file>
+        </xliff>
+
+    .. code-block:: php
+
+        // translations/messages.en.php
+        return [
+            'message' => "Hello %name%",
+        ];
+
+Using the filter will be rendered as:
+
+.. code-block:: twig
+
+    {{ 'message'|trans(arguments = {'%name%': 'John'}, domain = 'messages', locale = 'en') }}
+    {# output: Hello John #}
+
 sanitize_html
 ~~~~~~~~~~~~~
 
@@ -581,6 +775,16 @@ abbr_class
 Generates an ``<abbr>`` element with the short name of a PHP class (the
 FQCN will be shown in a tooltip when a user hovers over the element).
 
+.. code-block:: twig
+
+    {{ 'App\\Entity\\Product'|abbr_class }}
+
+The above example will be rendered as:
+
+.. code-block:: html
+
+    <abbr title="App\Entity\Product">Product</abbr>
+
 abbr_method
 ~~~~~~~~~~~
 
@@ -595,6 +799,16 @@ Generates an ``<abbr>`` element using the ``FQCN::method()`` syntax. If
 ``method`` is ``Closure``, ``Closure`` will be used instead and if ``method``
 doesn't have a class name, it's shown as a function (``method()``).
 
+.. code-block:: twig
+
+    {{ 'App\\Controller\\ProductController::list'|abbr_method }}
+
+The above example will be rendered as:
+
+.. code-block:: html
+
+    <abbr title="App\Controller\ProductController">ProductController</abbr>::list()
+
 format_args
 ~~~~~~~~~~~
 
@@ -637,6 +851,32 @@ Generates an excerpt of a code file around the given ``line`` number. The
 ``srcContext`` argument defines the total number of lines to display around the
 given line number (use ``-1`` to display the whole file).
 
+Consider the following as the content of ``file.txt``:
+
+.. code-block:: text
+
+    a
+    b
+    c
+    d
+    e
+
+.. code-block:: html+twig
+
+    {{ '/path/to/file.txt'|file_excerpt(line = 4, srcContext = 1) }}
+    {# output: #}
+    <ol start="3">
+        <li><a class="anchor" id="line3"></a><code>c</code></li>
+        <li class="selected"><a class="anchor" id="line4"></a><code>d</code></li>
+        <li><a class="anchor" id="line5"></a><code>e</code></li>
+    </ol>
+
+    {{ '/path/to/file.txt'|file_excerpt(line = 1, srcContext = 0) }}
+    {# output: #}
+    <ol start="1">
+        <li class="selected"><a class="anchor" id="line1"></a><code>a</code></li>
+    </ol>
+
 format_file
 ~~~~~~~~~~~
 
@@ -655,6 +895,27 @@ Generates the file path inside an ``<a>`` element. If the path is inside
 the kernel root directory, the kernel root directory path is replaced by
 ``kernel.project_dir`` (showing the full path in a tooltip on hover).
 
+.. code-block:: html+twig
+
+    {{ '/path/to/file.txt'|format_file(line = 1, text = "my_text") }}
+    {# output: #}
+    <a href="/path/to/file.txt#L1"
+        title="Click to open this file" class="file_link">my_text at line 1
+    </a>
+
+    {{ "/path/to/file.txt"|format_file(line = 3) }}
+    {# output: #}
+    <a href="/path/to/file.txt&amp;line=3"
+        title="Click to open this file" class="file_link">/path/to/file.txt at line 3
+    </a>
+
+.. tip::
+
+    If you set the :ref:`framework.ide <reference-framework-ide>` option, the
+    generated links will change to open the file in that IDE/editor. For example,
+    when using PhpStorm, the ``<a href="/path/to/file.txt&amp;line=3"`` link will
+    become ``<a href="phpstorm://open?file=/path/to/file.txt&amp;line=3"``.
+
 format_file_from_text
 ~~~~~~~~~~~~~~~~~~~~~
 
@@ -682,6 +943,11 @@ file_link
 Generates a link to the provided file and line number using
 a preconfigured scheme.
 
+.. code-block:: twig
+
+    {{ 'path/to/file/file.txt'|file_link(line = 3) }}
+    {# output: file://path/to/file/file.txt#L3 #}
+
 file_relative
 ~~~~~~~~~~~~~
 
@@ -724,6 +990,21 @@ serialize
 Accepts any data that can be serialized by the :doc:`Serializer component </serializer>`
 and returns a serialized string in the specified ``format``.
 
+For example::
+
+    $object = new \stdClass();
+    $object->foo = 'bar';
+    $object->content = [];
+    $object->createdAt = new \DateTime('2024-11-30');
+
+.. code-block:: twig
+
+    {{ object|serialize(format = 'json', context = {
+        'datetime_format': 'D, Y-m-d',
+        'empty_array_as_object': true,
+    }) }}
+    {# output: {"foo":"bar","content":{},"createdAt":"Sat, 2024-11-30"} #}
+
 .. _reference-twig-filter-emojify:
 
 emojify
diff --git a/routing.rst b/routing.rst
index df1f861c554..9925b323304 100644
--- a/routing.rst
+++ b/routing.rst
@@ -18,12 +18,13 @@ your favorite.
 :ref:`Symfony recommends attributes <best-practice-controller-attributes>`
 because it's convenient to put the route and controller in the same place.
 
+.. _routing-route-attributes:
+
 Creating Routes as Attributes
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-PHP attributes allow 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.
+PHP attributes allow you to define routes next to the code of the
+:doc:`controllers </controller>` associated to those routes.
 
 You need to add a bit of configuration to your project before using them. If your
 project uses :ref:`Symfony Flex <symfony-flex>`, this file is already created for you.
@@ -248,6 +249,68 @@ Use the ``methods`` option to restrict the verbs each route should respond to:
     automatically for you when the :ref:`framework.http_method_override <configuration-framework-http_method_override>`
     option is ``true``.
 
+Matching Environments
+~~~~~~~~~~~~~~~~~~~~~
+
+Use the ``env`` option to register a route only when the current
+:ref:`configuration environment <configuration-environments>` matches the
+given value:
+
+.. configuration-block::
+
+    .. code-block:: php-attributes
+
+        // src/Controller/DefaultController.php
+        namespace App\Controller;
+
+        use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+        use Symfony\Component\HttpFoundation\Response;
+        use Symfony\Component\Routing\Attribute\Route;
+
+        class DefaultController extends AbstractController
+        {
+            #[Route('/tools', name: 'tools', env: 'dev')]
+            public function developerTools(): Response
+            {
+                // ...
+            }
+        }
+
+    .. code-block:: yaml
+
+        # config/routes.yaml
+        tools:
+            path:       /tools
+            controller: App\Controller\DefaultController::developerTools
+            env:        dev
+
+    .. code-block:: xml
+
+        <!-- config/routes.xml -->
+        <?xml version="1.0" encoding="UTF-8" ?>
+        <routes xmlns="http://symfony.com/schema/routing"
+            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+            xsi:schemaLocation="http://symfony.com/schema/routing
+                https://symfony.com/schema/routing/routing-1.0.xsd">
+
+            <route id="tools" path="/tools" controller="App\Controller\DefaultController::developerTools">
+                <env>dev</env>
+            </route>
+        </routes>
+
+    .. code-block:: php
+
+        // config/routes.php
+        use App\Controller\DefaultController;
+        use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
+
+        return function (RoutingConfigurator $routes): void {
+            $routes->add('tools', '/tools')
+                ->controller([DefaultController::class, 'developerTools'])
+                ->env('dev')
+            ;
+        };
+
 .. _routing-matching-expressions:
 
 Matching Expressions
@@ -434,6 +497,18 @@ evaluates them:
     blog_show         ANY      ANY      ANY    /blog/{slug}
     ----------------  -------  -------  -----  --------------------------------------------
 
+    # pass this option to also display all the defined route aliases
+    $ php bin/console debug:router --show-aliases
+
+    # pass this option to only display routes that match the given HTTP method
+    # (you can use the special value ANY to see routes that match any method)
+    $ php bin/console debug:router --method=GET
+    $ php bin/console debug:router --method=ANY
+
+.. versionadded:: 7.3
+
+    The ``--method`` option was introduced in Symfony 7.3.
+
 Pass the name (or part of the name) of some route to this argument to print the
 route details:
 
@@ -451,11 +526,6 @@ route details:
     |             | utf8: true                                              |
     +-------------+---------------------------------------------------------+
 
-.. tip::
-
-    Use the ``--show-aliases`` option to show all available aliases for a given
-    route.
-
 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:
@@ -578,7 +648,7 @@ the ``{page}`` parameter using the ``requirements`` option:
             }
 
             #[Route('/blog/{slug}', name: 'blog_show')]
-            public function show($slug): Response
+            public function show(string $slug): Response
             {
                 // ...
             }
@@ -707,12 +777,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:
@@ -853,6 +917,10 @@ other configuration formats they are defined with the ``defaults`` option:
 Now, when the user visits ``/blog``, the ``blog_list`` route will match and
 ``$page`` will default to a value of ``1``.
 
+.. tip::
+
+    The default value is allowed to not match the requirement.
+
 .. warning::
 
     You can have more than one optional parameter (e.g. ``/blog/{slug}/{page}``),
@@ -948,6 +1016,7 @@ optional ``priority`` parameter in those routes to control their priority:
         namespace App\Controller;
 
         use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+        use Symfony\Component\HttpFoundation\Response;
         use Symfony\Component\Routing\Attribute\Route;
 
         class BlogController extends AbstractController
@@ -997,7 +1066,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
@@ -1011,9 +1080,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 look up the
+corresponding ``BlogPost`` object from the database using the slug.
+
+.. versionadded:: 7.1
+
+    Route parameter mapping was introduced in Symfony 7.1.
+
+When mapping multiple entities from route parameters, name collisions can occur.
+In this example, the route tries to define two mappings: one for an author and one
+for a category; both using the same ``name`` parameter. This isn't allowed because
+the route ends up declaring ``name`` twice::
+
+    #[Route('/search-book/{name:author}/{name:category}')]
+
+Such routes should instead be defined using the following syntax::
+
+    #[Route('/search-book/{authorName:author.name}/{categoryName:category.name}')]
+
+This way, the route parameter names are unique (``authorName`` and ``categoryName``),
+and the "param converter" can correctly map them to controller arguments (``$author``
+and ``$category``), loading them both by their name.
+
+.. versionadded:: 7.3
+
+    This more advanced style of route parameter mapping was introduced in Symfony 7.3.
+
+More advanced mappings can be achieved using the ``#[MapEntity]`` attribute.
 Check out the :ref:`Doctrine param conversion documentation <doctrine-entity-value-resolver>`
-to learn 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
 ~~~~~~~~~~~~~~~~~~~~~~
@@ -1305,15 +1402,94 @@ A possible solution is to change the parameter requirements to be more permissiv
 Route Aliasing
 --------------
 
-Route alias allow you to have multiple name for the same route:
+Route alias allows you to have multiple names for the same route
+and can be used to provide backward compatibility for routes that
+have been renamed. Let's say you have a route called ``product_show``:
+
+.. configuration-block::
+
+    .. code-block:: php-attributes
+
+        // src/Controller/ProductController.php
+        namespace App\Controller;
+
+        use Symfony\Component\HttpFoundation\Response;
+        use Symfony\Component\Routing\Attribute\Route;
+
+        class ProductController
+        {
+            #[Route('/product/{id}', name: 'product_show')]
+            public function show(): Response
+            {
+                // ...
+            }
+        }
+
+    .. code-block:: yaml
+
+        # config/routes.yaml
+        product_show:
+            path: /product/{id}
+            controller: App\Controller\ProductController::show
+
+    .. code-block:: xml
+
+        <!-- config/routes.xml -->
+        <?xml version="1.0" encoding="UTF-8" ?>
+        <routes xmlns="http://symfony.com/schema/routing"
+            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+            xsi:schemaLocation="http://symfony.com/schema/routing
+                https://symfony.com/schema/routing/routing-1.0.xsd">
+
+            <route id="product_show" path="/product/{id}" controller="App\Controller\ProductController::show"/>
+        </routes>
+
+    .. code-block:: php
+
+        // config/routes.php
+        use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
+
+        return static function (RoutingConfigurator $routes): void {
+            $routes->add('product_show', '/product/{id}')
+                    ->controller('App\Controller\ProductController::show');
+        };
+
+Now, let's say you want to create a new route called ``product_details``
+that acts exactly the same as ``product_show``.
+
+Instead of duplicating the original route, you can create an alias for it.
 
 .. configuration-block::
 
+    .. code-block:: php-attributes
+
+        // src/Controller/ProductController.php
+        namespace App\Controller;
+
+        use Symfony\Component\HttpFoundation\Response;
+        use Symfony\Component\Routing\Attribute\Route;
+
+        class ProductController
+        {
+            // the "alias" argument assigns an alternate name to this route;
+            // the alias will point to the actual route "product_show"
+            #[Route('/product/{id}', name: 'product_show', alias: ['product_details'])]
+            public function show(): Response
+            {
+                // ...
+            }
+        }
+
     .. code-block:: yaml
 
         # config/routes.yaml
-        new_route_name:
-            alias: original_route_name
+        product_show:
+            path: /product/{id}
+            controller: App\Controller\ProductController::show
+
+        product_details:
+            # "alias" option refers to the name of the route declared above
+            alias: product_show
 
     .. code-block:: xml
 
@@ -1324,7 +1500,9 @@ Route alias allow you to have multiple name for the same route:
             xsi:schemaLocation="http://symfony.com/schema/routing
                 https://symfony.com/schema/routing/routing-1.0.xsd">
 
-            <route id="new_route_name" alias="original_route_name"/>
+            <route id="product_show" path="/product/{id}" controller="App\Controller\ProductController::show"/>
+            <!-- "alias" attribute value refers to the name of the route declared above -->
+            <route id="product_details" alias="product_show"/>
         </routes>
 
     .. code-block:: php
@@ -1333,38 +1511,109 @@ Route alias allow you to have multiple name for the same route:
         use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
 
         return static function (RoutingConfigurator $routes): void {
-            $routes->alias('new_route_name', 'original_route_name');
+            $routes->add('product_show', '/product/{id}')
+                    ->controller('App\Controller\ProductController::show');
+            // second argument refers to the name of the route declared above
+            $routes->alias('product_details', 'product_show');
         };
 
-In this example, both ``original_route_name`` and ``new_route_name`` routes can
+.. versionadded:: 7.3
+
+    Support for route aliases in PHP attributes was introduced in Symfony 7.3.
+
+In this example, both ``product_show`` and ``product_details`` routes can
 be used in the application and will produce the same result.
 
+.. note::
+
+    YAML, XML, and PHP configuration formats are the only ways to define an alias
+    for a route that you do not own. You can't do this when using PHP attributes.
+
+    This allows you for example to use your own route name for URL generation,
+    while still targeting a route defined by a third-party bundle. The alias and
+    the original route do not need to be declared in the same file or format.
+
 .. _routing-alias-deprecation:
 
 Deprecating Route Aliases
 ~~~~~~~~~~~~~~~~~~~~~~~~~
 
-If some route alias should no longer be used (because it is outdated or
-you decided not to maintain it anymore), you can deprecate its definition:
+Route aliases can be used to provide backward compatibility for routes that
+have been renamed.
+
+Now, let's say you want to replace the ``product_show`` route in favor of
+``product_details`` and mark the old one as deprecated.
+
+In the previous example, the alias ``product_details`` was pointing to
+``product_show`` route.
+
+To mark the ``product_show`` route as deprecated, you need to "switch" the alias.
+The ``product_show`` become the alias, and will now point to the ``product_details`` route.
+This way, the ``product_show`` alias could be deprecated.
 
 .. configuration-block::
 
+    .. code-block:: php-attributes
+
+        // src/Controller/ProductController.php
+        namespace App\Controller;
+
+        use Symfony\Component\HttpFoundation\Response;
+        use Symfony\Component\Routing\Attribute\DeprecatedAlias;
+        use Symfony\Component\Routing\Attribute\Route;
+
+        class ProductController
+        {
+            // this outputs the following generic deprecation message:
+            // Since acme/package 1.2: The "product_show" route alias is deprecated. You should stop using it, as it will be removed in the future.
+            #[Route('/product/{id}',
+                name: 'product_details',
+                alias: new DeprecatedAlias(
+                    aliasName: 'product_show',
+                    package: 'acme/package',
+                    version: '1.2',
+                ),
+            )]
+            // Or, you can also define a custom deprecation message (%alias_id% placeholder is available)
+            #[Route('/product/{id}',
+                name: 'product_details',
+                alias: new DeprecatedAlias(
+                    aliasName: 'product_show',
+                    package: 'acme/package',
+                    version: '1.2',
+                    message: 'The "%alias_id%" route alias is deprecated. Please use "product_details" instead.',
+                ),
+            )]
+            public function show(): Response
+            {
+                // ...
+            }
+        }
+
     .. code-block:: yaml
 
-        new_route_name:
-            alias: original_route_name
+        # Move the concrete route definition under ``product_details``
+        product_details:
+            path: /product/{id}
+            controller: App\Controller\ProductController::show
+
+        # Define the alias and the deprecation under the ``product_show`` definition
+        product_show:
+            alias: product_details
 
             # this outputs the following generic deprecation message:
-            # Since acme/package 1.2: The "new_route_name" route alias is deprecated. You should stop using it, as it will be removed in the future.
+            # Since acme/package 1.2: The "product_show" route alias is deprecated. You should stop using it, as it will be removed in the future.
             deprecated:
                 package: 'acme/package'
                 version: '1.2'
 
-            # you can also define a custom deprecation message (%alias_id% placeholder is available)
+            # or
+
+            # you can define a custom deprecation message (%alias_id% placeholder is available)
             deprecated:
                 package: 'acme/package'
                 version: '1.2'
-                message: 'The "%alias_id%" route alias is deprecated. Do not use it anymore.'
+                message: 'The "%alias_id%" route alias is deprecated. Please use "product_details" instead.'
 
     .. code-block:: xml
 
@@ -1374,35 +1623,50 @@ you decided not to maintain it anymore), you can deprecate its definition:
             xsi:schemaLocation="http://symfony.com/schema/routing
                 https://symfony.com/schema/routing/routing-1.0.xsd">
 
-            <route id="new_route_name" alias="original_route_name">
+            <!-- Move the concrete route definition under ``product_details`` -->
+            <route id="product_details" path="/product/{id}" controller="App\Controller\ProductController::show"/>
+
+            <!-- Define the alias and the deprecation under the ``product_show`` definition -->
+            <route id="product_show" alias="product_details">
                 <!-- this outputs the following generic deprecation message:
-                     Since acme/package 1.2: The "new_route_name" route alias is deprecated. You should stop using it, as it will be removed in the future. -->
+                     Since acme/package 1.2: The "product_show" route alias is deprecated. You should stop using it, as it will be removed in the future. -->
                 <deprecated package="acme/package" version="1.2"/>
 
-                <!-- you can also define a custom deprecation message (%alias_id% placeholder is available) -->
+                <!-- or -->
+
+                <!-- you can define a custom deprecation message (%alias_id% placeholder is available) -->
                 <deprecated package="acme/package" version="1.2">
-                    The "%alias_id%" route alias is deprecated. Do not use it anymore.
+                    The "%alias_id%" route alias is deprecated. Please use "product_details" instead.
                 </deprecated>
             </route>
         </routes>
 
     .. code-block:: php
 
-        $routes->alias('new_route_name', 'original_route_name')
+        $routes->add('product_details', '/product/{id}')
+                ->controller('App\Controller\ProductController::show');
+
+        $routes->alias('product_show', 'product_details')
             // this outputs the following generic deprecation message:
-            // Since acme/package 1.2: The "new_route_name" route alias is deprecated. You should stop using it, as it will be removed in the future.
+            // Since acme/package 1.2: The "product_show" route alias is deprecated. You should stop using it, as it will be removed in the future.
             ->deprecate('acme/package', '1.2', '')
 
-            // you can also define a custom deprecation message (%alias_id% placeholder is available)
+            // or
+
+            // you can define a custom deprecation message (%alias_id% placeholder is available)
             ->deprecate(
                 'acme/package',
                 '1.2',
-                'The "%alias_id%" route alias is deprecated. Do not use it anymore.'
+                'The "%alias_id%" route alias is deprecated. Please use "product_details" instead.'
             )
         ;
 
-In this example, every time the ``new_route_name`` alias is used, a deprecation
-warning is triggered, advising you to stop using that alias.
+.. versionadded:: 7.3
+
+    The ``DeprecatedAlias`` class for PHP attributes was introduced in Symfony 7.3.
+
+In this example, every time the ``product_show`` alias is used, a deprecation
+warning is triggered, advising you to stop using this route and prefer using ``product_details``.
 
 The message is actually a message template, which replaces occurrences of the
 ``%alias_id%`` placeholder by the route alias name. You **must** have
@@ -2476,23 +2740,23 @@ The solution is to configure the ``default_uri`` option to define the
 
 Now you'll get the expected results when generating URLs in your commands::
 
-    // src/Command/SomeCommand.php
+    // src/Command/MyCommand.php
     namespace App\Command;
 
-    use Symfony\Component\Console\Command\Command;
-    use Symfony\Component\Console\Input\InputInterface;
-    use Symfony\Component\Console\Output\OutputInterface;
+    use Symfony\Component\Console\Attribute\AsCommand;
+    use Symfony\Component\Console\Style\SymfonyStyle;
     use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
     // ...
 
-    class SomeCommand extends Command
+    #[AsCommand(name: 'app:my-command')]
+    class MyCommand
     {
-        public function __construct(private UrlGeneratorInterface $urlGenerator)
-        {
-            parent::__construct();
+        public function __construct(
+            private UrlGeneratorInterface $urlGenerator,
+        ) {
         }
 
-        protected function execute(InputInterface $input, OutputInterface $output): int
+        public function __invoke(SymfonyStyle $io): int
         {
             // generate a URL with no route arguments
             $signUpPage = $this->urlGenerator->generate('sign_up');
@@ -2802,11 +3066,41 @@ argument of :method:`Symfony\\Component\\HttpFoundation\\UriSigner::sign`::
 
     The feature to add an expiration date for a signed URI was introduced in Symfony 7.1.
 
-.. note::
+If you need to know the reason why a signed URI is invalid, you can use the
+``verify()`` method which throws exceptions on failure::
+
+    use Symfony\Component\HttpFoundation\Exception\ExpiredSignedUriException;
+    use Symfony\Component\HttpFoundation\Exception\UnsignedUriException;
+    use Symfony\Component\HttpFoundation\Exception\UnverifiedSignedUriException;
+
+    // ...
+
+    try {
+        $uriSigner->verify($uri); // $uri can be a string or Request object
+
+        // the URI is valid
+    } catch (UnsignedUriException) {
+        // the URI isn't signed
+    } catch (UnverifiedSignedUriException) {
+        // the URI is signed but the signature is invalid
+    } catch (ExpiredSignedUriException) {
+        // the URI is signed but expired
+    }
+
+.. versionadded:: 7.3
+
+    The ``verify()`` method was introduced in Symfony 7.3.
+
+.. tip::
+
+    If ``symfony/clock`` is installed, it will be used to create and verify
+    expirations. This allows you to :ref:`mock the current time in your tests
+    <clock_writing-tests>`.
+
+.. versionadded:: 7.3
 
-    The generated URI hashes may include the ``/`` and ``+`` characters, which
-    can cause issues with certain clients. If you encounter this problem, replace
-    them using the following: ``strtr($hash, ['/' => '_', '+' => '-'])``.
+    Support for :doc:`Symfony Clock </components/clock>` in ``UriSigner`` was
+    introduced in Symfony 7.3.
 
 Troubleshooting
 ---------------
diff --git a/scheduler.rst b/scheduler.rst
index ddc40aa4952..0d286b37d92 100644
--- a/scheduler.rst
+++ b/scheduler.rst
@@ -1,6 +1,11 @@
 Scheduler
 =========
 
+.. admonition:: Screencast
+    :class: screencast
+
+    Like video tutorials? Check out this `Scheduler quick-start screencast`_.
+
 The scheduler component manages task scheduling within your PHP application, like
 running a task each night at 3 AM, every two weeks except for holidays or any
 other custom schedule you might need.
@@ -15,17 +20,16 @@ stack Symfony application.
 Installation
 ------------
 
-In applications using :ref:`Symfony Flex <symfony-flex>`, run this command to
-install the scheduler component:
+Run this command to install the scheduler component:
 
 .. code-block:: terminal
 
     $ composer require symfony/scheduler
 
-.. tip::
+.. note::
 
-    Starting in `MakerBundle`_ ``v1.58.0``, you can run ``php bin/console make:schedule``
-    to generate a basic schedule, that you can customize to create your own Scheduler.
+    In applications using :ref:`Symfony Flex <symfony-flex>`, installing the component
+    also creates an initial schedule that's ready to start adding your tasks.
 
 Symfony Scheduler Basics
 ------------------------
@@ -223,7 +227,7 @@ this will create a very long running list of schedules at that exact time.
 This may cause an issue if a task has a memory leak.
 
 You can add a hash symbol (``#``) in expressions to generate random values.
-Athough the values are random, they are predictable and consistent because they
+Although the values are random, they are predictable and consistent because they
 are generated based on the message. A message with string representation ``my task``
 and a defined frequency of ``# # * * *`` will have an idempotent frequency
 of ``56 20 * * *`` (every day at 8:56pm).
@@ -272,14 +276,40 @@ defined by PHP datetime functions::
     RecurringMessage::every('3 weeks', new Message());
     RecurringMessage::every('first Monday of next month', new Message());
 
-    $from = new \DateTimeImmutable('13:47', new \DateTimeZone('Europe/Paris'));
-    $until = '2023-06-12';
-    RecurringMessage::every('first Monday of next month', new Message(), $from, $until);
+.. note::
+
+    Comma-separated weekdays (e.g., ``'Monday, Thursday, Saturday'``) are not supported
+    by the ``every()`` method. For multiple weekdays, use cron expressions instead:
+
+    .. code-block:: diff
+
+        - RecurringMessage::every('Monday, Thursday, Saturday', new Message());
+        + RecurringMessage::cron('5 12 * * 1,4,6', new Message());
 
 .. tip::
 
     You can also define periodic tasks using :ref:`the AsPeriodicTask attribute <scheduler-attributes-periodic-task>`.
 
+You can also define ``from`` and ``until`` times for your schedule::
+
+    // create a message every day at 13:00
+    $from = new \DateTimeImmutable('13:00', new \DateTimeZone('Europe/Paris'));
+    RecurringMessage::every('1 day', new Message(), $from);
+
+    // create a message every day until a specific date
+    $until = '2023-06-12';
+    RecurringMessage::every('1 day', new Message(), null, $until);
+
+    // combine from and until for more precise control
+    $from = new \DateTimeImmutable('2023-01-01 13:47', new \DateTimeZone('Europe/Paris'));
+    $until = '2023-06-12';
+    RecurringMessage::every('first Monday of next month', new Message(), $from, $until);
+
+When starting the scheduler, the message isn't sent to the messenger immediately.
+If you don't set a ``from`` parameter, the first frequency period starts from the
+moment the scheduler runs. For example, if you start it at 8:33 and the message
+is scheduled hourly, it will run at 9:33, 10:33, 11:33, etc.
+
 Custom Triggers
 ~~~~~~~~~~~~~~~
 
@@ -418,10 +448,10 @@ by adding one of these attributes to a service or a command:
 :class:`Symfony\\Component\\Scheduler\\Attribute\\AsCronTask`.
 
 For both of these attributes, you have the ability to define the schedule to
-use via the ``schedule``option. By default, the ``default`` named schedule will
+use via the ``schedule`` option. By default, the ``default`` named schedule will
 be used. Also, by default, the ``__invoke`` method of your service will be called
-but, it's also possible to specify the method to call via the ``method``option
-and you can define arguments via ``arguments``option if necessary.
+but, it's also possible to specify the method to call via the ``method`` option
+and you can define arguments via ``arguments`` option if necessary.
 
 .. _scheduler-attributes-cron-task:
 
@@ -458,7 +488,8 @@ The attribute takes more parameters to customize the trigger::
     // when applying this attribute to a Symfony console command, you can pass
     // arguments and options to the command using the 'arguments' option:
     #[AsCronTask('0 0 * * *', arguments: 'some_argument --some-option --another-option=some_value')]
-    class MyCommand extends Command
+    #[AsCommand(name: 'app:my-command')]
+    class MyCommand
 
 .. _scheduler-attributes-periodic-task:
 
@@ -507,7 +538,8 @@ The ``#[AsPeriodicTask]`` attribute takes many parameters to customize the trigg
     // when applying this attribute to a Symfony console command, you can pass
     // arguments and options to the command using the 'arguments' option:
     #[AsPeriodicTask(frequency: '1 day', arguments: 'some_argument --some-option --another-option=some_value')]
-    class MyCommand extends Command
+    #[AsCommand(name: 'app:my-command')]
+    class MyCommand
 
 Managing Scheduled Messages
 ---------------------------
@@ -559,7 +591,7 @@ In your handler, you can check a condition and, if affirmative, access the
     {
         public function getSchedule(): Schedule
         {
-            $this->removeOldReports = RecurringMessage::cron(‘3 8 * * 1’, new CleanUpOldSalesReport());
+            $this->removeOldReports = RecurringMessage::cron('3 8 * * 1', new CleanUpOldSalesReport());
 
             return $this->schedule ??= (new Schedule())
                 ->with(
@@ -723,10 +755,15 @@ after a message is consumed::
         $schedule = $event->getSchedule();
         $context = $event->getMessageContext();
         $message = $event->getMessage();
+        $result = $event->getResult();
 
-        // do something with the schedule, context or message
+        // do something with the schedule, context, message or result
     }
 
+.. versionadded:: 7.3
+
+    The ``getResult()`` method was introduced in Symfony 7.3.
+
 Execute this command to find out which listeners are registered for this event
 and their priorities:
 
@@ -841,6 +878,41 @@ code::
     use the ``messenger:consume`` command as explained in the previous
     section.
 
+Modifying the Schedule at Runtime
+---------------------------------
+
+When a recurring message is added to or removed from the schedule,
+the scheduler automatically restarts and recalculates the internal trigger heap.
+This enables dynamic control of scheduled tasks at runtime::
+
+    // src/Scheduler/DynamicScheduleProvider.php
+    namespace App\Scheduler;
+
+    #[AsSchedule('uptoyou')]
+    class DynamicScheduleProvider implements ScheduleProviderInterface
+    {
+        private ?Schedule $schedule = null;
+
+        public function getSchedule(): Schedule
+        {
+            return $this->schedule ??= (new Schedule())
+                ->with(
+                    // ...
+                )
+            ;
+        }
+
+        public function clearAndAddMessages(): void
+        {
+            // clear the current schedule and add new recurring messages
+            $this->schedule?->clear();
+            $this->schedule?->add(
+                RecurringMessage::cron('@hourly', new DoActionMessage()),
+                RecurringMessage::cron('@daily', new DoAnotherActionMessage()),
+            );
+        }
+    }
+
 Debugging the Schedule
 ----------------------
 
@@ -887,7 +959,8 @@ While this behavior may not necessarily pose a problem, there is a possibility t
 
 That's why the scheduler allows to remember the last execution date of a message
 via the ``stateful`` option (and the :doc:`Cache component </components/cache>`).
-This allows the system to retain the state of the schedule, ensuring that when a worker is restarted, it resumes from the point it left off.::
+This allows the system to retain the state of the schedule, ensuring that when a
+worker is restarted, it resumes from the point it left off::
 
     // src/Scheduler/SaleTaskProvider.php
     namespace App\Scheduler;
@@ -907,6 +980,32 @@ This allows the system to retain the state of the schedule, ensuring that when a
         }
     }
 
+With the ``stateful`` option, all missed messages will be handled. If you need to
+handle a message only once, you can use the ``processOnlyLastMissedRun`` option::
+
+    // src/Scheduler/SaleTaskProvider.php
+    namespace App\Scheduler;
+
+    #[AsSchedule('uptoyou')]
+    class SaleTaskProvider implements ScheduleProviderInterface
+    {
+        public function getSchedule(): Schedule
+        {
+            $this->removeOldReports = RecurringMessage::cron('3 8 * * 1', new CleanUpOldSalesReport());
+
+            return $this->schedule ??= (new Schedule())
+                ->with(
+                    // ...
+                )
+                ->stateful($this->cache)
+                ->processOnlyLastMissedRun(true)
+        }
+    }
+
+.. versionadded:: 7.2
+
+    The ``processOnlyLastMissedRun`` option was introduced in Symfony 7.2.
+
 To scale your schedules more effectively, you can use multiple workers. In such
 cases, a good practice is to add a :doc:`lock </components/lock>` to prevent the
 same task more than once::
@@ -959,9 +1058,9 @@ When using the ``RedispatchMessage``, Symfony will attach a
 :class:`Symfony\\Component\\Scheduler\\Messenger\\ScheduledStamp` to the message,
 helping you identify those messages when needed.
 
-.. _`MakerBundle`: https://symfony.com/doc/current/bundles/SymfonyMakerBundle/index.html
 .. _`Deploying to Production`: https://symfony.com/doc/current/messenger.html#deploying-to-production
 .. _`Memoizing`: https://en.wikipedia.org/wiki/Memoization
 .. _`cron command-line utility`: https://en.wikipedia.org/wiki/Cron
 .. _`crontab.guru website`: https://crontab.guru/
 .. _`relative formats`: https://www.php.net/manual/en/datetime.formats.php#datetime.formats.relative
+.. _`Scheduler quick-start screencast`: https://symfonycasts.com/screencast/mailtrap/bonus-symfony-scheduler
diff --git a/security.rst b/security.rst
index 7a551885ee4..bc394fd95b8 100644
--- a/security.rst
+++ b/security.rst
@@ -43,7 +43,7 @@ creates a ``security.yaml`` configuration file for you:
                 # https://symfony.com/doc/current/security/impersonating_user.html
                 # switch_user: true
 
-        # Easy way to control access for large sections of your site
+        # An easy way to control access for large sections of your site
         # Note: Only the *first* access control that matches will be used
         access_control:
             # - { path: ^/admin, roles: ROLE_ADMIN }
@@ -193,14 +193,7 @@ from the `MakerBundle`_:
             return $this;
         }
 
-        /**
-         * @see UserInterface
-         */
-        public function eraseCredentials(): void
-        {
-            // If you store any temporary, sensitive data on the user, clear it here
-            // $this->plainPassword = null;
-        }
+        // [...]
     }
 
 .. tip::
@@ -622,13 +615,10 @@ don't accidentally block Symfony's dev tools - which live under URLs like
 
     This feature is not supported by the XML configuration format.
 
-All *real* URLs are handled by the ``main`` firewall (no ``pattern`` key means
-it matches *all* URLs). A firewall can have many modes of authentication,
-in other words, it enables many ways to ask the question "Who are you?".
-
-Often, the user is unknown (i.e. not logged in) when they first visit your
-website. If you visit your homepage right now, you *will* have access and
-you'll see that you're visiting a page behind the firewall in the toolbar:
+A firewall can have many modes of authentication, in other words, it enables many
+ways to ask the question "Who are you?". Often, the user is unknown (i.e. not logged in)
+when they first visit your website. If you visit your homepage right now, you *will*
+have access and you'll see that you're visiting a page behind the firewall in the toolbar:
 
 .. image:: /_images/security/anonymous_wdt.png
    :alt: The Symfony profiler toolbar where the Security information shows "Authenticated: no" and "Firewall name: main"
@@ -1769,9 +1759,12 @@ You can log in a user programmatically using the ``login()`` method of the
             // you can also log in on a different firewall...
             $security->login($user, 'form_login', 'other_firewall');
 
-            // ...and add badges
+            // ... add badges...
             $security->login($user, 'form_login', 'other_firewall', [(new RememberMeBadge())->enable()]);
 
+            // ... and also add passport attributes
+            $security->login($user, 'form_login', 'other_firewall', [(new RememberMeBadge())->enable()], ['referer' => 'https://oauth.example.com']);
+
             // use the redirection logic applied to regular login
             $redirectResponse = $security->login($user);
             return $redirectResponse;
@@ -1781,6 +1774,12 @@ You can log in a user programmatically using the ``login()`` method of the
         }
     }
 
+.. versionadded:: 7.2
+
+    The support for passport attributes in the
+    :method:`Symfony\\Bundle\\SecurityBundle\\Security::login` method was
+    introduced in Symfony 7.2.
+
 .. _security-logging-out:
 
 Logging Out
@@ -2540,6 +2539,17 @@ the built-in ``is_granted()`` helper function in any Twig template:
 
 .. _security-isgranted:
 
+Similarly, if you want to check if a specific user has a certain role, you can use
+the built-in ``is_granted_for_user()`` helper function:
+
+.. code-block:: html+twig
+
+    {% if is_granted_for_user(user, 'ROLE_ADMIN') %}
+        <a href="...">Delete</a>
+    {% endif %}
+
+.. _security-isgrantedforuser:
+
 Securing other Services
 .......................
 
@@ -2576,6 +2586,19 @@ want to include extra details only for users that have a ``ROLE_SALES_ADMIN`` ro
           // ...
       }
 
+
+.. tip::
+
+    The ``isGranted()`` method checks authorization for the currently logged-in user.
+    If you need to check authorization for a different user or when the user session
+    is unavailable (e.g., in a CLI context such as a message queue or cron job), you
+    can use the ``isGrantedForUser()`` method to explicitly set the target user.
+
+    .. versionadded:: 7.3
+
+        The :method:`Symfony\\Bundle\\SecurityBundle\\Security::isGrantedForUser`
+        method was introduced in Symfony 7.3.
+
 If you're using the :ref:`default services.yaml configuration <service-container-services-load-example>`,
 Symfony will automatically pass the ``security.helper`` to your service
 thanks to autowiring and the ``Security`` type-hint.
@@ -2671,13 +2694,14 @@ anonymous users access by checking if there is no user set on the token::
     // ...
     use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
     use Symfony\Component\Security\Core\Authentication\User\UserInterface;
+    use Symfony\Component\Security\Core\Authorization\Voter\Vote;
     use Symfony\Component\Security\Core\Authorization\Voter\Voter;
 
     class PostVoter extends Voter
     {
         // ...
 
-        protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool
+        protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token, ?Vote $vote = null): bool
         {
             // ...
 
@@ -2689,6 +2713,11 @@ anonymous users access by checking if there is no user set on the token::
         }
     }
 
+.. versionadded:: 7.3
+    
+    The ``$vote`` argument of the ``voteOnAttribute()`` method was introduced
+    in Symfony 7.3.
+
 Setting Individual User Permissions
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -2756,7 +2785,35 @@ object) are "compared" to see if they are "equal". By default, the core
 your user will be logged out. This is a security measure to make sure that malicious
 users can be de-authenticated if core user data changes.
 
-However, in some cases, this process can cause unexpected authentication problems.
+Storing the (plain or hashed) password in the session can be a security risk.
+To mitigate this, implement the ``__serialize()`` magic method in your user class
+to exclude or transform the password before storing the serialized user object
+in the session.
+
+Two strategies are supported:
+
+#. Remove the password completely. After unserialization, ``getPassword()`` returns
+   ``null`` and Symfony refreshes the user without checking the password. Use this
+   only if you store plaintext passwords (not recommended).
+#. Hash the password using the ``crc32c`` algorithm. Symfony will hash the password
+   of the refreshed user and compare it to the session value. This approach avoids
+   storing the real hash and lets you invalidate sessions on password change.
+
+   Example (assuming the password is stored in a private property called ``password``)::
+
+       public function __serialize(): array
+       {
+           $data = (array) $this;
+           $data["\0".self::class."\0password"] = hash('crc32c', $this->password);
+
+           return $data;
+       }
+
+.. versionadded:: 7.3
+
+    Support for hashing passwords with ``crc32c`` in session serialization was
+    introduced in Symfony 7.3.
+
 If you're having problems authenticating, it could be that you *are* authenticating
 successfully, but you immediately lose authentication after the first redirect.
 
diff --git a/security/access_token.rst b/security/access_token.rst
index c0ff4692676..70c9e21980e 100644
--- a/security/access_token.rst
+++ b/security/access_token.rst
@@ -411,6 +411,76 @@ and retrieve the user info:
             ;
         };
 
+To enable `OpenID Connect Discovery`_, the ``OidcUserInfoTokenHandler``
+requires the ``symfony/cache`` package to store the OIDC configuration in
+the cache. If you haven't installed it yet, run the following command:
+
+.. code-block:: terminal
+
+    $ composer require symfony/cache
+
+Next, configure the ``base_uri`` and ``discovery`` options:
+
+.. configuration-block::
+
+    .. code-block:: yaml
+
+        # config/packages/security.yaml
+        security:
+            firewalls:
+                main:
+                    access_token:
+                        token_handler:
+                            oidc_user_info:
+                                base_uri: https://www.example.com/realms/demo/
+                                discovery:
+                                    cache: cache.app
+
+    .. code-block:: xml
+
+        <!-- config/packages/security.xml -->
+        <?xml version="1.0" encoding="UTF-8"?>
+        <srv:container xmlns="http://symfony.com/schema/dic/security"
+            xmlns:srv="http://symfony.com/schema/dic/services"
+            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+            xsi:schemaLocation="http://symfony.com/schema/dic/services
+                https://symfony.com/schema/dic/services/services-1.0.xsd
+                http://symfony.com/schema/dic/security
+                https://symfony.com/schema/dic/security/security-1.0.xsd">
+
+            <config>
+                <firewall name="main">
+                    <access-token>
+                        <token-handler>
+                            <oidc-user-info base-uri="https://www.example.com/realms/demo/">
+                                <discovery cache="cache.app"/>
+                            </oidc-user-info>
+                        </token-handler>
+                    </access-token>
+                </firewall>
+            </config>
+        </srv:container>
+
+    .. code-block:: php
+
+        // config/packages/security.php
+        use Symfony\Config\SecurityConfig;
+
+        return static function (SecurityConfig $security) {
+            $security->firewall('main')
+                ->accessToken()
+                    ->tokenHandler()
+                        ->oidcUserInfo()
+                            ->baseUri('https://www.example.com/realms/demo/')
+                            ->discovery()
+                                ->cache('cache.app')
+            ;
+        };
+
+.. versionadded:: 7.3
+
+    Support for OpenID Connect Discovery was introduced in Symfony 7.3.
+
 Following the `OpenID Connect Specification`_, the ``sub`` claim is used as user
 identifier by default. To use another claim, specify it on the configuration:
 
@@ -545,8 +615,8 @@ If you haven't installed it yet, run this command:
 
     $ composer require web-token/jwt-library
 
-Symfony provides a generic ``OidcTokenHandler`` to decode your token, validate
-it and retrieve the user info from it:
+Symfony provides a generic ``OidcTokenHandler`` that decodes the token, validates
+it, and retrieves the user information from it. Optionally, the token can be encrypted (JWE):
 
 .. configuration-block::
 
@@ -567,6 +637,11 @@ it and retrieve the user info from it:
                                 audience: 'api-example'
                                 # Issuers (`iss` claim): required for validation purpose
                                 issuers: ['https://oidc.example.com']
+                                encryption:
+                                    enabled: true # Default to false
+                                    enforce: false # Default to false, requires an encrypted token when true
+                                    algorithms: ['ECDH-ES', 'A128GCM']
+                                    keyset: '{"keys": [...]}' # Encryption private keyset
 
     .. code-block:: xml
 
@@ -592,6 +667,10 @@ it and retrieve the user info from it:
                                 <algorithm>ES256</algorithm>
                                 <algorithm>RS256</algorithm>
                                 <issuer>https://oidc.example.com</issuer>
+                                <encryption enabled="true" enforce="true" keyset="{'keys': [...]}">
+                                    <algorithm>ECDH-ES</algorithm>
+                                    <algorithm>A128GCM</algorithm>
+                                </encryption>
                             </oidc>
                         </token-handler>
                     </access-token>
@@ -611,12 +690,20 @@ it and retrieve the user info from it:
                         ->oidc()
                             // Algorithm used to sign the JWS
                             ->algorithms(['ES256', 'RS256'])
-                            // A JSON-encoded JWK
+                            // A JSON-encoded JWKSet (public keys)
                             ->keyset('{"keys":[{"kty":"...","k":"..."}]}')
                             // Audience (`aud` claim): required for validation purpose
                             ->audience('api-example')
                             // Issuers (`iss` claim): required for validation purpose
                             ->issuers(['https://oidc.example.com'])
+                            ->encryption()
+                                ->enabled(true) //Default to false
+                                ->enforce(false) //Default to false, requires an encrypted token when true
+                                // Algorithm used to decrypt the JWE
+                                ->algorithms(['ECDH-ES', 'A128GCM'])
+                                // A JSON-encoded JWKSet (private keys)
+                                ->keyset('{"keys":[...]}')
+
             ;
         };
 
@@ -625,6 +712,88 @@ it and retrieve the user info from it:
     The support of multiple algorithms to sign the JWS was introduced in Symfony 7.1.
     In previous versions, only the ``ES256`` algorithm was supported.
 
+.. versionadded:: 7.3
+
+    Support for encryption algorithms to decrypt JWEs was introduced in Symfony 7.3.
+
+To enable `OpenID Connect Discovery`_, the ``OidcTokenHandler`` requires the
+``symfony/cache`` package to store the OIDC configuration in the cache. If you
+haven't installed it yet, run the following command:
+
+.. code-block:: terminal
+
+    $ composer require symfony/cache
+
+Then, you can remove the ``keyset`` configuration option (it will be imported
+from the OpenID Connect Discovery), and configure the ``discovery`` option:
+
+.. configuration-block::
+
+    .. code-block:: yaml
+
+        # config/packages/security.yaml
+        security:
+            firewalls:
+                main:
+                    access_token:
+                        token_handler:
+                            oidc:
+                                claim: email
+                                algorithms: ['ES256', 'RS256']
+                                audience: 'api-example'
+                                issuers: ['https://oidc.example.com']
+                                discovery:
+                                    base_uri: https://www.example.com/realms/demo/
+                                    cache: cache.app
+
+    .. code-block:: xml
+
+        <!-- config/packages/security.xml -->
+        <?xml version="1.0" encoding="UTF-8"?>
+        <srv:container xmlns="http://symfony.com/schema/dic/security"
+            xmlns:srv="http://symfony.com/schema/dic/services"
+            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+            xsi:schemaLocation="http://symfony.com/schema/dic/services
+                https://symfony.com/schema/dic/services/services-1.0.xsd
+                http://symfony.com/schema/dic/security
+                https://symfony.com/schema/dic/security/security-1.0.xsd">
+
+            <config>
+                <firewall name="main">
+                    <access-token>
+                        <token-handler>
+                            <oidc claim="email" audience="api-example">
+                                <algorithm>ES256</algorithm>
+                                <algorithm>RS256</algorithm>
+                                <issuer>https://oidc.example.com</issuer>
+                                <discovery base-uri="https://www.example.com/realms/demo/" cache="cache.app">
+                            </oidc>
+                        </token-handler>
+                    </access-token>
+                </firewall>
+            </config>
+        </srv:container>
+
+    .. code-block:: php
+
+        // config/packages/security.php
+        use Symfony\Config\SecurityConfig;
+
+        return static function (SecurityConfig $security) {
+            $security->firewall('main')
+                ->accessToken()
+                    ->tokenHandler()
+                        ->oidc()
+                            ->claim('email')
+                            ->algorithms(['ES256', 'RS256'])
+                            ->audience('api-example')
+                            ->issuers(['https://oidc.example.com'])
+                            ->discovery()
+                                ->baseUri('https://www.example.com/realms/demo/')
+                                ->cache('cache.app')
+            ;
+        };
+
 Following the `OpenID Connect Specification`_, the ``sub`` claim is used by
 default as user identifier. To use another claim, specify it on the
 configuration:
@@ -925,5 +1094,6 @@ for :ref:`stateless firewalls <reference-security-stateless>`.
 .. _`JSON Web Tokens (JWT)`: https://datatracker.ietf.org/doc/html/rfc7519
 .. _`OpenID Connect (OIDC)`: https://en.wikipedia.org/wiki/OpenID#OpenID_Connect_(OIDC)
 .. _`OpenID Connect Specification`: https://openid.net/specs/openid-connect-core-1_0.html
+.. _`OpenID Connect Discovery`: https://openid.net/specs/openid-connect-discovery-1_0.html
 .. _`RFC6750`: https://datatracker.ietf.org/doc/html/rfc6750
 .. _`SAML2 (XML structures)`: https://docs.oasis-open.org/security/saml/Post2.0/sstc-saml-tech-overview-2.0.html
diff --git a/security/csrf.rst b/security/csrf.rst
index be8348597c7..8797b4e7553 100644
--- a/security/csrf.rst
+++ b/security/csrf.rst
@@ -34,6 +34,10 @@ unique tokens added to forms as hidden fields. The legit server validates them t
 ensure that the request originated from the expected source and not some other
 malicious website.
 
+Anti-CSRF tokens can be managed in two ways: using a **stateful** approach,
+where tokens are stored in the session and are unique per user and action; or a
+**stateless** approach, where tokens are generated on the client side.
+
 Installation
 ------------
 
@@ -85,14 +89,14 @@ for more information):
             ;
         };
 
-The tokens used for CSRF protection are meant to be different for every user and
-they are stored in the session. That's why a session is started automatically as
-soon as you render a form with CSRF protection.
+By default, the tokens used for CSRF protection are stored in the session.
+That's why a session is started automatically as soon as you render a form
+with CSRF protection.
 
 .. _caching-pages-that-contain-csrf-protected-forms:
 
-Moreover, this means that you cannot fully cache pages that include CSRF
-protected forms. As an alternative, you can:
+This leads to many strategies to help with caching pages that include CSRF
+protected forms, among them:
 
 * Embed the form inside an uncached :doc:`ESI fragment </http_cache/esi>` and
   cache the rest of the page contents;
@@ -101,6 +105,9 @@ protected forms. As an alternative, you can:
   load the CSRF token with an uncached AJAX request and replace the form
   field value with it.
 
+The most effective way to cache pages that need CSRF protected forms is to use
+:ref:`stateless CSRF tokens <csrf-stateless-tokens>`, as explained below.
+
 .. _csrf-protection-forms:
 
 CSRF Protection in Symfony Forms
@@ -112,7 +119,7 @@ to do anything to be protected against CSRF attacks.
 
 .. _form-csrf-customization:
 
-By default Symfony adds the CSRF token in a hidden field called ``_token``, but
+By default Symfony adds the CSRF token in a hidden field called ``_csrf_token``, but
 this can be customized (1) globally for all forms and (2) on a form-by-form basis.
 Globally, you can configure it under the ``framework.form`` option:
 
@@ -183,6 +190,7 @@ method of each form::
                 'csrf_field_name' => '_token',
                 // an arbitrary string used to generate the value of the token
                 // using a different string for each form improves its security
+                // when using stateful tokens (which is the default)
                 'csrf_token_id'   => 'task_item',
             ]);
         }
@@ -190,7 +198,7 @@ method of each form::
         // ...
     }
 
-You can also customize the rendering of the CSRF form field creating a custom
+You can also customize the rendering of the CSRF form field by creating a custom
 :doc:`form theme </form/form_themes>` and using ``csrf_token`` as the prefix of
 the field (e.g. define ``{% block csrf_token_widget %} ... {% endblock %}`` to
 customize the entire form field contents).
@@ -221,7 +229,7 @@ generate a CSRF token in the template and store it as a hidden form field:
 .. code-block:: html+twig
 
     <form action="{{ url('admin_post_delete', { id: post.id }) }}" method="post">
-        {# the argument of csrf_token() is an arbitrary string used to generate the token #}
+        {# the argument of csrf_token() is the ID of this token #}
         <input type="hidden" name="token" value="{{ csrf_token('delete-item') }}">
 
         <button type="submit">Delete item</button>
@@ -229,7 +237,7 @@ generate a CSRF token in the template and store it as a hidden form field:
 
 Then, get the value of the CSRF token in the controller action and use the
 :method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController::isCsrfTokenValid`
-method to check its validity::
+method to check its validity, passing the same token ID used in the template::
 
     use Symfony\Component\HttpFoundation\Request;
     use Symfony\Component\HttpFoundation\Response;
@@ -273,6 +281,20 @@ Suppose you want a CSRF token per item, so in the template you have something li
         <button type="submit">Delete item</button>
     </form>
 
+This attribute can also be applied to a controller class. When used this way,
+the CSRF token validation will be applied to **all actions** defined in that
+controller::
+
+    use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+    use Symfony\Component\Security\Http\Attribute\IsCsrfTokenValid;
+    // ...
+
+    #[IsCsrfTokenValid('the token ID')]
+    final class SomeController extends AbstractController
+    {
+        // ...
+    }
+
 The :class:`Symfony\\Component\\Security\\Http\\Attribute\\IsCsrfTokenValid`
 attribute also accepts an :class:`Symfony\\Component\\ExpressionLanguage\\Expression`
 object evaluated to the id::
@@ -288,11 +310,26 @@ object evaluated to the id::
         // ... do something, like deleting an object
     }
 
+By default, the ``IsCsrfTokenValid`` attribute performs the CSRF token check for
+all HTTP methods. You can restrict this validation to specific methods using the
+``methods`` parameter. If the request uses a method not listed in the ``methods``
+array, the attribute is ignored for that request, and no CSRF validation occurs::
+
+    #[IsCsrfTokenValid('delete-item', tokenKey: 'token', methods: ['DELETE'])]
+    public function delete(Post $post): Response
+    {
+        // ... delete the object
+    }
+
 .. versionadded:: 7.1
 
     The :class:`Symfony\\Component\\Security\\Http\\Attribute\\IsCsrfTokenValid`
     attribute was introduced in Symfony 7.1.
 
+.. versionadded:: 7.3
+
+    The ``methods`` parameter was introduced in Symfony 7.3.
+
 CSRF Tokens and Compression Side-Channel Attacks
 ------------------------------------------------
 
@@ -302,6 +339,171 @@ targeted parts of the plaintext. To mitigate these attacks, and prevent an
 attacker from guessing the CSRF tokens, a random mask is prepended to the token
 and used to scramble it.
 
+.. _csrf-stateless-tokens:
+
+Stateless CSRF Tokens
+---------------------
+
+.. versionadded:: 7.2
+
+    Stateless anti-CSRF protection was introduced in Symfony 7.2.
+
+Traditionally, CSRF tokens are stateful, meaning they're stored in the session.
+However, some token IDs can be declared as stateless using the
+``stateless_token_ids`` option. Stateless CSRF tokens are enabled by default
+in applications using :ref:`Symfony Flex <symfony-flex>`.
+
+.. configuration-block::
+
+    .. code-block:: yaml
+
+        # config/packages/csrf.yaml
+        framework:
+            # ...
+            csrf_protection:
+                stateless_token_ids: ['submit', 'authenticate', 'logout']
+
+    .. code-block:: xml
+
+        <!-- config/packages/csrf.xml -->
+        <?xml version="1.0" encoding="UTF-8" ?>
+        <container xmlns="http://symfony.com/schema/dic/services"
+            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+            xmlns:framework="http://symfony.com/schema/dic/symfony"
+            xsi:schemaLocation="http://symfony.com/schema/dic/services
+                https://symfony.com/schema/dic/services/services-1.0.xsd
+                http://symfony.com/schema/dic/symfony
+                https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
+
+            <framework:config>
+                <framework:csrf-protection>
+                    <framework:stateless-token-id>submit</framework:stateless-token-id>
+                    <framework:stateless-token-id>authenticate</framework:stateless-token-id>
+                    <framework:stateless-token-id>logout</framework:stateless-token-id>
+                </framework:csrf-protection>
+            </framework:config>
+        </container>
+
+    .. code-block:: php
+
+        // config/packages/csrf.php
+        use Symfony\Config\FrameworkConfig;
+
+        return static function (FrameworkConfig $framework): void {
+            $framework->csrfProtection()
+                ->statelessTokenIds(['submit', 'authenticate', 'logout'])
+            ;
+        };
+
+Stateless CSRF tokens provide protection without relying on the session. This
+allows you to fully cache pages while still protecting against CSRF attacks.
+
+When validating a stateless CSRF token, Symfony checks the ``Origin`` and
+``Referer`` headers of the incoming HTTP request. If either header matches the
+application's target origin (i.e. its domain), the token is considered valid.
+
+This mechanism relies on the application being able to determine its own origin.
+If you're behind a reverse proxy, make sure it's properly configured. See
+:doc:`/deployment/proxies`.
+
+Using a Default Token ID
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+Stateful CSRF tokens are typically scoped per form or action, while stateless
+tokens don't require many identifiers.
+
+In the example above, the ``authenticate`` and ``logout`` identifiers are listed
+because they are used by default in the Symfony Security component. The ``submit``
+identifier is included so that form types defined by the application can also use
+CSRF protection by default.
+
+The following configuration applies only to form types registered via
+:ref:`autoconfiguration <services-autoconfigure>` (which is the default for your
+own services), and it sets ``submit`` as their default token identifier:
+
+.. configuration-block::
+
+    .. code-block:: yaml
+
+        # config/packages/csrf.yaml
+        framework:
+            form:
+                csrf_protection:
+                    token_id: 'submit'
+
+    .. code-block:: xml
+
+        <!-- config/packages/csrf.xml -->
+        <?xml version="1.0" encoding="UTF-8" ?>
+        <container xmlns="http://symfony.com/schema/dic/services"
+            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+            xmlns:framework="http://symfony.com/schema/dic/symfony"
+            xsi:schemaLocation="http://symfony.com/schema/dic/services
+                https://symfony.com/schema/dic/services/services-1.0.xsd
+                http://symfony.com/schema/dic/symfony
+                https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
+
+            <framework:config>
+                <framework:form>
+                    <framework:csrf-protection token-id="submit"/>
+                </framework:form>
+            </framework:config>
+        </container>
+
+    .. code-block:: php
+
+        // config/packages/csrf.php
+        use Symfony\Config\FrameworkConfig;
+
+        return static function (FrameworkConfig $framework): void {
+            $framework->form()
+                ->csrfProtection()
+                    ->tokenId('submit')
+            ;
+        };
+
+Forms configured with a token identifier listed in the above ``stateless_token_ids``
+option will use the stateless CSRF protection.
+
+Generating CSRF Token Using Javascript
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+In addition to the ``Origin`` and ``Referer`` HTTP headers, stateless CSRF protection
+can also validate tokens using a cookie and a header (named ``csrf-token`` by
+default; see the :ref:`CSRF configuration reference <reference-framework-csrf-protection>`).
+
+These additional checks are part of the **defense-in-depth** strategy provided by
+stateless CSRF protection. They are optional and require `some JavaScript`_ to
+be enabled. This JavaScript generates a cryptographically secure random token
+when a form is submitted. It then inserts the token into the form's hidden CSRF
+field and sends it in both a cookie and a request header.
+
+On the server side, CSRF token validation compares the values in the cookie and
+the header. This "double-submit" protection relies on the browser's same-origin
+policy and is further hardened by:
+
+* generating a new token for each submission (to prevent cookie fixation);
+* using ``samesite=strict`` and ``__Host-`` cookie attributes (to enforce HTTPS
+  and limit the cookie to the current domain).
+
+By default, the Symfony JavaScript snippet expects the hidden CSRF field to be
+named ``_csrf_token`` or to include the ``data-controller="csrf-protection"``
+attribute. You can adapt this logic to your needs as long as the same protocol
+is followed.
+
+To prevent validation from being downgraded, an extra behavioral check is performed:
+if (and only if) a session already exists, successful "double-submit" is remembered
+and becomes required for future requests. This ensures that once the optional cookie/header
+validation has been proven effective, it remains enforced for that session.
+
+.. note::
+
+    Enforcing "double-submit" validation on all requests is not recommended,
+    as it may lead to a broken user experience. The opportunistic approach
+    described above is preferred, allowing the application to gracefully
+    fall back to ``Origin`` / ``Referer`` checks when JavaScript is unavailable.
+
 .. _`Cross-site request forgery`: https://en.wikipedia.org/wiki/Cross-site_request_forgery
 .. _`BREACH`: https://en.wikipedia.org/wiki/BREACH
 .. _`CRIME`: https://en.wikipedia.org/wiki/CRIME
+.. _`some JavaScript`: https://github.com/symfony/recipes/blob/main/symfony/stimulus-bundle/2.20/assets/controllers/csrf_protection_controller.js
diff --git a/security/custom_authenticator.rst b/security/custom_authenticator.rst
index 8b2ec9d7f34..462ec21521c 100644
--- a/security/custom_authenticator.rst
+++ b/security/custom_authenticator.rst
@@ -1,18 +1,28 @@
 How to Write a Custom Authenticator
 ===================================
 
-Symfony comes with :ref:`many authenticators <security-authenticators>` and
-third party bundles also implement more complex cases like JWT and oAuth
-2.0. However, sometimes you need to implement a custom authentication
-mechanism that doesn't exist yet or you need to customize one. In such
-cases, you must create and use your own authenticator.
+Symfony comes with :ref:`many authenticators <security-authenticators>`, and
+third-party bundles also implement more complex cases like JWT and OAuth 2.0.
+However, sometimes you need to implement a custom authentication mechanism
+that doesn't exist yet, or you need to customize an existing one.
 
-Authenticators should implement the
-:class:`Symfony\\Component\\Security\\Http\\Authenticator\\AuthenticatorInterface`.
-You can also extend
-:class:`Symfony\\Component\\Security\\Http\\Authenticator\\AbstractAuthenticator`,
-which has a default implementation for the ``createToken()``
-method that fits most use-cases::
+To save time, you can install `Symfony Maker`_ and let Symfony generate a new
+authenticator by running the following command:
+
+.. code-block:: terminal
+
+    $ php bin/console make:security:custom
+
+      What is the class name of the authenticator (e.g. CustomAuthenticator):
+      > ApiKeyAuthenticator
+
+      updated: config/packages/security.yaml
+      created: src/Security/ApiKeyAuthenticator.php
+
+      Success!
+
+Open the ``src/Security/ApiKeyAuthenticator.php`` file created by this command,
+and you'll find something like the following::
 
     // src/Security/ApiKeyAuthenticator.php
     namespace App\Security;
@@ -77,13 +87,23 @@ method that fits most use-cases::
         }
     }
 
+Authenticators must implement the
+:class:`Symfony\\Component\\Security\\Http\\Authenticator\\AuthenticatorInterface`.
+You can also extend
+:class:`Symfony\\Component\\Security\\Http\\Authenticator\\AbstractAuthenticator`,
+which provides a default implementation of the ``createToken()`` method suitable
+for most use cases.
+
 .. tip::
 
-    If your custom authenticator is a login form, you can extend from the
+    If your custom authenticator is a login form, consider extending
     :class:`Symfony\\Component\\Security\\Http\\Authenticator\\AbstractLoginFormAuthenticator`
-    class instead to make your job easier.
+    to simplify your implementation.
 
-The authenticator can be enabled using the ``custom_authenticators`` setting:
+Custom authenticators must be explicitly enabled in the security configuration
+using the ``custom_authenticators`` setting of your firewall(s). If you used the
+``make:security:custom`` command, this configuration is already updated, but you
+should review it:
 
 .. configuration-block::
 
@@ -209,13 +229,20 @@ requires a user and some sort of "credentials" (e.g. a password).
 Use the
 :class:`Symfony\\Component\\Security\\Http\\Authenticator\\Passport\\Badge\\UserBadge`
 to attach the user to the passport. The ``UserBadge`` requires a user
-identifier (e.g. the username or email), which is used to load the user
-using :ref:`the user provider <security-user-providers>`::
+identifier (e.g. the username or email)::
 
     use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
 
     // ...
-    $passport = new Passport(new UserBadge($email), $credentials);
+    $passport = new Passport(new UserBadge($userIdentifier), $credentials);
+
+User Identifier
+~~~~~~~~~~~~~~~
+
+The user identifier is a unique string that identifies the user. It is often
+something like their email address or username, but it can be any unique value
+associated with the user. It allows loading the user through the configured
+:ref:`user provider <security-user-providers>`.
 
 .. note::
 
@@ -255,6 +282,69 @@ using :ref:`the user provider <security-user-providers>`::
             }
         }
 
+Some applications normalize user identifiers before processing them. For example,
+lowercasing identifiers helps treat values like "john.doe", "John.Doe", or
+"JOHN.DOE" as equivalent in systems where identifiers are case-insensitive.
+
+If needed, you can pass a normalizer as the third argument to ``UserBadge``.
+This callable receives the ``$userIdentifier`` and must return a string.
+
+.. versionadded:: 7.3
+
+    Support for user identifier normalizers was introduced in Symfony 7.3.
+
+The example below uses a normalizer that converts usernames to a normalized,
+ASCII-only, lowercase format::
+
+    // src/Security/NormalizedUserBadge.php
+    namespace App\Security;
+
+    use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
+    use function Symfony\Component\String\u;
+
+    final class NormalizedUserBadge extends UserBadge
+    {
+        public function __construct(string $identifier)
+        {
+            $callback = static fn (string $identifier): string => u($identifier)->normalize(UnicodeString::NFKC)->ascii()->lower()->toString();
+
+            parent::__construct($identifier, null, $callback);
+        }
+    }
+
+::
+
+    // src/Security/PasswordAuthenticator.php
+    namespace App\Security;
+
+    final class PasswordAuthenticator extends AbstractLoginFormAuthenticator
+    {
+        // simplified for brevity
+        public function authenticate(Request $request): Passport
+        {
+            $username = (string) $request->request->get('username', '');
+            $password = (string) $request->request->get('password', '');
+
+            $request->getSession()
+                ->set(SecurityRequestAttributes::LAST_USERNAME, $username);
+
+            return new Passport(
+                new NormalizedUserBadge($username),
+                new PasswordCredentials($password),
+                [
+                    // all other useful badges
+                ]
+            );
+        }
+    }
+
+User Credential
+~~~~~~~~~~~~~~~
+
+The user credential is used to authenticate the user; that is, to verify
+the validity of the provided information (such as a password, an API token,
+or custom credentials).
+
 The following credential classes are supported by default:
 
 :class:`Symfony\\Component\\Security\\Http\\Authenticator\\Passport\\Credentials\\PasswordCredentials`
@@ -389,4 +479,5 @@ authenticator methods (e.g. ``createToken()``)::
         }
     }
 
+.. _`Symfony Maker`: https://symfony.com/doc/current/bundles/SymfonyMakerBundle/index.html
 .. _`session storage flooding`: https://symfony.com/blog/cve-2016-4423-large-username-storage-in-session
diff --git a/security/expressions.rst b/security/expressions.rst
index 569c7f093bf..a4ec02c7b84 100644
--- a/security/expressions.rst
+++ b/security/expressions.rst
@@ -201,6 +201,38 @@ Inside the subject's expression, you have access to two variables:
 ``args``
     An array of controller arguments that are passed to the controller.
 
+Additionally to expressions, the ``#[IsGranted]`` attribute also accepts
+closures that return a boolean value. The subject can also be a closure that
+returns an array of values that will be injected into the closure::
+
+    // src/Controller/MyController.php
+    namespace App\Controller;
+
+    use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+    use Symfony\Component\HttpFoundation\Response;
+    use Symfony\Component\Security\Http\Attribute\IsGranted;
+    use Symfony\Component\Security\Http\Attribute\IsGrantedContext;
+
+    class MyController extends AbstractController
+    {
+        #[IsGranted(static function (IsGrantedContext $context, mixed $subject) {
+            return $context->user === $subject['post']->getAuthor();
+        }, subject: static function (array $args) {
+            return [
+                'post' => $args['post'],
+            ];
+        })]
+        public function index($post): Response
+        {
+            // ...
+        }
+    }
+
+.. versionadded:: 7.3
+
+    The support for closures in the ``#[IsGranted]`` attribute was introduced
+    in Symfony 7.3 and requires PHP 8.5.
+
 Learn more
 ----------
 
diff --git a/security/ldap.rst b/security/ldap.rst
index 081be764290..c4c3646122b 100644
--- a/security/ldap.rst
+++ b/security/ldap.rst
@@ -256,6 +256,24 @@ This is the default role you wish to give to a user fetched from the LDAP
 server. If you do not configure this key, your users won't have any roles,
 and will not be considered as authenticated fully.
 
+role_fetcher
+............
+
+**Type**: ``string`` **Default**: ``null``
+
+When your LDAP service provides user roles, this option allows you to define
+the service that retrieves these roles. The role fetcher service must implement
+the ``Symfony\Component\Ldap\Security\RoleFetcherInterface``. When this option
+is set, the ``default_roles`` option is ignored.
+
+Symfony provides ``Symfony\Component\Ldap\Security\MemberOfRoles``, a concrete
+implementation of the interface that fetches roles from the ``ismemberof``
+attribute.
+
+.. versionadded:: 7.3
+
+    The ``role_fetcher`` configuration option was introduced in Symfony 7.3.
+
 uid_key
 .......
 
diff --git a/security/passwords.rst b/security/passwords.rst
index fe20187b3a0..7f05bc3acb9 100644
--- a/security/passwords.rst
+++ b/security/passwords.rst
@@ -124,75 +124,38 @@ Further in this article, you can find a
 
         .. code-block:: yaml
 
-            # config/packages/test/security.yaml
-            security:
-                # ...
-
-                password_hashers:
-                    # Use your user class name here
-                    App\Entity\User:
-                        algorithm: plaintext # disable hashing (only do this in tests!)
-
-                    # or use the lowest possible values
-                    App\Entity\User:
-                        algorithm: auto # This should be the same value as in config/packages/security.yaml
-                        cost: 4 # Lowest possible value for bcrypt
-                        time_cost: 3 # Lowest possible value for argon
-                        memory_cost: 10 # Lowest possible value for argon
-
-        .. code-block:: xml
-
-            <!-- config/packages/test/security.xml -->
-            <?xml version="1.0" encoding="UTF-8"?>
-            <srv:container xmlns="http://symfony.com/schema/dic/security"
-                xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-                xmlns:srv="http://symfony.com/schema/dic/services"
-                xsi:schemaLocation="http://symfony.com/schema/dic/services
-                    https://symfony.com/schema/dic/services/services-1.0.xsd">
-
-                <config>
-                    <!-- class: Use your user class name here -->
-                    <!-- algorithm: disable hashing (only do this in tests!) -->
-                    <security:password-hasher
-                        class="App\Entity\User"
-                        algorithm="plaintext"
-                    />
-
-                    <!-- or use the lowest possible values -->
-                    <!-- algorithm: This should be the same value as in config/packages/security.yaml -->
-                    <!-- cost: Lowest possible value for bcrypt -->
-                    <!-- time_cost: Lowest possible value for argon -->
-                    <!-- memory_cost: Lowest possible value for argon -->
-                    <security:password-hasher
-                        class="App\Entity\User"
-                        algorithm="auto"
-                        cost="4"
-                        time_cost="3"
-                        memory_cost="10"
-                    />
-                </config>
-            </srv:container>
+            # config/packages/security.yaml
+            when@test:
+                security:
+                    # ...
+
+                    password_hashers:
+                        # Use your user class name here
+                        App\Entity\User:
+                            algorithm: auto
+                            cost: 4 # Lowest possible value for bcrypt
+                            time_cost: 3 # Lowest possible value for argon
+                            memory_cost: 10 # Lowest possible value for argon
 
         .. code-block:: php
 
-            // config/packages/test/security.php
+            // config/packages/security.php
             use App\Entity\User;
+            use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
             use Symfony\Config\SecurityConfig;
 
-            return static function (SecurityConfig $security): void {
+            return static function (SecurityConfig $security, ContainerConfigurator $container): void {
                 // ...
 
-                // Use your user class name here
-                $security->passwordHasher(User::class)
-                    ->algorithm('plaintext'); // disable hashing (only do this in tests!)
-
-                // or use the lowest possible values
-                $security->passwordHasher(User::class)
-                    ->algorithm('auto') // This should be the same value as in config/packages/security.yaml
-                    ->cost(4) // Lowest possible value for bcrypt
-                    ->timeCost(2) // Lowest possible value for argon
-                    ->memoryCost(10) // Lowest possible value for argon
-                ;
+                if ('test' === $container->env()) {
+                    // Use your user class name here
+                    $security->passwordHasher(User::class)
+                        ->algorithm('auto') // This should be the same value as in config/packages/security.yaml
+                        ->cost(4) // Lowest possible value for bcrypt
+                        ->timeCost(2) // Lowest possible value for argon
+                        ->memoryCost(10) // Lowest possible value for argon
+                    ;
+                }
             };
 
 Hashing the Password
@@ -500,13 +463,14 @@ the user provider::
     namespace App\Security;
 
     // ...
+    use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
     use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
 
     class UserProvider implements UserProviderInterface, PasswordUpgraderInterface
     {
         // ...
 
-        public function upgradePassword(UserInterface $user, string $newHashedPassword): void
+        public function upgradePassword(PasswordAuthenticatedUserInterface $user, string $newHashedPassword): void
         {
             // set the new hashed password on the User object
             $user->setPassword($newHashedPassword);
diff --git a/security/remember_me.rst b/security/remember_me.rst
index 8fac6d78849..2fd0f7e8d1e 100644
--- a/security/remember_me.rst
+++ b/security/remember_me.rst
@@ -19,7 +19,7 @@ the session lasts using a cookie with the ``remember_me`` firewall option:
                 main:
                     # ...
                     remember_me:
-                        secret:   '%kernel.secret%' # required
+                        secret: '%kernel.secret%'
                         lifetime: 604800 # 1 week in seconds
                         # by default, the feature is enabled by checking a
                         # checkbox in the login form (see below), uncomment the
@@ -44,7 +44,7 @@ the session lasts using a cookie with the ``remember_me`` firewall option:
                 <firewall name="main">
                     <!-- ... -->
 
-                    <!-- secret: required
+                    <!-- secret: default to "%kernel.secret%"
                          lifetime: 604800 is 1 week in seconds -->
                     <remember-me
                         secret="%kernel.secret%"
@@ -67,7 +67,7 @@ the session lasts using a cookie with the ``remember_me`` firewall option:
             $security->firewall('main')
                 // ...
                 ->rememberMe()
-                    ->secret('%kernel.secret%') // required
+                    ->secret('%kernel.secret%')
                     ->lifetime(604800) // 1 week in seconds
 
                     // by default, the feature is enabled by checking a
@@ -77,9 +77,11 @@ the session lasts using a cookie with the ``remember_me`` firewall option:
             ;
         };
 
-The ``secret`` option is the only required option and it is used to sign
-the remember me cookie. It's common to use the ``kernel.secret`` parameter,
-which is defined using the ``APP_SECRET`` environment variable.
+.. versionadded:: 7.2
+
+    The ``secret`` option is no longer required starting from Symfony 7.2. By
+    default, ``%kernel.secret%`` is used, which is defined using the
+    ``APP_SECRET`` environment variable.
 
 After enabling the ``remember_me`` system in the configuration, there are a
 couple more things to do before remember me works correctly:
@@ -171,7 +173,6 @@ allow users to opt-out. In these cases, you can use the
                 main:
                     # ...
                     remember_me:
-                        secret: '%kernel.secret%'
                         # ...
                         always_remember_me: true
 
@@ -194,7 +195,6 @@ allow users to opt-out. In these cases, you can use the
                     <!-- ... -->
 
                     <remember-me
-                        secret="%kernel.secret%"
                         always-remember-me="true"
                     />
                 </firewall>
@@ -211,7 +211,6 @@ allow users to opt-out. In these cases, you can use the
             $security->firewall('main')
                 // ...
                 ->rememberMe()
-                    ->secret('%kernel.secret%')
                     // ...
                     ->alwaysRememberMe(true)
             ;
@@ -335,7 +334,6 @@ are fetched from the user object using the
                 main:
                     # ...
                     remember_me:
-                        secret: '%kernel.secret%'
                         # ...
                         signature_properties: ['password', 'updatedAt']
 
@@ -357,7 +355,7 @@ are fetched from the user object using the
                 <firewall name="main">
                     <!-- ... -->
 
-                    <remember-me secret="%kernel.secret%">
+                    <remember-me>
                         <signature-property>password</signature-property>
                         <signature-property>updatedAt</signature-property>
                     </remember-me>
@@ -375,7 +373,6 @@ are fetched from the user object using the
             $security->firewall('main')
                 // ...
                 ->rememberMe()
-                    ->secret('%kernel.secret%')
                     // ...
                     ->signatureProperties(['password', 'updatedAt'])
             ;
@@ -419,7 +416,6 @@ You can enable the doctrine token provider using the ``doctrine`` setting:
                 main:
                     # ...
                     remember_me:
-                        secret: '%kernel.secret%'
                         # ...
                         token_provider:
                             doctrine: true
@@ -442,7 +438,7 @@ You can enable the doctrine token provider using the ``doctrine`` setting:
                 <firewall name="main">
                     <!-- ... -->
 
-                    <remember-me secret="%kernel.secret%">
+                    <remember-me>
                         <token-provider doctrine="true"/>
                     </remember-me>
                 </firewall>
@@ -459,7 +455,6 @@ You can enable the doctrine token provider using the ``doctrine`` setting:
             $security->firewall('main')
                 // ...
                 ->rememberMe()
-                    ->secret('%kernel.secret%')
                     // ...
                     ->tokenProvider([
                         'doctrine' => true,
diff --git a/security/user_checkers.rst b/security/user_checkers.rst
index d62cc0bea32..ec8f49da522 100644
--- a/security/user_checkers.rst
+++ b/security/user_checkers.rst
@@ -21,6 +21,8 @@ displayed to the user::
     namespace App\Security;
 
     use App\Entity\User as AppUser;
+    use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
+    use Symfony\Component\Security\Core\Exception\AccessDeniedException;
     use Symfony\Component\Security\Core\Exception\AccountExpiredException;
     use Symfony\Component\Security\Core\Exception\CustomUserMessageAccountStatusException;
     use Symfony\Component\Security\Core\User\UserCheckerInterface;
@@ -40,7 +42,7 @@ displayed to the user::
             }
         }
 
-        public function checkPostAuth(UserInterface $user): void
+        public function checkPostAuth(UserInterface $user, TokenInterface $token): void
         {
             if (!$user instanceof AppUser) {
                 return;
@@ -50,9 +52,17 @@ displayed to the user::
             if ($user->isExpired()) {
                 throw new AccountExpiredException('...');
             }
+
+            if (!\in_array('foo', $token->getRoleNames())) {
+                throw new AccessDeniedException('...');
+            }
         }
     }
 
+.. versionadded:: 7.2
+
+    The ``token`` argument for the ``checkPostAuth()`` method was introduced in Symfony 7.2.
+
 Enabling the Custom User Checker
 --------------------------------
 
diff --git a/security/user_providers.rst b/security/user_providers.rst
index 09d47c270f2..73b723faaaf 100644
--- a/security/user_providers.rst
+++ b/security/user_providers.rst
@@ -347,23 +347,23 @@ providers until the user is found:
         return static function (SecurityConfig $security): void {
             // ...
 
-            $backendProvider = $security->provider('backend_users')
+            $security->provider('backend_users')
                 ->ldap()
                 // ...
             ;
 
-            $legacyProvider = $security->provider('legacy_users')
+            $security->provider('legacy_users')
                 ->entity()
                 // ...
             ;
 
-            $userProvider = $security->provider('users')
+            $security->provider('users')
                 ->entity()
                 // ...
             ;
 
-            $allProviders = $security->provider('all_users')->chain()
-                ->providers([$backendProvider, $legacyProvider, $userProvider])
+            $security->provider('all_users')->chain()
+                ->providers(['backend_users', 'legacy_users', 'users'])
             ;
         };
 
@@ -425,7 +425,7 @@ command will generate a nice skeleton to get you started::
         public function refreshUser(UserInterface $user): UserInterface
         {
             if (!$user instanceof User) {
-                throw new UnsupportedUserException(sprintf('Invalid user class "%s".', get_class($user)));
+                throw new UnsupportedUserException(sprintf('Invalid user class "%s".', $user::class));
             }
 
             // Return a User object after making sure its data is "fresh".
diff --git a/security/voters.rst b/security/voters.rst
index e7452fadf99..e621263abb4 100644
--- a/security/voters.rst
+++ b/security/voters.rst
@@ -40,23 +40,21 @@ or extend :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\Vote
 which makes creating a voter even easier::
 
     use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
+    use Symfony\Component\Security\Core\Authorization\Voter\Vote;
     use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
 
     abstract class Voter implements VoterInterface
     {
         abstract protected function supports(string $attribute, mixed $subject): bool;
-        abstract protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool;
+        abstract protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token, ?Vote $vote = null): bool;
     }
 
-.. _how-to-use-the-voter-in-a-controller:
-
-.. tip::
+.. versionadded:: 7.3
+    
+    The ``$vote`` argument of the ``voteOnAttribute()`` method was introduced
+    in Symfony 7.3.
 
-    Checking each voter several times can be time consuming for applications
-    that perform a lot of permission checks. To improve performance in those cases,
-    you can make your voters implement the :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\CacheableVoterInterface`.
-    This allows the access decision manager to remember the attribute and type
-    of subject supported by the voter, to only call the needed voters each time.
+.. _how-to-use-the-voter-in-a-controller:
 
 Setup: Checking for Access in a Controller
 ------------------------------------------
@@ -140,6 +138,7 @@ would look like this::
     use App\Entity\Post;
     use App\Entity\User;
     use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
+    use Symfony\Component\Security\Core\Authorization\Voter\Vote;
     use Symfony\Component\Security\Core\Authorization\Voter\Voter;
 
     class PostVoter extends Voter
@@ -163,12 +162,13 @@ would look like this::
             return true;
         }
 
-        protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
+        protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token, ?Vote $vote = null): bool
         {
             $user = $token->getUser();
 
             if (!$user instanceof User) {
                 // the user must be logged in; if not, deny access
+                $vote?->addReason('The user is not logged in.');
                 return false;
             }
 
@@ -178,7 +178,7 @@ would look like this::
 
             return match($attribute) {
                 self::VIEW => $this->canView($post, $user),
-                self::EDIT => $this->canEdit($post, $user),
+                self::EDIT => $this->canEdit($post, $user, $vote),
                 default => throw new \LogicException('This code should not be reached!')
             };
         }
@@ -194,10 +194,19 @@ would look like this::
             return !$post->isPrivate();
         }
 
-        private function canEdit(Post $post, User $user): bool
+        private function canEdit(Post $post, User $user, ?Vote $vote): bool
         {
-            // this assumes that the Post object has a `getOwner()` method
-            return $user === $post->getOwner();
+            // this assumes that the Post object has a `getAuthor()` method
+            if ($user === $post->getAuthor()) {
+                return true;
+            }
+
+            $vote?->addReason(sprintf(
+                'The logged in user (username: %s) is not the author of this post (id: %d).',
+                $user->getUsername(), $post->getId()
+            ));
+
+            return false;
         }
     }
 
@@ -215,11 +224,12 @@ To recap, here's what's expected from the two abstract methods:
     return ``true`` if the attribute is ``view`` or ``edit`` and if the object is
     a ``Post`` instance.
 
-``voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token)``
+``voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token, ?Vote $vote = null)``
     If you return ``true`` from ``supports()``, then this method is called. Your
     job is to return ``true`` to allow access and ``false`` to deny access.
-    The ``$token`` can be used to find the current user object (if any). In this
-    example, all of the complex business logic is included to determine access.
+    The ``$token`` can be used to find the current user object (if any).
+    The ``$vote`` argument can be used to provide an explanation for the vote.
+    This explanation is included in log messages and on exception pages.
 
 .. _declaring-the-voter-as-a-service:
 
@@ -256,7 +266,7 @@ with ``ROLE_SUPER_ADMIN``::
         ) {
         }
 
-        protected function voteOnAttribute($attribute, mixed $subject, TokenInterface $token): bool
+        protected function voteOnAttribute($attribute, mixed $subject, TokenInterface $token, ?Vote $vote = null): bool
         {
             // ...
 
@@ -292,6 +302,89 @@ If you're using the :ref:`default services.yaml configuration <service-container
 you're done! Symfony will automatically pass the ``security.helper``
 service when instantiating your voter (thanks to autowiring).
 
+Improving Voter Performance
+---------------------------
+
+If your application defines many voters and checks permissions on many objects
+during a single request, this can impact performance. Most of the time, voters
+only care about specific permissions (attributes), such as ``EDIT_BLOG_POST``,
+or specific object types, such as ``User`` or ``Invoice``. That's why Symfony
+can cache the voter resolution (i.e. the decision to apply or skip a voter for
+a given attribute or object).
+
+To enable this optimization, make your voter implement
+:class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\CacheableVoterInterface`.
+This is already the case when extending the abstract ``Voter`` class shown above.
+Then, override one or both of the following methods::
+
+    use App\Entity\Post;
+    use Symfony\Component\Security\Core\Authorization\Voter\Voter;
+    // ...
+
+    class PostVoter extends Voter
+    {
+        const VIEW = 'view';
+        const EDIT = 'edit';
+
+        protected function supports(string $attribute, mixed $subject): bool
+        {
+            // ...
+        }
+
+        protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
+        {
+            // ...
+        }
+
+        // this method returns true if the voter applies to the given attribute;
+        // if it returns false, Symfony won't call it again for this attribute
+        public function supportsAttribute(string $attribute): bool
+        {
+            return in_array($attribute, [self::VIEW, self::EDIT], true);
+        }
+
+        // this method returns true if the voter applies to the given object class/type;
+        // if it returns false, Symfony won't call it again for that type of object
+        public function supportsType(string $subjectType): bool
+        {
+            // you can't use a simple Post::class === $subjectType comparison
+            // because the subject type might be a Doctrine proxy class
+            return is_a($subjectType, Post::class, true);
+        }
+    }
+
+.. _security-voters-change-message-and-status-code:
+
+Changing the message and status code returned
+---------------------------------------------
+
+By default, the ``#[IsGranted]`` attribute will throw a
+:class:`Symfony\\Component\\Security\\Core\\Exception\\AccessDeniedException`
+and return an http **403** status code with **Access Denied** as message.
+
+However, you can change this behavior by specifying the message and status code returned::
+
+    // src/Controller/PostController.php
+
+    // ...
+    use Symfony\Component\Security\Http\Attribute\IsGranted;
+
+    class PostController extends AbstractController
+    {
+        #[Route('/posts/{id}', name: 'post_show')]
+        #[IsGranted('show', 'post', 'Post not found', 404)]
+        public function show(Post $post): Response
+        {
+            // ...
+        }
+    }
+
+.. tip::
+
+    If the status code is different than 403, an
+    :class:`Symfony\\Component\\HttpKernel\\Exception\\HttpException`
+    will be thrown instead.
+
 .. _security-voters-change-strategy:
 
 Changing the Access Decision Strategy
@@ -463,35 +556,3 @@ must implement the :class:`Symfony\\Component\\Security\\Core\\Authorization\\Ac
                 // ...
             ;
         };
-
-.. _security-voters-change-message-and-status-code:
-
-Changing the message and status code returned
----------------------------------------------
-
-By default, the ``#[IsGranted]`` attribute will throw a
-:class:`Symfony\\Component\\Security\\Core\\Exception\\AccessDeniedException`
-and return an http **403** status code with **Access Denied** as message.
-
-However, you can change this behavior by specifying the message and status code returned::
-
-    // src/Controller/PostController.php
-
-    // ...
-    use Symfony\Component\Security\Http\Attribute\IsGranted;
-
-    class PostController extends AbstractController
-    {
-        #[Route('/posts/{id}', name: 'post_show')]
-        #[IsGranted('show', 'post', 'Post not found', 404)]
-        public function show(Post $post): Response
-        {
-            // ...
-        }
-    }
-
-.. tip::
-
-    If the status code is different than 403, an
-    :class:`Symfony\\Component\\HttpKernel\\Exception\\HttpException`
-    will be thrown instead.
diff --git a/serializer.rst b/serializer.rst
index d541310aa15..eb06f1b34a1 100644
--- a/serializer.rst
+++ b/serializer.rst
@@ -10,7 +10,7 @@ to a PHP object that is consumed by your application. Then, when generating
 the response, you can use the serializer to transform the PHP objects back
 to a JSON response.
 
-It can also be used to for instance load CSV configuration data as PHP
+It can also be used to, for instance, load CSV configuration data as PHP
 objects, or even to transform between formats (e.g. YAML to XML).
 
 .. _activating_the_serializer:
@@ -344,7 +344,7 @@ instance to disallow extra fields while deserializing:
 
         return static function (FrameworkConfig $framework): void {
             $framework->serializer()
-                ->defaultContext('', [
+                ->defaultContext([
                     'allow_extra_attributes' => false,
                 ])
             ;
@@ -516,8 +516,8 @@ You can also specify a context specific to normalization or denormalization:
             attributes:
                 createdAt:
                     contexts:
-                        - normalizationContext: { datetime_format: 'Y-m-d' }
-                          denormalizationContext: { datetime_format: !php/const \DateTime::RFC3339 }
+                        - normalization_context: { datetime_format: 'Y-m-d' }
+                          denormalization_context: { datetime_format: !php/const \DateTime::RFC3339 }
 
     .. code-block:: xml
 
@@ -629,6 +629,35 @@ all the properties of the class::
         // ...
     }
 
+Serializing JSON Using Streams
+------------------------------
+
+Symfony can encode PHP data structures to JSON streams and decode JSON streams
+back into PHP data structures.
+
+To do this, it relies on the :doc:`JsonStreamer component </serializer/streaming_json>`,
+which is designed for high efficiency and can process large JSON data incrementally,
+without needing to load the entire content into memory.
+
+When deciding between the Serializer component and the JsonStreamer component,
+consider the following:
+
+* **Serializer Component**: Best suited for use cases that require flexibility,
+  such as dynamically manipulating object structures using normalizers and
+  denormalizers, or handling complex objects with multiple serialization
+  formats. It also supports output formats beyond JSON (including your own
+  custom ones).
+* **JsonStreamer Component**: Best suited for simple objects and scenarios that
+  demand high performance and low memory usage. It's particularly effective
+  for processing very large JSON datasets or when streaming JSON in real-time
+  without loading the entire dataset into memory.
+
+The choice depends on your specific use case. The JsonStreamer component is
+tailored for performance and memory efficiency, whereas the Serializer
+component provides greater flexibility and broader format support.
+
+Read more about :doc:`streaming JSON </serializer/streaming_json>`.
+
 Serializing to or from PHP Arrays
 ---------------------------------
 
@@ -1239,6 +1268,71 @@ setting the ``name_converter`` setting to
         ];
         $serializer = new Serializer($normalizers, $encoders);
 
+snake_case to CamelCase
+~~~~~~~~~~~~~~~~~~~~~~~
+
+In Symfony applications, it is common to use camelCase for naming properties.
+However some packages may follow a snake_case convention.
+
+Symfony provides a built-in name converter designed to transform between
+CamelCase and snake_case styles during serialization and deserialization
+processes. You can use it instead of the metadata-aware name converter by
+setting the ``name_converter`` setting to
+``serializer.name_converter.snake_case_to_camel_case``:
+
+.. configuration-block::
+
+    .. code-block:: yaml
+
+        # config/packages/serializer.yaml
+        framework:
+            serializer:
+                name_converter: 'serializer.name_converter.snake_case_to_camel_case'
+
+    .. code-block:: xml
+
+        <!-- config/packages/serializer.xml -->
+        <?xml version="1.0" encoding="UTF-8" ?>
+        <container xmlns="http://symfony.com/schema/dic/services"
+            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+            xmlns:framework="http://symfony.com/schema/dic/symfony"
+            xsi:schemaLocation="http://symfony.com/schema/dic/services
+                https://symfony.com/schema/dic/services/services-1.0.xsd
+                http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
+
+            <framework:config>
+                <framework:serializer
+                    name-converter="serializer.name_converter.snake_case_to_camel_case"
+                />
+            </framework:config>
+        </container>
+
+    .. code-block:: php
+
+        // config/packages/serializer.php
+        use Symfony\Config\FrameworkConfig;
+
+        return static function (FrameworkConfig $framework): void {
+            $framework->serializer()
+                ->nameConverter('serializer.name_converter.snake_case_to_camel_case')
+            ;
+        };
+
+    .. code-block:: php-standalone
+
+        use Symfony\Component\Serializer\NameConverter\SnakeCaseToCamelCaseNameConverter;
+        use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
+
+        // ...
+        $normalizers = [
+            new ObjectNormalizer(null, new SnakeCaseToCamelCaseNameConverter()),
+        ];
+        $serializer = new Serializer($normalizers, $encoders);
+
+.. versionadded:: 7.2
+
+    The snake_case to CamelCase converter was introduced in Symfony 7.2.
+
 .. _serializer-built-in-normalizers:
 
 Serializer Normalizers
@@ -1322,6 +1416,14 @@ normalizers (in order of priority):
     By default, an exception is thrown when data is not a valid backed enumeration. If you
     want ``null`` instead, you can set the ``BackedEnumNormalizer::ALLOW_INVALID_VALUES`` option.
 
+:class:`Symfony\\Component\\Serializer\\Normalizer\\NumberNormalizer`
+    This normalizer converts between :phpclass:`BcMath\\Number` or :phpclass:`GMP` objects and
+    strings or integers.
+
+.. versionadded:: 7.2
+
+    The ``NumberNormalizer`` was introduced in Symfony 7.2.
+
 :class:`Symfony\\Component\\Serializer\\Normalizer\\DataUriNormalizer`
     This normalizer converts between :phpclass:`SplFileInfo` objects and a
     `data URI`_ string (``data:...``) such that files can be embedded into
@@ -1344,6 +1446,28 @@ normalizers (in order of priority):
     This denormalizer converts an array of arrays to an array of objects
     (with the given type). See :ref:`Handling Arrays <serializer-handling-arrays>`.
 
+    Use :class:`Symfony\\Component\\PropertyInfo\\PropertyInfoExtractor` to provide
+    hints with annotations like ``@var Person[]``:
+
+    .. configuration-block::
+
+        .. code-block:: php-standalone
+
+            use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor;
+            use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
+            use Symfony\Component\PropertyInfo\PropertyInfoExtractor;
+            use Symfony\Component\Serializer\Encoder\JsonEncoder;
+            use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
+            use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader;
+            use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer;
+            use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
+            use Symfony\Component\Serializer\Serializer;
+
+            $propertyInfo = new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]);
+            $normalizers = [new ObjectNormalizer(new ClassMetadataFactory(new AttributeLoader()), null, null, $propertyInfo), new ArrayDenormalizer()];
+
+            $this->serializer = new Serializer($normalizers, [new JsonEncoder()]);
+
 :class:`Symfony\\Component\\Serializer\\Normalizer\\ObjectNormalizer`
     This is the most powerful default normalizer and used for any object
     that could not be normalized by the other normalizers.
@@ -1369,7 +1493,7 @@ Built-in Normalizers
 ~~~~~~~~~~~~~~~~~~~~
 
 Besides the normalizers registered by default (see previous section), the
-serializer component also provides some extra normalizers.You can register
+serializer component also provides some extra normalizers. You can register
 these by defining a service and tag it with :ref:`serializer.normalizer <reference-dic-tags-serializer-normalizer>`.
 For instance, to use the ``CustomNormalizer`` you have to define a service
 like:
@@ -1472,6 +1596,254 @@ like:
             PropertyNormalizer::NORMALIZE_VISIBILITY => PropertyNormalizer::NORMALIZE_PUBLIC | PropertyNormalizer::NORMALIZE_PROTECTED,
         ]);
 
+Named Serializers
+-----------------
+
+.. versionadded:: 7.2
+
+    Named serializers were introduced in Symfony 7.2.
+
+Sometimes, you may need multiple configurations for the serializer, such as
+different default contexts, name converters, or sets of normalizers and encoders,
+depending on the use case. For example, when your application communicates with
+multiple APIs, each of which follows its own set of serialization rules.
+
+You can achieve this by configuring multiple serializer instances using
+the ``named_serializers`` option:
+
+.. configuration-block::
+
+    .. code-block:: yaml
+
+        # config/packages/serializer.yaml
+        framework:
+            serializer:
+                named_serializers:
+                    api_client1:
+                        name_converter: 'serializer.name_converter.camel_case_to_snake_case'
+                        default_context:
+                            enable_max_depth: true
+                    api_client2:
+                        default_context:
+                            enable_max_depth: false
+
+    .. code-block:: xml
+
+        <!-- config/packages/serializer.xml -->
+        <?xml version="1.0" encoding="UTF-8" ?>
+        <container xmlns="http://symfony.com/schema/dic/services"
+            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+            xmlns:framework="http://symfony.com/schema/dic/symfony"
+            xsi:schemaLocation="http://symfony.com/schema/dic/services
+                https://symfony.com/schema/dic/services/services-1.0.xsd
+                http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
+
+            <framework:config>
+                <framework:serializer>
+
+                    <framework:named-serializer
+                        name="api_client1"
+                        name-converter="serializer.name_converter.camel_case_to_snake_case"
+                    >
+                        <framework:default-context>
+                            <framework:enable_max_depth>true</framework:enable_max_depth>
+                        </framework:default-context>
+                    </framework:named-serializer>
+
+                    <framework:named-serializer name="api_client2">
+                        <framework:default-context>
+                            <framework:enable_max_depth>false</framework:enable_max_depth>
+                        </framework:default-context>
+                    </framework:named-serializer>
+
+                </framework:serializer>
+            </framework:config>
+        </container>
+
+    .. code-block:: php
+
+        // config/packages/serializer.php
+        use Symfony\Config\FrameworkConfig;
+
+        return static function (FrameworkConfig $framework): void {
+            $framework->serializer()
+                ->namedSerializer('api_client1')
+                    ->nameConverter('serializer.name_converter.camel_case_to_snake_case')
+                    ->defaultContext([
+                        'enable_max_depth' => true,
+                    ])
+            ;
+            $framework->serializer()
+                ->namedSerializer('api_client2')
+                    ->defaultContext([
+                        'enable_max_depth' => false,
+                    ])
+            ;
+        };
+
+You can inject these different serializer instances
+using :ref:`named aliases <autowiring-multiple-implementations-same-type>`::
+
+    namespace App\Controller;
+
+    // ...
+    use Symfony\Component\DependencyInjection\Attribute\Target;
+
+    class PersonController extends AbstractController
+    {
+        public function index(
+            SerializerInterface $serializer,           // default serializer
+            SerializerInterface $apiClient1Serializer, // api_client1 serializer
+            #[Target('apiClient2.serializer')]         // api_client2 serializer
+            SerializerInterface $customName,
+        ) {
+            // ...
+        }
+    }
+
+By default, named serializers use the built-in set of normalizers and encoders,
+just like the main serializer service. However, you can customize them by
+registering additional normalizers or encoders for a specific named serializer.
+To do that, add a ``serializer`` attribute to
+the :ref:`serializer.normalizer <reference-dic-tags-serializer-normalizer>`
+or :ref:`serializer.encoder <reference-dic-tags-serializer-encoder>` tags:
+
+.. configuration-block::
+
+    .. code-block:: yaml
+
+        # config/services.yaml
+        services:
+            # ...
+
+            Symfony\Component\Serializer\Normalizer\CustomNormalizer:
+                # prevent this normalizer from being automatically added to the default serializer
+                autoconfigure: false
+                tags:
+                    # add this normalizer only to a specific named serializer
+                    - serializer.normalizer: { serializer: 'api_client1' }
+                    # add this normalizer to several named serializers
+                    - serializer.normalizer: { serializer: [ 'api_client1', 'api_client2' ] }
+                    # add this normalizer to all serializers, including the default one
+                    - serializer.normalizer: { serializer: '*' }
+
+    .. code-block:: xml
+
+        <!-- config/services.xml -->
+        <?xml version="1.0" encoding="UTF-8" ?>
+        <container xmlns="http://symfony.com/schema/dic/services"
+            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+            xsi:schemaLocation="http://symfony.com/schema/dic/services
+                https://symfony.com/schema/dic/services/services-1.0.xsd">
+
+            <services>
+                <!-- ... -->
+
+                <!-- prevent this normalizer from being automatically added to the default serializer -->
+                <service
+                    id="Symfony\Component\Serializer\Normalizer\CustomNormalizer"
+                    autoconfigure="false"
+                >
+                    <!-- add this normalizer only to a specific named serializer -->
+                    <tag name="serializer.normalizer" serializer="api_client1"/>
+
+                    <!-- add this normalizer to several named serializers -->
+                    <tag name="serializer.normalizer" serializer="api_client1"/>
+                    <tag name="serializer.normalizer" serializer="api_client2"/>
+
+                    <!-- add this normalizer to all serializers, including the default one -->
+                    <tag name="serializer.normalizer" serializer="*"/>
+                </service>
+            </services>
+        </container>
+
+    .. code-block:: php
+
+        // config/services.php
+        namespace Symfony\Component\DependencyInjection\Loader\Configurator;
+
+        use Symfony\Component\Serializer\Normalizer\CustomNormalizer;
+
+        return function(ContainerConfigurator $container) {
+            // ...
+
+            $services->set(CustomNormalizer::class)
+                // prevent this normalizer from being automatically added to the default serializer
+                ->autoconfigure(false)
+
+                // add this normalizer only to a specific named serializer
+                ->tag('serializer.normalizer', ['serializer' => 'api_client1'])
+                // add this normalizer to several named serializers
+                ->tag('serializer.normalizer', ['serializer' => ['api_client1', 'api_client2']])
+                // add this normalizer to all serializers, including the default one
+                ->tag('serializer.normalizer', ['serializer' => '*'])
+            ;
+        };
+
+When the ``serializer`` attribute is not set, the service is registered only with
+the default serializer.
+
+Each normalizer or encoder used in a named serializer is tagged with a
+``serializer.normalizer.<name>`` or ``serializer.encoder.<name>`` tag.
+You can inspect their priorities using the following command:
+
+.. code-block:: terminal
+
+    $ php bin/console debug:container --tag serializer.<normalizer|encoder>.<name>
+
+Additionally, you can exclude the default set of normalizers and encoders from a
+named serializer by setting the ``include_built_in_normalizers`` and
+``include_built_in_encoders`` options to ``false``:
+
+.. configuration-block::
+
+    .. code-block:: yaml
+
+        # config/packages/serializer.yaml
+        framework:
+            serializer:
+                named_serializers:
+                    api_client1:
+                        include_built_in_normalizers: false
+                        include_built_in_encoders: true
+
+    .. code-block:: xml
+
+        <!-- config/packages/serializer.xml -->
+        <?xml version="1.0" encoding="UTF-8" ?>
+        <container xmlns="http://symfony.com/schema/dic/services"
+            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+            xmlns:framework="http://symfony.com/schema/dic/symfony"
+            xsi:schemaLocation="http://symfony.com/schema/dic/services
+                https://symfony.com/schema/dic/services/services-1.0.xsd
+                http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
+
+            <framework:config>
+                <framework:serializer>
+
+                    <framework:named-serializer
+                        name="api_client1"
+                        include-built-in-normalizers="false"
+                        include-built-in-encoders="true"
+                    />
+
+                </framework:serializer>
+            </framework:config>
+        </container>
+
+    .. code-block:: php
+
+        // config/packages/serializer.php
+        use Symfony\Config\FrameworkConfig;
+
+        return static function (FrameworkConfig $framework): void {
+            $framework->serializer()
+                ->namedSerializer('api_client1')
+                    ->includeBuiltInNormalizers(false)
+                    ->includeBuiltInEncoders(true)
+            ;
+        };
+
 Debugging the Serializer
 ------------------------
 
@@ -1534,6 +1906,14 @@ to ``true``::
     ]);
     // $jsonContent contains {"name":"Jane Doe"}
 
+Preserving Empty Objects
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+By default, the Serializer transforms an empty array to ``[]``. You can change
+this behavior by setting the ``AbstractObjectNormalizer::PRESERVE_EMPTY_OBJECTS``
+context option to ``true``. When the value is an instance of ``\ArrayObject()``,
+the serialized data will be ``{}``.
+
 Handling Uninitialized Properties
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -2033,6 +2413,70 @@ correct class for properties typed as ``InvoiceItemInterface``::
         $invoiceLine = $serializer->deserialize($jsonString, InvoiceLine::class, 'json');
         // $invoiceLine contains new InvoiceLine(new Product(...))
 
+You can add a default type to avoid the need to add the type property
+when deserializing:
+
+.. configuration-block::
+
+    .. code-block:: php-attributes
+
+        namespace App\Model;
+
+        use Symfony\Component\Serializer\Attribute\DiscriminatorMap;
+
+        #[DiscriminatorMap(
+            typeProperty: 'type',
+            mapping: [
+                'product' => Product::class,
+                'shipping' => Shipping::class,
+            ],
+            defaultType: 'product',
+        )]
+        interface InvoiceItemInterface
+        {
+            // ...
+        }
+
+    .. code-block:: yaml
+
+        App\Model\InvoiceItemInterface:
+            discriminator_map:
+                type_property: type
+                mapping:
+                    product: 'App\Model\Product'
+                    shipping: 'App\Model\Shipping'
+                default_type: product
+
+    .. code-block:: xml
+
+        <?xml version="1.0" encoding="UTF-8" ?>
+        <serializer xmlns="http://symfony.com/schema/dic/serializer-mapping"
+            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+            xsi:schemaLocation="http://symfony.com/schema/dic/serializer-mapping
+                https://symfony.com/schema/dic/serializer-mapping/serializer-mapping-1.0.xsd"
+        >
+            <class name="App\Model\InvoiceItemInterface">
+                <discriminator-map type-property="type" default-type="product">
+                    <mapping type="product" class="App\Model\Product"/>
+                    <mapping type="shipping" class="App\Model\Shipping"/>
+                </discriminator-map>
+            </class>
+        </serializer>
+
+Now it deserializes like this:
+
+.. configuration-block::
+
+    .. code-block:: php
+
+        // $jsonString does NOT contain "type" in "invoiceItem"
+        $invoiceLine = $serializer->deserialize('{"invoiceItem":{...},...}', InvoiceLine::class, 'json');
+        // $invoiceLine contains new InvoiceLine(new Product(...))
+
+.. versionadded:: 7.3
+
+    The ``defaultType`` parameter was added in Symfony 7.3.
+
 .. _serializer-unwrapping-denormalizer:
 
 Deserializing Input Partially (Unwrapping)
diff --git a/serializer/custom_context_builders.rst b/serializer/custom_context_builders.rst
index 8eeb584d761..e25b6d77813 100644
--- a/serializer/custom_context_builders.rst
+++ b/serializer/custom_context_builders.rst
@@ -27,7 +27,7 @@ value is ``0000-00-00``. To do that you'll first have to create your normalizer:
     {
         use DenormalizerAwareTrait;
 
-        public function denormalize($data, string $type, ?string $format = null, array $context = []): mixed
+        public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed
         {
             if ('0000-00-00' === $data) {
                 return null;
@@ -38,7 +38,7 @@ value is ``0000-00-00``. To do that you'll first have to create your normalizer:
             return $this->denormalizer->denormalize($data, $type, $format, $context);
         }
 
-        public function supportsDenormalization($data, string $type, ?string $format = null, array $context = []): bool
+        public function supportsDenormalization(mixed $data, string $type, ?string $format = null, array $context = []): bool
         {
             return true === ($context['zero_datetime_to_null'] ?? false)
                 && is_a($type, \DateTimeInterface::class, true);
diff --git a/serializer/custom_normalizer.rst b/serializer/custom_normalizer.rst
index 7435474c05c..4e78d9d394e 100644
--- a/serializer/custom_normalizer.rst
+++ b/serializer/custom_normalizer.rst
@@ -36,16 +36,16 @@ normalization process::
         ) {
         }
 
-        public function normalize($topic, ?string $format = null, array $context = []): array
+        public function normalize(mixed $data, ?string $format = null, array $context = []): array
         {
-            $data = $this->normalizer->normalize($topic, $format, $context);
+            $normalizedData = $this->normalizer->normalize($data, $format, $context);
 
             // Here, add, edit, or delete some data:
-            $data['href']['self'] = $this->router->generate('topic_show', [
-                'id' => $topic->getId(),
+            $normalizedData['href']['self'] = $this->router->generate('topic_show', [
+                'id' => $data->getId(),
             ], UrlGeneratorInterface::ABSOLUTE_URL);
 
-            return $data;
+            return $normalizedData;
         }
 
         public function supportsNormalization($data, ?string $format = null, array $context = []): bool
@@ -126,41 +126,32 @@ If you're not using ``autoconfigure``, you have to tag the service with
             ;
         };
 
-Performance of Normalizers/Denormalizers
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Improving Performance of Normalizers/Denormalizers
+--------------------------------------------------
 
-To figure which normalizer (or denormalizer) must be used to handle an object,
-the :class:`Symfony\\Component\\Serializer\\Serializer` class will call the
-:method:`Symfony\\Component\\Serializer\\Normalizer\\NormalizerInterface::supportsNormalization`
-(or :method:`Symfony\\Component\\Serializer\\Normalizer\\DenormalizerInterface::supportsDenormalization`)
-of all registered normalizers (or denormalizers) in a loop.
+Both :class:Symfony\\Component\\Serializer\\Normalizer\\NormalizerInterface
+and :class:Symfony\\Component\\Serializer\\Normalizer\\DenormalizerInterface
+define a ``getSupportedTypes()`` method to declare which types they support and
+whether their ``supports*()`` result can be cached.
 
-Additionally, both
-:class:`Symfony\\Component\\Serializer\\Normalizer\\NormalizerInterface`
-and :class:`Symfony\\Component\\Serializer\\Normalizer\\DenormalizerInterface`
-contain the ``getSupportedTypes()`` method. This method allows normalizers or
-denormalizers to declare the type of objects they can handle, and whether they
-are cacheable. With this info, even if the ``supports*()`` call is not cacheable,
-the Serializer can skip a ton of method calls to ``supports*()`` improving
-performance substantially in some cases.
+This **does not** cache the actual normalization or denormalization result. It
+only **caches the decision** of whether a normalizer supports a given type, allowing
+the Serializer to skip unnecessary ``supports*()`` calls and improve performance.
 
 The ``getSupportedTypes()`` method should return an array where the keys
-represent the supported types, and the values indicate whether the result of
-the ``supports*()`` method call can be cached or not. The format of the
-returned array is as follows:
+represent the supported types, and the values indicate whether the result of the
+corresponding ``supports*()`` call can be cached. The array format is as follows:
 
 #. The special key ``object`` can be used to indicate that the normalizer or
    denormalizer supports any classes or interfaces.
 #. The special key ``*`` can be used to indicate that the normalizer or
-   denormalizer might support any types.
-#. The other keys in the array should correspond to specific types that the
-   normalizer or denormalizer supports.
-#. The values associated with each type should be a boolean indicating if the
-   result of the ``supports*()`` method call for that type can be cached or not.
-   A value of ``true`` means that the result is cacheable, while ``false`` means
-   that the result is not cacheable.
-#. A ``null`` value for a type means that the normalizer or denormalizer does
-   not support that type.
+   denormalizer might support any type.
+#. Other keys should correspond to specific types that the normalizer or
+   denormalizer supports.
+#. The values should be booleans indicating whether the result of the
+   ``supports*()`` call for that type is cacheable. Use ``true`` if the result
+   can be cached, ``false`` if it cannot.
+#. A ``null`` value means the normalizer or denormalizer does not support that type.
 
 Here is an example of how to use the ``getSupportedTypes()`` method::
 
@@ -173,9 +164,9 @@ Here is an example of how to use the ``getSupportedTypes()`` method::
         public function getSupportedTypes(?string $format): array
         {
             return [
-                'object' => null,             // Doesn't support any classes or interfaces
-                '*' => false,                 // Supports any other types, but the result is not cacheable
-                MyCustomClass::class => true, // Supports MyCustomClass and result is cacheable
+                'object' => null,             // doesn't support any classes or interfaces
+                '*' => false,                 // supports any other types, but the decision is not cacheable
+                MyCustomClass::class => true, // supports MyCustomClass and decision is cacheable
             ];
         }
     }
diff --git a/serializer/encoders.rst b/serializer/encoders.rst
index 3cf1ff912a4..8238d4d057d 100644
--- a/serializer/encoders.rst
+++ b/serializer/encoders.rst
@@ -65,6 +65,11 @@ are available to customize the behavior of the encoder:
 ``csv_end_of_line`` (default: ``\n``)
     Sets the character(s) used to mark the end of each line in the CSV file.
 ``csv_escape_char`` (default: empty string)
+
+    .. deprecated:: 7.2
+
+        The ``csv_escape_char`` option was deprecated in Symfony 7.2.
+
     Sets the escape character (at most one character).
 ``csv_key_separator`` (default: ``.``)
     Sets the separator for array's keys during its flattening
@@ -197,14 +202,19 @@ These are the options available on the :ref:`serializer context <serializer-cont
     If set to ``false``, will not wrap any value containing one of the
     following characters ( ``<``, ``>``, ``&``) in `a CDATA section`_ like
     following: ``<![CDATA[...]]>``.
-``cdata_wrapping_pattern`` (default: ````/[<>&]/````)
+``cdata_wrapping_pattern`` (default: ``/[<>&]/``)
     A regular expression pattern to determine if a value should be wrapped
     in a CDATA section.
+``ignore_empty_attributes`` (default: ``false``)
+    If set to true, ignores all attributes with empty values in the generated XML
 
 .. versionadded:: 7.1
 
     The ``cdata_wrapping_pattern`` option was introduced in Symfony 7.1.
 
+.. versionadded:: 7.3
+
+    The ``ignore_empty_attributes`` option was introduced in Symfony 7.3.
 
 Example with a custom ``context``::
 
@@ -261,7 +271,7 @@ Creating a Custom Encoder
 Imagine you want to serialize and deserialize `NEON`_. For that you'll have to
 create your own encoder::
 
-    // src/Serializer/YamlEncoder.php
+    // src/Serializer/NeonEncoder.php
     namespace App\Serializer;
 
     use Nette\Neon\Neon;
diff --git a/serializer/streaming_json.rst b/serializer/streaming_json.rst
new file mode 100644
index 00000000000..3fd44824bc6
--- /dev/null
+++ b/serializer/streaming_json.rst
@@ -0,0 +1,664 @@
+Streaming JSON
+==============
+
+.. versionadded:: 7.3
+
+    The JsonStreamer component was introduced in Symfony 7.3 as an
+    :doc:`experimental feature </contributing/code/experimental>`.
+
+Symfony can encode PHP data structures to JSON streams and decode JSON streams
+back into PHP data structures.
+
+To do so, it relies on the **JsonStreamer** component, which is designed for
+high efficiency and can process large JSON data incrementally without needing
+to load the entire content into memory.
+
+This component is ideal for handling APIs or interacting with third-party APIs.
+It transforms incoming JSON request payloads into PHP objects that your
+application can work with. Similarly, it converts processed PHP objects into a
+JSON stream for outgoing responses.
+
+Installation
+------------
+
+In applications using :ref:`Symfony Flex <symfony-flex>`, run this command to
+install the JsonStreamer component:
+
+.. code-block:: terminal
+
+    $ composer require symfony/json-streamer
+
+.. include:: /components/require_autoload.rst.inc
+
+Encoding Objects
+----------------
+
+JsonStreamer only works with PHP classes that have **no constructor** and are
+composed solely of **public properties**, like `DTO classes`_. Consider the
+following ``Cat`` class::
+
+    // src/Dto/Cat.php
+    namespace App\Dto;
+
+    class Cat
+    {
+        public string $name;
+        public string $age;
+    }
+
+To encode ``Cat`` objects into a JSON stream (e.g., to send them in an API
+response), first apply the ``#[JsonStreamable]`` attribute to the class. This
+attribute is optional, but it :ref:`improves performance <json-streamer-streamable-attribute>`
+by pre-generating encoding and decoding files during cache warm-up::
+
+    namespace App\Dto;
+
+    use Symfony\Component\JsonStreamer\Attribute\JsonStreamable;
+
+    #[JsonStreamable]
+    class Cat
+    {
+        // ...
+    }
+
+Next, inject the JSON stream writer into your service. The service ``id`` is
+``json_streamer.stream_writer``, but you can also get it by type-hinting a
+``$jsonStreamWriter`` argument with :class:`Symfony\\Component\\JsonStreamer\\StreamWriterInterface`.
+
+Use the :method:`Symfony\\Component\\JsonStreamer\\StreamWriterInterface::write`
+method of the service to perform the actual JSON conversion:
+
+.. configuration-block::
+
+    .. code-block:: php-symfony
+
+        // src/Controller/CatController.php
+        namespace App\Controller;
+
+        use App\Dto\Cat;
+        use App\Repository\CatRepository;
+        use Symfony\Component\HttpFoundation\StreamedResponse;
+        use Symfony\Component\JsonStreamer\StreamWriterInterface;
+        use Symfony\Component\TypeInfo\Type;
+
+        class CatController
+        {
+            public function retrieveCats(StreamWriterInterface $jsonStreamWriter, CatRepository $catRepository): StreamedResponse
+            {
+                $cats = $catRepository->findAll();
+                $type = Type::list(Type::object(Cat::class));
+
+                $json = $jsonStreamWriter->write($cats, $type);
+
+                return new StreamedResponse($json);
+            }
+        }
+
+    .. code-block:: php-standalone
+
+        use App\Dto\Cat;
+        use App\Repository\CatRepository;
+        use Symfony\Component\HttpFoundation\StreamedResponse;
+        use Symfony\Component\JsonStreamer\JsonStreamWriter;
+        use Symfony\Component\TypeInfo\Type;
+
+        // ...
+
+        $jsonWriter = JsonStreamWriter::create();
+
+        $cats = $catRepository->findAll();
+        $type = Type::list(Type::object(Cat::class));
+
+        $json = $jsonWriter->write($cats, $type);
+
+        $response = new StreamedResponse($json);
+
+        // ...
+
+.. tip::
+
+    You can explicitly inject the ``json_streamer.stream_writer`` service by
+    using the ``#[Target('json_streamer.stream_writer')]`` autowire attribute.
+
+Decoding Objects
+----------------
+
+In addition to encoding, you can decode JSON into PHP objects.
+
+To do this, inject the JSON stream reader into your service. The service ``id`` is
+``json_streamer.stream_reader``, but you can also get it by type-hinting a
+``$jsonStreamReader`` argument with :class:`Symfony\\Component\\JsonStreamer\\StreamReaderInterface`.
+Next, use the :method:`Symfony\\Component\\JsonStreamer\\StreamReaderInterface::read`
+method to perform the actual JSON parsing:
+
+.. configuration-block::
+
+    .. code-block:: php-symfony
+
+        // src/Service/TombolaService.php
+        namespace App\Service;
+
+        use App\Dto\Cat;
+        use Symfony\Component\DependencyInjection\Attribute\Autowire;
+        use Symfony\Component\JsonStreamer\StreamReaderInterface;
+        use Symfony\Component\TypeInfo\Type;
+
+        class TombolaService
+        {
+            private string $catsJsonFile;
+
+            public function __construct(
+                private StreamReaderInterface $jsonStreamReader,
+                #[Autowire(param: 'kernel.root_dir')]
+                string $rootDir,
+            ) {
+                $this->catsJsonFile = sprintf('%s/var/cats.json', $rootDir);
+            }
+
+            public function pickTheTenthCat(): ?Cat
+            {
+                $jsonResource = fopen($this->catsJsonFile, 'r');
+                $type = Type::iterable(Type::object(Cat::class));
+
+                /** @var iterable<Cat> $cats */
+                $cats = $this->jsonStreamReader->read($jsonResource, $type);
+
+                $i = 0;
+                foreach ($cats as $cat) {
+                    if ($i === 9) {
+                        return $cat;
+                    }
+
+                    ++$i;
+                }
+
+                return null;
+            }
+
+            /**
+             * @return list<string>
+             */
+            public function listEligibleCatNames(): array
+            {
+                $json = file_get_contents($this->catsJsonFile);
+                $type = Type::iterable(Type::object(Cat::class));
+
+                /** @var iterable<Cat> $cats */
+                $cats = $this->jsonStreamReader->read($json, $type);
+
+                return array_map(fn(Cat $cat) => $cat->name, iterator_to_array($cats));
+            }
+        }
+
+    .. code-block:: php-standalone
+
+        // src/Service/TombolaService.php
+        namespace App\Service;
+
+        use App\Dto\Cat;
+        use Symfony\Component\JsonStreamer\JsonStreamReader;
+        use Symfony\Component\JsonStreamer\StreamReaderInterface;
+        use Symfony\Component\TypeInfo\Type;
+
+        class TombolaService
+        {
+            private StreamReaderInterface $jsonStreamReader;
+            private string $catsJsonFile;
+
+            public function __construct(
+                private string $catsJsonFile,
+            ) {
+                $this->jsonStreamReader = JsonStreamReader::create();
+            }
+
+            public function pickTheTenthCat(): ?Cat
+            {
+                $jsonResource = fopen($this->catsJsonFile, 'r');
+                $type = Type::iterable(Type::object(Cat::class));
+
+                /** @var iterable<Cat> $cats */
+                $cats = $this->jsonStreamReader->read($jsonResource, $type);
+
+                $i = 0;
+                foreach ($cats as $cat) {
+                    if ($i === 9) {
+                        return $cat;
+                    }
+
+                    ++$i;
+                }
+
+                return null;
+            }
+
+            /**
+             * @return list<string>
+             */
+            public function listEligibleCatNames(): array
+            {
+                $json = file_get_contents($this->catsJsonFile);
+                $type = Type::iterable(Type::object(Cat::class));
+
+                /** @var iterable<Cat> $cats */
+                $cats = $this->jsonStreamReader->read($json, $type);
+
+                return array_map(fn(Cat $cat) => $cat->name, iterator_to_array($cats));
+            }
+        }
+
+.. tip::
+
+    You can explicitly inject the ``json_streamer.stream_reader`` service by
+    using the ``#[Target('json_streamer.stream_reader')]`` autowire attribute.
+
+The examples above demonstrate two different approaches to decoding JSON data
+using JsonStreamer:
+
+* decoding from a stream (``pickTheTenthCat``)
+* decoding from a string (``listEligibleCatNames``)
+
+Both methods handle the same JSON data but differ in memory usage and performance.
+Use streams if optimizing memory usage is more important. Use strings if
+performance is more important.
+
+Decoding from a Stream
+~~~~~~~~~~~~~~~~~~~~~~
+
+In the ``pickTheTenthCat`` method, the JSON data is read as a stream using
+:phpfunction:`fopen`. This is useful for large files, as the data is processed
+incrementally rather than being fully loaded into memory.
+
+To optimize memory usage, JsonStreamer creates `ghost objects`_ instead of
+fully instantiating them. These lightweight placeholders delay object creation
+until the data is actually needed.
+
+* Advantage: Efficient memory usage, ideal for very large JSON files.
+* Disadvantage: Slightly slower due to lazy loading.
+
+Decoding from a String
+~~~~~~~~~~~~~~~~~~~~~~
+
+In the ``listEligibleCatNames`` method, the entire JSON file is read into a
+string using :phpfunction:`file_get_contents`. The decoder then instantiates
+all the objects immediately.
+
+This approach is faster because all objects are created immediately, but it
+requires more memory.
+
+* Advantage: Faster, ideal for small to medium JSON files.
+* Disadvantage: Higher memory usage, unsuitable for large files.
+
+Enabling PHPDoc Reading
+-----------------------
+
+The JsonStreamer component can read advanced PHPDoc type definitions (e.g.,
+generics) and process complex PHP objects accordingly.
+
+Consider the ``Shelter`` class that defines a generic ``TAnimal`` type, which
+can be a ``Cat`` or a ``Dog``::
+
+    // src/Dto/Shelter.php
+    namespace App\Dto;
+
+    use Symfony\Component\JsonStreamer\Attribute\JsonStreamable;
+
+    /**
+     * @template TAnimal of Cat|Dog
+     */
+    #[JsonStreamable]
+    class Shelter
+    {
+        /**
+         * @var list<TAnimal>
+         */
+        public array $animals;
+    }
+
+To enable PHPDoc parsing, run:
+
+.. code-block:: terminal
+
+    $ composer require phpstan/phpdoc-parser
+
+Then, when encoding/decoding a ``Shelter`` instance, you can specify the
+concrete type information, and JsonStreamer will correctly interpret the JSON
+structure::
+
+    use App\Dto\Cat;
+    use App\Dto\Shelter;
+    use Symfony\Component\TypeInfo\Type;
+
+    $json = <<<JSON
+    {
+      "animals": [
+        {"name": "Eva", "age": 29},
+        {...}
+      ]
+    }
+    JSON;
+
+    // maps the TAnimal template in Shelter to the Cat concrete type
+    $type = Type::generic(Type::object(Shelter::class), Type::object(Cat::class));
+
+    $catShelter = $jsonStreamReader->read($json, $type); // will be populated with Cat instances
+
+Configuring Encoding/Decoding
+-----------------------------
+
+While it's usually best not to alter the shape or values of objects during
+serialization, sometimes it's necessary.
+
+Configuring the Encoded Name
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You can configure the JSON key for a property using the
+:class:`Symfony\\Component\\JsonStreamer\\Attribute\\StreamedName` attribute::
+
+    // src/Dto/Duck.php
+    namespace App\Dto;
+
+    use Symfony\Component\JsonStreamer\Attribute\JsonStreamable;
+    use Symfony\Component\JsonStreamer\Attribute\StreamedName;
+
+    #[JsonStreamable]
+    class Duck
+    {
+        #[StreamedName('@id')]
+        public string $id;
+    }
+
+This maps the ``Duck::$id`` property to the ``@id`` JSON key::
+
+    use App\Dto\Duck;
+    use Symfony\Component\TypeInfo\Type;
+
+    // ...
+
+    $duck = new Duck();
+    $duck->id = '/ducks/daffy';
+
+    echo (string) $jsonStreamWriter->write($duck, Type::object(Duck::class));
+
+    // This will output:
+    // {
+    //   "@id": "/ducks/daffy"
+    // }
+
+Configuring the Encoded Value
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+To transform a property's value during encoding, use the
+:class:`Symfony\\Component\\JsonStreamer\\Attribute\\ValueTransformer`
+attribute. Its ``nativeToStream`` option accepts a callable or a
+:ref:`value transformer service id <json-streamer-transform-with-services>`.
+
+The callable must be a public static method or non-anonymous function with this
+signature::
+
+    $transformer = function (mixed $data, array $options = []): mixed { /* ... */ };
+
+Then specify it in the attribute::
+
+    // src/Dto/Duck.php
+    namespace App\Dto;
+
+    use Symfony\Component\JsonStreamer\Attribute\JsonStreamable;
+    use Symfony\Component\JsonStreamer\Attribute\ValueTransformer;
+
+    #[JsonStreamable]
+    class Duck
+    {
+        #[ValueTransformer(nativeToStream: 'strtoupper')]
+        public string $name;
+
+        #[ValueTransformer(nativeToStream: [self::class, 'formatHeight'])]
+        public int $height;
+
+        public static function formatHeight(int $value, array $options = []): string
+        {
+            return sprintf('%.2fcm', $value / 100);
+        }
+    }
+
+The following example transforms the ``name`` and ``height`` properties during
+encoding::
+
+    use App\Dto\Duck;
+    use Symfony\Component\TypeInfo\Type;
+
+    // ...
+
+    $duck = new Duck();
+    $duck->name = 'daffy';
+    $duck->height = 5083;
+
+    echo (string) $jsonStreamWriter->write($duck, Type::object(Duck::class));
+
+    // This will output:
+    // {
+    //   "name": "DAFFY",
+    //   "height": "50.83cm"
+    // }
+
+Configuring the Decoded Value
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+To transform a property's value during decoding, use the
+:class:`Symfony\\Component\\JsonStreamer\\Attribute\\ValueTransformer`
+attribute. Its ``streamToNative`` option accepts a callable or a
+:ref:`value transformer service id <json-streamer-transform-with-services>`.
+
+The callable must be a public static method or non-anonymous function with this
+signature::
+
+    $valueTransformer = function (mixed $data, array $options = []): mixed { /* ... */ };
+
+Then specify it in the attribute::
+
+    // src/Dto/Duck.php
+    namespace App\Dto;
+
+    use Symfony\Component\JsonStreamer\Attribute\JsonStreamable;
+    use Symfony\Component\JsonStreamer\Attribute\ValueTransformer;
+
+    #[JsonStreamable]
+    class Duck
+    {
+        #[ValueTransformer(streamToNative: [self::class, 'retrieveFirstName'])]
+        public string $firstName;
+
+        #[ValueTransformer(streamToNative: [self::class, 'retrieveLastName'])]
+        public string $lastName;
+
+        public static function retrieveFirstName(string $normalized, array $options = []): string
+        {
+            return explode(' ', $normalized)[0];
+        }
+
+        public static function retrieveLastName(string $normalized, array $options = []): string
+        {
+            return explode(' ', $normalized)[1];
+        }
+    }
+
+This will extract first and last names from a full name in the input JSON::
+
+    use App\Dto\Duck;
+    use Symfony\Component\TypeInfo\Type;
+
+    // ...
+
+    $duck = $jsonStreamReader->read(
+        '{"name": "Daffy Duck"}',
+        Type::object(Duck::class),
+    );
+
+    // The $duck variable will contain:
+    // object(Duck)#1 (1) {
+    //   ["firstName"] => string(5) "Daffy"
+    //   ["lastName"] => string(4) "Duck"
+    // }
+
+.. _json-streamer-transform-with-services:
+
+Transforming Value Using Services
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When callables are not enough, you can use a service implementing the
+:class:`Symfony\\Component\\JsonStreamer\\ValueTransformer\\ValueTransformerInterface`::
+
+    // src/Transformer/DogUrlTransformer.php
+    namespace App\Transformer;
+
+    use Symfony\Component\JsonStreamer\ValueTransformer\ValueTransformerInterface;
+    use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
+    use Symfony\Component\TypeInfo\Type;
+
+    class DogUrlTransformer implements ValueTransformerInterface
+    {
+        public function __construct(
+            private UrlGeneratorInterface $urlGenerator,
+        ) {
+        }
+
+        public function transform(mixed $value, array $options = []): string
+        {
+            if (!is_int($value)) {
+                throw new \InvalidArgumentException(sprintf('The value must be "int", "%s" given.', get_debug_type($value)));
+            }
+
+            return $this->urlGenerator->generate('show_dog', ['id' => $value]);
+        }
+
+        public static function getStreamValueType(): Type
+        {
+            return Type::string();
+        }
+    }
+
+.. note::
+
+    The ``getStreamValueType()`` method must return the value's type as it will
+    appear in the JSON stream.
+
+To use this transformer in a class, configure the ``#[ValueTransformer]`` attribute::
+
+    // src/Dto/Dog.php
+    namespace App\Dto;
+
+    use App\Transformer\DogUrlTransformer;
+    use Symfony\Component\JsonStreamer\Attribute\JsonStreamable;
+    use Symfony\Component\JsonStreamer\Attribute\StreamedName;
+    use Symfony\Component\JsonStreamer\Attribute\ValueTransformer;
+
+    #[JsonStreamable]
+    class Dog
+    {
+        #[StreamedName('url')]
+        #[ValueTransformer(nativeToStream: DogUrlTransformer::class)]
+        public int $id;
+    }
+
+.. tip::
+
+    Value transformers are called frequently during encoding and decoding. Keep
+    them lightweight and avoid calls to external APIs or the database.
+
+Configuring Keys and Values Dynamically
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+JsonStreamer uses services that implement the
+:class:`Symfony\\Component\\JsonStreamer\\Mapping\\PropertyMetadataLoaderInterface`
+to control the shape and values of objects during encoding/decoding.
+
+These services are highly flexible and can be decorated to support dynamic
+configurations, providing more flexibility than attributes::
+
+    namespace App\Streamer\SensitivePropertyMetadataLoader;
+
+    use App\Dto\SensitiveInterface;
+    use App\Streamer\ValueTransformer\EncryptorValueTransformer;
+    use Symfony\Component\DependencyInjection\Attribute\AsDecorator;
+    use Symfony\Component\DependencyInjection\Attribute\AutowireDecorated;
+    use Symfony\Component\JsonStreamer\Mapping\PropertyMetadata;
+    use Symfony\Component\JsonStreamer\Mapping\PropertyMetadataLoaderInterface;
+    use Symfony\Component\TypeInfo\Type;
+
+    #[AsDecorator('json_streamer.write.property_metadata_loader')]
+    class SensitivePropertyMetadataLoader implements PropertyMetadataLoaderInterface
+    {
+        public function __construct(
+            #[AutowireDecorated]
+            private PropertyMetadataLoaderInterface $decorated,
+        ) {
+        }
+
+        public function load(string $className, array $options = [], array $context = []): array
+        {
+            $propertyMetadataMap = $this->decorated->load($className, $options, $context);
+
+            if (!is_a($className, SensitiveInterface::class, true)) {
+                return $propertyMetadataMap;
+            }
+
+            // you can configure value transformers
+            foreach ($propertyMetadataMap as $jsonKey => $metadata) {
+                if (in_array($metadata->getName(), $className::getPropertiesToEncrypt(), true)) {
+                    $propertyMetadataMap[$jsonKey] = $metadata
+                        ->withType(Type::string())
+                        ->withAdditionalNativeToStreamValueTransformer(EncryptorValueTransformer::class);
+                }
+            }
+
+            // you can remove existing properties
+            foreach ($propertyMetadataMap as $jsonKey => $metadata) {
+                if (in_array($metadata->getName(), $className::getPropertiesToRemove(), true)) {
+                    unset($propertyMetadataMap[$jsonKey]);
+                }
+            }
+
+            // you can rename JSON keys
+            foreach ($propertyMetadataMap as $jsonKey => $metadata) {
+                $propertyMetadataMap[md5($jsonKey)] = $propertyMetadataMap[$jsonKey];
+                unset($propertyMetadataMap[$jsonKey]);
+            }
+
+            // you can add virtual properties
+            $propertyMetadataMap['is_sensitive'] = new PropertyMetadata(
+                name: 'theNameWontBeUsed',
+                type: Type::bool(),
+                nativeToStreamValueTransformers: [fn() => true],
+            );
+
+            return $propertyMetadataMap;
+        }
+    }
+
+Although powerful, this approach introduces complexity. Decorating property
+metadata loaders requires a deep understanding of the internals.
+
+For most use cases, attribute-based configuration is sufficient. Reserve
+dynamic loaders for advanced scenarios.
+
+.. _json-streamer-streamable-attribute:
+
+Marking Objects as Streamable
+-----------------------------
+
+The ``JsonStreamable`` attribute marks a class as streamable. While not strictly
+required, it's highly recommended because it enables cache warm-up to pre-generate
+encoding/decoding files, improving performance.
+
+It includes two optional properties: ``asObject`` and ``asList``, which define
+how the class should be prepared during cache warm-up::
+
+    use Symfony\Component\JsonStreamer\Attribute\JsonStreamable;
+
+    #[JsonStreamable(asObject: true, asList: true)]
+    class StreamableData
+    {
+        // ...
+    }
+
+.. _`DTO classes`: https://en.wikipedia.org/wiki/Data_transfer_object
+.. _ghost objects: https://en.wikipedia.org/wiki/Lazy_loading#Ghost
diff --git a/service_container.rst b/service_container.rst
index eb2e7fb60ae..8b86d06a833 100644
--- a/service_container.rst
+++ b/service_container.rst
@@ -162,10 +162,6 @@ each time you ask for it.
                 # this creates a service per class whose id is the fully-qualified class name
                 App\:
                     resource: '../src/'
-                    exclude:
-                        - '../src/DependencyInjection/'
-                        - '../src/Entity/'
-                        - '../src/Kernel.php'
 
                 # order is important in this file because service definitions
                 # always *replace* previous ones; add your own service configuration below
@@ -187,7 +183,7 @@ each time you ask for it.
 
                     <!-- makes classes in src/ available to be used as services -->
                     <!-- this creates a service per class whose id is the fully-qualified class name -->
-                    <prototype namespace="App\" resource="../src/" exclude="../src/{DependencyInjection,Entity,Kernel.php}"/>
+                    <prototype namespace="App\" resource="../src/"/>
 
                     <!-- order is important in this file because service definitions
                          always *replace* previous ones; add your own service configuration below -->
@@ -212,8 +208,7 @@ each time you ask for it.
 
                 // makes classes in src/ available to be used as services
                 // this creates a service per class whose id is the fully-qualified class name
-                $services->load('App\\', '../src/')
-                    ->exclude('../src/{DependencyInjection,Entity,Kernel.php}');
+                $services->load('App\\', '../src/');
 
                 // order is important in this file because service definitions
                 // always *replace* previous ones; add your own service configuration below
@@ -221,15 +216,57 @@ each time you ask for it.
 
     .. tip::
 
-        The value of the ``resource`` and ``exclude`` options can be any valid
-        `glob pattern`_. The value of the ``exclude`` option can also be an
-        array of glob patterns.
+        The value of the ``resource`` option can be any valid `glob pattern`_.
 
     Thanks to this configuration, you can automatically use any classes from the
     ``src/`` directory as a service, without needing to manually configure
     it. Later, you'll learn how to :ref:`import many services at once
     <service-psr4-loader>` with resource.
 
+    If some files or directories in your project should not become services, you
+    can exclude them using the ``exclude`` option:
+
+    .. configuration-block::
+
+        .. code-block:: yaml
+
+            # config/services.yaml
+            services:
+                # ...
+                App\:
+                    resource: '../src/'
+                    exclude:
+                        - '../src/SomeDirectory/'
+                        - '../src/AnotherDirectory/'
+                        - '../src/SomeFile.php'
+
+        .. code-block:: xml
+
+            <!-- config/services.xml -->
+            <?xml version="1.0" encoding="UTF-8" ?>
+            <container xmlns="http://symfony.com/schema/dic/services"
+                xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                xsi:schemaLocation="http://symfony.com/schema/dic/services
+                    https://symfony.com/schema/dic/services/services-1.0.xsd">
+
+                <services>
+                    <prototype namespace="App\" resource="../src/" exclude="../src/{SomeDirectory,AnotherDirectory,Kernel.php}"/>
+                    <!-- ... -->
+                </services>
+            </container>
+
+        .. code-block:: php
+
+            // config/services.php
+            namespace Symfony\Component\DependencyInjection\Loader\Configurator;
+
+            return function(ContainerConfigurator $container): void {
+                // ...
+
+                $services->load('App\\', '../src/')
+                    ->exclude('../src/{SomeDirectory,AnotherDirectory,Kernel.php}');
+            };
+
     If you'd prefer to manually wire your service, you can
     :ref:`use explicit configuration <services-explicitly-configure-wire-services>`.
 
@@ -260,6 +297,32 @@ as a service in some environments::
         // ...
     }
 
+If you want to exclude a service from being registered in a specific
+environment, you can use the ``#[WhenNot]`` attribute::
+
+    use Symfony\Component\DependencyInjection\Attribute\WhenNot;
+
+    // SomeClass is registered in all environments except "dev"
+
+    #[WhenNot(env: 'dev')]
+    class SomeClass
+    {
+        // ...
+    }
+
+    // you can apply more than one WhenNot attribute to the same class
+
+    #[WhenNot(env: 'dev')]
+    #[WhenNot(env: 'test')]
+    class AnotherClass
+    {
+        // ...
+    }
+
+.. versionadded:: 7.2
+
+    The ``#[WhenNot]`` attribute was introduced in Symfony 7.2.
+
 .. _services-constructor-injection:
 
 Injecting Services/Config into a Service
@@ -323,6 +386,127 @@ type-hints by running:
 
       [...]
 
+In addition to injecting services, you can also pass scalar values and collections
+as arguments of other services:
+
+.. configuration-block::
+
+    .. code-block:: yaml
+
+        # config/services.yaml
+        services:
+            App\Service\SomeService:
+                arguments:
+                    # string, numeric and boolean arguments can be passed "as is"
+                    - 'Foo'
+                    - true
+                    - 7
+                    - 3.14
+
+                    # constants can be built-in, user-defined, or Enums
+                    - !php/const E_ALL
+                    - !php/const PDO::FETCH_NUM
+                    - !php/const Symfony\Component\HttpKernel\Kernel::VERSION
+                    - !php/const App\Config\SomeEnum::SomeCase
+
+                    # when not using autowiring, you can pass service arguments explicitly
+                    - '@some-service-id'  # the leading '@' tells this is a service ID, not a string
+                    - '@?some-service-id' # using '?' means to pass null if service doesn't exist
+
+                    # binary contents are passed encoded as base64 strings
+                    - !!binary VGhpcyBpcyBhIEJlbGwgY2hhciAH
+
+                    # collections (arrays) can include any type of argument
+                    -
+                        first: !php/const true
+                        second: 'Foo'
+
+    .. code-block:: xml
+
+        <!-- config/services.xml -->
+        <?xml version="1.0" encoding="UTF-8" ?>
+        <container xmlns="http://symfony.com/schema/dic/services"
+            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+            xmlns:framework="http://symfony.com/schema/dic/symfony"
+            xsi:schemaLocation="http://symfony.com/schema/dic/services
+                https://symfony.com/schema/dic/services/services-1.0.xsd
+                http://symfony.com/schema/dic/symfony
+                https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
+
+            <services>
+                <service id="App\Service\SomeService">
+                    <!-- arguments without a type can be strings or numbers -->
+                    <argument>Foo</argument>
+                    <argument>7</argument>
+                    <argument>3.14</argument>
+                    <!-- explicitly declare a string argument -->
+                    <argument type="string">Foo</argument>
+                    <!-- booleans are passed as constants -->
+                    <argument type="constant">true</argument>
+
+                    <!-- constants can be built-in, user-defined, or Enums -->
+                    <argument type="constant">E_ALL</argument>
+                    <argument type="constant">PDO::FETCH_NUM</argument>
+                    <argument type="constant">Symfony\Component\HttpKernel\Kernel::VERSION</argument>
+                    <argument type="constant">App\Config\SomeEnum::SomeCase</argument>
+
+                    <!-- when not using autowiring, you can pass service arguments explicitly -->
+                    <argument type="service"
+                              id="some-service-id"
+                              on-invalid="dependency_injection-ignore"/>
+
+                    <!-- binary contents are passed encoded as base64 strings -->
+                    <argument type="binary">VGhpcyBpcyBhIEJlbGwgY2hhciAH</argument>
+
+                    <!-- collections (arrays) can include any type of argument -->
+                    <argument type="collection">
+                        <argument key="first" type="constant">true</argument>
+                        <argument key="second" type="string">Foo</argument>
+                    </argument>
+                </service>
+
+                <!-- ... -->
+            </services>
+        </container>
+
+    .. code-block:: php
+
+        // config/services.php
+        namespace Symfony\Component\DependencyInjection\Loader\Configurator;
+
+        use Symfony\Component\DependencyInjection\ContainerInterface;
+        use Symfony\Component\DependencyInjection\Reference;
+
+        return static function (ContainerConfigurator $container) {
+            $services = $container->services();
+
+            $services->set(App\Service\SomeService::class)
+                // string, numeric and boolean arguments can be passed "as is"
+                ->arg(0, 'Foo')
+                ->arg(1, true)
+                ->arg(2, 7)
+                ->arg(3, 3.14)
+
+                // constants: built-in, user-defined, or Enums
+                ->arg(4, E_ALL)
+                ->arg(5, \PDO::FETCH_NUM)
+                ->arg(6, Symfony\Component\HttpKernel\Kernel::VERSION)
+                ->arg(7, App\Config\SomeEnum::SomeCase)
+
+                // when not using autowiring, you can pass service arguments explicitly
+                ->arg(8, service('some-service-id')) # fails if service doesn't exist
+                # passes null if service doesn't exist
+                ->arg(9, new Reference('some-service-id', Reference::IGNORE_ON_INVALID_REFERENCE))
+
+                // collection with mixed argument types
+                ->arg(10, [
+                    'first' => true,
+                    'second' => 'Foo',
+                ]);
+
+            // ...
+        };
+
 Handling Multiple Services
 ~~~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -1042,6 +1226,14 @@ application to production (e.g. in your continuous integration server):
 
     $ php bin/console lint:container
 
+    # optionally, you can force the resolution of environment variables;
+    # the command will fail if any of those environment variables are missing
+    $ php bin/console lint:container --resolve-env-vars
+
+.. versionadded:: 7.2
+
+    The ``--resolve-env-vars`` option was introduced in Symfony 7.2.
+
 Performing those checks whenever the container is compiled can hurt performance.
 That's why they are implemented in :doc:`compiler passes </service_container/compiler_passes>`
 called ``CheckTypeDeclarationsPass`` and ``CheckAliasValidityPass``, which are
diff --git a/service_container/alias_private.rst b/service_container/alias_private.rst
index f99f7cb5f3e..22bf649d861 100644
--- a/service_container/alias_private.rst
+++ b/service_container/alias_private.rst
@@ -181,6 +181,32 @@ This means that when using the container directly, you can access the
             # ...
             app.mailer: '@App\Mail\PhpMailer'
 
+The ``#[AsAlias]`` attribute can also be limited to one or more specific
+:ref:`config environments <configuration-environments>` using the ``when`` argument::
+
+    // src/Mail/PhpMailer.php
+    namespace App\Mail;
+
+    // ...
+    use Symfony\Component\DependencyInjection\Attribute\AsAlias;
+
+    #[AsAlias(id: 'app.mailer', when: 'dev')]
+    class PhpMailer
+    {
+        // ...
+    }
+
+    // pass an array to apply it in multiple config environments
+    #[AsAlias(id: 'app.mailer', when: ['dev', 'test'])]
+    class PhpMailer
+    {
+        // ...
+    }
+
+.. versionadded:: 7.3
+
+    The ``when`` argument of the ``#[AsAlias]`` attribute was introduced in Symfony 7.3.
+
 .. tip::
 
     When using ``#[AsAlias]`` attribute, you may omit passing ``id`` argument
diff --git a/service_container/autowiring.rst b/service_container/autowiring.rst
index 48bb40985b8..ea1bf1b12ff 100644
--- a/service_container/autowiring.rst
+++ b/service_container/autowiring.rst
@@ -408,8 +408,8 @@ Suppose you create a second class - ``UppercaseTransformer`` that implements
 If you register this as a service, you now have *two* services that implement the
 ``App\Util\TransformerInterface`` type. Autowiring subsystem can not decide
 which one to use. Remember, autowiring isn't magic; it looks for a service
-whose id matches the type-hint. So you need to choose one by creating an alias
-from the type to the correct service id (see :ref:`autowiring-interface-alias`).
+whose id matches the type-hint. So you need to choose one by :ref:`creating an alias
+<autowiring-interface-alias>` from the type to the correct service id.
 Additionally, you can define several named autowiring aliases if you want to use
 one implementation in some cases, and another implementation in some
 other cases.
@@ -862,6 +862,40 @@ typed properties:
             }
         }
 
+Autowiring Anonymous Services Inline
+------------------------------------
+
+.. versionadded:: 7.1
+
+   The ``#[AutowireInline]`` attribute was added in Symfony 7.1.
+
+Similar to how anonymous services can be defined inline in configuration files,
+the :class:`Symfony\\Component\\DependencyInjection\\Attribute\\AutowireInline`
+attribute allows you to declare anonymous services inline, directly next to their
+corresponding arguments::
+
+    public function __construct(
+        #[AutowireInline(
+            factory: [ScopingHttpClient::class, 'forBaseUri'],
+            arguments: [
+                '$baseUri' => 'https://api.example.com',
+                '$defaultOptions' => [
+                    'auth_bearer' => '%env(EXAMPLE_TOKEN)%',
+                ],
+            ]
+        )]
+        private HttpClientInterface $client,
+    ) {
+    }
+
+This example tells Symfony to inject an object created by calling the
+``ScopingHttpClient::forBaseUri()`` factory with the specified base URI and
+default options. This is just one example: you can use the ``#[AutowireInline]``
+attribute to define any kind of anonymous service.
+
+While this approach is convenient for simple service definitions, consider moving
+complex or heavily configured services to a configuration file to ease maintenance.
+
 Autowiring Controller Action Methods
 ------------------------------------
 
diff --git a/service_container/compiler_passes.rst b/service_container/compiler_passes.rst
index 11458a4e8e3..096c60c2642 100644
--- a/service_container/compiler_passes.rst
+++ b/service_container/compiler_passes.rst
@@ -77,10 +77,11 @@ bundle class::
     namespace App\MyBundle;
 
     use App\DependencyInjection\Compiler\CustomPass;
+    use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
     use Symfony\Component\DependencyInjection\ContainerBuilder;
     use Symfony\Component\HttpKernel\Bundle\AbstractBundle;
 
-    class MyBundle extends AbstractBundle
+    class MyBundle extends AbstractBundle implements CompilerPassInterface
     {
         public function process(ContainerBuilder $container): void
         {
diff --git a/service_container/debug.rst b/service_container/debug.rst
index c09413e7213..9e3e28a5343 100644
--- a/service_container/debug.rst
+++ b/service_container/debug.rst
@@ -52,5 +52,8 @@ its id:
 
     $ php bin/console debug:container App\Service\Mailer
 
-    # to show the service arguments:
-    $ php bin/console debug:container App\Service\Mailer --show-arguments
+.. deprecated:: 7.3
+
+    Starting in Symfony 7.3, this command displays the service arguments by default.
+    In earlier Symfony versions, you needed to use the ``--show-arguments`` option,
+    which is now deprecated.
diff --git a/service_container/factories.rst b/service_container/factories.rst
index 0c6a4724609..9864287d57a 100644
--- a/service_container/factories.rst
+++ b/service_container/factories.rst
@@ -389,7 +389,7 @@ e.g. change the service based on a parameter:
 
             # you can use the arg() function to retrieve an argument from the definition
             App\Email\NewsletterManagerInterface:
-                factory: "@=arg(0).createNewsletterManager() ?: service("default_newsletter_manager")"
+                factory: '@=arg(0).createNewsletterManager() ?: service("default_newsletter_manager")'
                 arguments:
                     - '@App\Email\NewsletterManagerFactory'
 
@@ -410,7 +410,7 @@ e.g. change the service based on a parameter:
 
                 <!-- you can use the arg() function to retrieve an argument from the definition -->
                 <service id="App\Email\NewsletterManagerInterface">
-                    <factory expression="arg(0).createNewsletterManager() ?: service("default_newsletter_manager")"/>
+                    <factory expression="arg(0).createNewsletterManager() ?: service('default_newsletter_manager')"/>
                     <argument type="service" id="App\Email\NewsletterManagerFactory"/>
                 </service>
             </services>
diff --git a/service_container/import.rst b/service_container/import.rst
index d5056032115..47af34d3a34 100644
--- a/service_container/import.rst
+++ b/service_container/import.rst
@@ -82,7 +82,6 @@ a relative or absolute path to the imported file:
 
             App\:
                 resource: '../src/*'
-                exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}'
 
             # ...
 
@@ -104,8 +103,98 @@ a relative or absolute path to the imported file:
             <services>
                 <defaults autowire="true" autoconfigure="true"/>
 
-                <prototype namespace="App\" resource="../src/*"
-                    exclude="../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}"/>
+                <prototype namespace="App\" resource="../src/*"/>
+
+                <!-- ... -->
+            </services>
+        </container>
+
+    .. code-block:: php
+
+        // config/services.php
+        namespace Symfony\Component\DependencyInjection\Loader\Configurator;
+
+        return function(ContainerConfigurator $container): void {
+            $container->import('services/mailer.php');
+            // If you want to import a whole directory:
+            $container->import('services/');
+
+            $services = $container->services()
+                ->defaults()
+                    ->autowire()
+                    ->autoconfigure()
+            ;
+
+            $services->load('App\\', '../src/*');
+        };
+
+When loading a configuration file, Symfony first processes all imported files in
+the order they are listed under the ``imports`` key. After all imports are processed,
+it then processes the parameters and services defined directly in the current file.
+In practice, this means that **later definitions override earlier ones**.
+
+For example, if you use the :ref:`default services.yaml configuration <service-container-services-load-example>`
+as in the above example, your main ``config/services.yaml`` file uses the ``App\``
+namespace to auto-discover services and loads them after all imported files.
+If an imported file (e.g. ``config/services/mailer.yaml``) defines a service that
+is also auto-discovered, the definition from ``services.yaml`` will take precedence.
+
+To make sure your specific service definitions are not overridden by auto-discovery,
+consider one of the following strategies:
+
+#. :ref:`Exclude services from auto-discovery <import-exclude-services-from-auto-discovery>`
+#. :ref:`Override services in the same file <import-override-services-in-the-same-file>`
+#. :ref:`Control import order <import-control-import-order>`
+
+.. _import-exclude-services-from-auto-discovery:
+
+**Exclude services from auto-discovery**
+
+Adjust the ``App\`` definition to use the ``exclude`` option. This prevents Symfony
+from auto-registering classes that are defined manually elsewhere:
+
+.. configuration-block::
+
+    .. code-block:: yaml
+
+        # config/services.yaml
+        imports:
+            - { resource: services/mailer.yaml }
+            # ... other imports
+
+        services:
+            _defaults:
+                autowire: true
+                autoconfigure: true
+
+            App\:
+                resource: '../src/*'
+                exclude:
+                    - '../src/Mailer/'
+                    - '../src/SpecificClass.php'
+
+    .. code-block:: xml
+
+        <!-- config/services.xml -->
+        <?xml version="1.0" encoding="UTF-8" ?>
+        <container xmlns="http://symfony.com/schema/dic/services"
+            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+            xsi:schemaLocation="http://symfony.com/schema/dic/services
+                https://symfony.com/schema/dic/services/services-1.0.xsd">
+
+            <imports>
+                <import resource="services/mailer.xml"/>
+                <!-- If you want to import a whole directory: -->
+                <import resource="services/"/>
+            </imports>
+
+            <services>
+                <defaults autowire="true" autoconfigure="true"/>
+
+                <prototype namespace="App\" resource="../src/*">
+                    <exclude>../src/Mailer/</exclude>
+                    <exclude>../src/SpecificClass.php</exclude>
+                </prototype>
 
                 <!-- ... -->
             </services>
@@ -128,20 +217,209 @@ a relative or absolute path to the imported file:
             ;
 
             $services->load('App\\', '../src/*')
-                ->exclude('../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}');
+                ->exclude([
+                    '../src/Mailer/',
+                    '../src/SpecificClass.php',
+                ]);
         };
 
-When loading a configuration file, Symfony loads first the imported files and
-then it processes the parameters and services defined in the file. If you use the
-:ref:`default services.yaml configuration <service-container-services-load-example>`
-as in the above example, the ``App\`` definition creates services for classes
-found in ``../src/*``. If your imported file defines services for those classes
-too, they will be overridden.
-
-A possible solution for this is to add the classes and/or directories of the
-imported files in the ``exclude`` option of the ``App\`` definition. Another
-solution is to not use imports and add the service definitions in the same file,
-but after the ``App\`` definition to override it.
+.. _import-override-services-in-the-same-file:
+
+**Override services in the same file**
+
+You can define specific services after the ``App\`` auto-discovery block in the
+same file. These later definitions will override the auto-registered ones:
+
+.. configuration-block::
+
+    .. code-block:: yaml
+
+        # config/services.yaml
+        services:
+            _defaults:
+                autowire: true
+                autoconfigure: true
+
+            App\:
+                resource: '../src/*'
+
+            App\Mailer\MyMailer:
+                arguments: ['%env(MAILER_DSN)%']
+
+    .. code-block:: xml
+
+        <!-- config/services.xml -->
+        <?xml version="1.0" encoding="UTF-8" ?>
+        <container xmlns="http://symfony.com/schema/dic/services"
+            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+            xsi:schemaLocation="http://symfony.com/schema/dic/services
+                https://symfony.com/schema/dic/services/services-1.0.xsd">
+
+            <imports>
+                <import resource="services/mailer.xml"/>
+                <!-- If you want to import a whole directory: -->
+                <import resource="services/"/>
+            </imports>
+
+            <services>
+                <defaults autowire="true" autoconfigure="true"/>
+
+                <prototype namespace="App\" resource="../src/*"/>
+
+                <service id="App\Mailer\MyMailer">
+                    <argument>%env(MAILER_DSN)%</argument>
+                </service>
+
+                <!-- ... -->
+            </services>
+        </container>
+
+    .. code-block:: php
+
+        // config/services.php
+        namespace Symfony\Component\DependencyInjection\Loader\Configurator;
+
+        return function(ContainerConfigurator $container): void {
+            $services = $container->services()
+                ->defaults()
+                    ->autowire()
+                    ->autoconfigure();
+
+            $services->load('App\\', '../src/*');
+
+            $services->set(App\Mailer\MyMailer::class)
+                ->arg(0, '%env(MAILER_DSN)%');
+        };
+
+.. _import-control-import-order:
+
+**Control import order**
+
+Move the ``App\`` auto-discovery config to a separate file and import it
+before more specific service files. This way, specific service definitions
+can override the auto-discovered ones.
+
+.. configuration-block::
+
+    .. code-block:: yaml
+
+        # config/services/autodiscovery.yaml
+        services:
+            _defaults:
+                autowire: true
+                autoconfigure: true
+
+            App\:
+                resource: '../../src/*'
+                exclude:
+                    - '../../src/Mailer/'
+
+        # config/services/mailer.yaml
+        services:
+            App\Mailer\SpecificMailer:
+                # ... custom configuration
+
+        # config/services.yaml
+        imports:
+            - { resource: services/autodiscovery.yaml }
+            - { resource: services/mailer.yaml }
+            - { resource: services/ }
+
+        services:
+            # definitions here override anything from the imports above
+            # consider keeping most definitions inside imported files
+
+    .. code-block:: xml
+
+        <!-- config/services/autodiscovery.xml -->
+        <?xml version="1.0" encoding="UTF-8" ?>
+        <container xmlns="http://symfony.com/schema/dic/services"
+                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                   xsi:schemaLocation="http://symfony.com/schema/dic/services
+                       https://symfony.com/schema/dic/services/services-1.0.xsd">
+
+            <services>
+                <defaults autowire="true" autoconfigure="true"/>
+
+                <prototype namespace="App\" resource="../../src/*">
+                    <exclude>../../src/Mailer/</exclude>
+                </prototype>
+            </services>
+        </container>
+
+        <!-- config/services/mailer.xml -->
+        <?xml version="1.0" encoding="UTF-8" ?>
+        <container xmlns="http://symfony.com/schema/dic/services"
+                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                   xsi:schemaLocation="http://symfony.com/schema/dic/services
+                       https://symfony.com/schema/dic/services/services-1.0.xsd">
+
+            <services>
+                <service id="App\Mailer\SpecificMailer">
+                    <!-- ... custom configuration -->
+                </service>
+            </services>
+        </container>
+
+        <!-- config/services.xml -->
+        <?xml version="1.0" encoding="UTF-8" ?>
+        <container xmlns="http://symfony.com/schema/dic/services"
+                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                   xsi:schemaLocation="http://symfony.com/schema/dic/services
+                       https://symfony.com/schema/dic/services/services-1.0.xsd">
+
+            <imports>
+                <import resource="services/autodiscovery.xml"/>
+                <import resource="services/mailer.xml"/>
+                <import resource="services/"/>
+            </imports>
+
+            <services>
+                <!-- definitions here override anything from the imports above -->
+                <!-- consider keeping most definitions inside imported files -->
+            </services>
+        </container>
+
+    .. code-block:: php
+
+        // config/services/autodiscovery.php
+        use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
+
+        return function (ContainerConfigurator $container): void {
+            $services = $container->services()
+                ->defaults()
+                    ->autowire()
+                    ->autoconfigure();
+
+            $services->load('App\\', '../../src/*')
+                ->exclude([
+                    '../../src/Mailer/',
+                ]);
+        };
+
+        // config/services/mailer.php
+        use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
+
+        return function (ContainerConfigurator $container): void {
+            $services = $container->services();
+
+            $services->set(App\Mailer\SpecificMailer::class);
+            // Add any custom configuration here if needed
+        };
+
+        // config/services.php
+        use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
+
+        return function (ContainerConfigurator $container): void {
+            $container->import('services/autodiscovery.php');
+            $container->import('services/mailer.php');
+            $container->import('services/');
+
+            $services = $container->services();
+
+            // definitions here override anything from the imports above
+            // consider keeping most definitions inside imported files
+        };
 
 .. include:: /components/dependency_injection/_imports-parameters-note.rst.inc
 
diff --git a/service_container/lazy_services.rst b/service_container/lazy_services.rst
index 23d76a4cfbf..abb3c2cca7f 100644
--- a/service_container/lazy_services.rst
+++ b/service_container/lazy_services.rst
@@ -26,9 +26,6 @@ until you interact with the proxy in some way.
     Lazy services do not support `final`_ or ``readonly`` classes, but you can use
     `Interface Proxifying`_ to work around this limitation.
 
-    In PHP versions prior to 8.0 lazy services do not support parameters with
-    default values for built-in PHP classes (e.g. ``PDO``).
-
 .. _lazy-services_configuration:
 
 Configuration
@@ -78,11 +75,6 @@ same signature of the class representing the service should be injected. A lazy
 itself when being accessed for the first time). The same happens when calling
 ``Container::get()`` directly.
 
-To check if your lazy service works you can check the interface of the received object::
-
-    dump(class_implements($service));
-    // the output should include "Symfony\Component\VarExporter\LazyObjectInterface"
-
 You can also configure your service's laziness thanks to the
 :class:`Symfony\\Component\\DependencyInjection\\Attribute\\Autoconfigure` attribute.
 For example, to define your service as lazy use the following::
diff --git a/service_container/service_closures.rst b/service_container/service_closures.rst
index cedbaaa2bf9..88b0ab64002 100644
--- a/service_container/service_closures.rst
+++ b/service_container/service_closures.rst
@@ -52,6 +52,13 @@ argument of type ``service_closure``:
                 # In case the dependency is optional
                 # arguments: [!service_closure '@?mailer']
 
+            # you can also use the special '@>' syntax as a shortcut of '!service_closure'
+            App\Service\AnotherService:
+                arguments: ['@>mailer']
+
+                # the shortcut also works for optional dependencies
+                # arguments: ['@>?mailer']
+
     .. code-block:: xml
 
         <!-- config/services.xml -->
@@ -90,6 +97,10 @@ argument of type ``service_closure``:
             //     ->args([service_closure('mailer')->ignoreOnInvalid()]);
         };
 
+.. versionadded:: 7.3
+
+    The ``@>`` shortcut syntax for YAML was introduced in Symfony 7.3.
+
 .. seealso::
 
     Service closures can be injected :ref:`by using autowiring <autowiring_closures>`
diff --git a/service_container/service_subscribers_locators.rst b/service_container/service_subscribers_locators.rst
index 9c6451931d1..9026478cf33 100644
--- a/service_container/service_subscribers_locators.rst
+++ b/service_container/service_subscribers_locators.rst
@@ -36,7 +36,7 @@ to handle their respective command when it is asked for::
 
         public function handle(Command $command): mixed
         {
-            $commandClass = get_class($command);
+            $commandClass = $command::class;
 
             if (!$handler = $this->handlerMap[$commandClass] ?? null) {
                 return;
@@ -94,7 +94,7 @@ in the service subscriber::
 
         public function handle(Command $command): mixed
         {
-            $commandClass = get_class($command);
+            $commandClass = $command::class;
 
             if ($this->locator->has($commandClass)) {
                 $handler = $this->locator->get($commandClass);
@@ -270,8 +270,8 @@ the following dependency injection attributes in the ``getSubscribedServices()``
 method directly:
 
 * :class:`Symfony\\Component\\DependencyInjection\\Attribute\\Autowire`
-* :class:`Symfony\\Component\\DependencyInjection\\Attribute\\TaggedIterator`
-* :class:`Symfony\\Component\\DependencyInjection\\Attribute\\TaggedLocator`
+* :class:`Symfony\\Component\\DependencyInjection\\Attribute\\AutowireIterator`
+* :class:`Symfony\\Component\\DependencyInjection\\Attribute\\AutowireLocator`
 * :class:`Symfony\\Component\\DependencyInjection\\Attribute\\Target`
 * :class:`Symfony\\Component\\DependencyInjection\\Attribute\\AutowireDecorated`
 
@@ -282,8 +282,8 @@ This is done by having ``getSubscribedServices()`` return an array of
     use Psr\Container\ContainerInterface;
     use Psr\Log\LoggerInterface;
     use Symfony\Component\DependencyInjection\Attribute\Autowire;
-    use Symfony\Component\DependencyInjection\Attribute\TaggedIterator;
-    use Symfony\Component\DependencyInjection\Attribute\TaggedLocator;
+    use Symfony\Component\DependencyInjection\Attribute\AutowireIterator;
+    use Symfony\Component\DependencyInjection\Attribute\AutowireLocator;
     use Symfony\Component\DependencyInjection\Attribute\Target;
     use Symfony\Contracts\Service\Attribute\SubscribedService;
 
@@ -299,11 +299,11 @@ This is done by having ``getSubscribedServices()`` return an array of
             // Target
             new SubscribedService('event.logger', LoggerInterface::class, attributes: new Target('eventLogger')),
 
-            // TaggedIterator
-            new SubscribedService('loggers', 'iterable', attributes: new TaggedIterator('logger.tag')),
+            // AutowireIterator
+            new SubscribedService('loggers', 'iterable', attributes: new AutowireIterator('logger.tag')),
 
-            // TaggedLocator
-            new SubscribedService('handlers', ContainerInterface::class, attributes: new TaggedLocator('handler.tag')),
+            // AutowireLocator
+            new SubscribedService('handlers', ContainerInterface::class, attributes: new AutowireLocator('handler.tag')),
         ];
     }
 
@@ -320,10 +320,10 @@ This is done by having ``getSubscribedServices()`` return an array of
     The above example requires using ``3.2`` version or newer of ``symfony/service-contracts``.
 
 .. _service-locator_autowire-locator:
-.. _service-locator_autowire-iterator:
+.. _the-autowirelocator-and-autowireiterator-attributes:
 
-The AutowireLocator and AutowireIterator Attributes
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+The AutowireLocator Attribute
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 Another way to define a service locator is to use the
 :class:`Symfony\\Component\\DependencyInjection\\Attribute\\AutowireLocator`
@@ -350,7 +350,7 @@ attribute::
 
         public function handle(Command $command): mixed
         {
-            $commandClass = get_class($command);
+            $commandClass = $command::class;
 
             if ($this->handlers->has($commandClass)) {
                 $handler = $this->handlers->get($commandClass);
@@ -397,13 +397,43 @@ attribute::
         }
     }
 
-.. note::
+.. _service-locator_autowire-iterator:
 
-    To receive an iterable instead of a service locator, you can switch the
-    :class:`Symfony\\Component\\DependencyInjection\\Attribute\\AutowireLocator`
-    attribute to
-    :class:`Symfony\\Component\\DependencyInjection\\Attribute\\AutowireIterator`
-    attribute.
+The AutowireIterator Attribute
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+A variant of ``AutowireLocator`` that injects an iterable of services tagged
+with a specific :doc:`tag </service_container/tags>`. This is useful to loop
+over a set of tagged services instead of retrieving them individually.
+
+For example, to collect all handlers for different command types, use the
+``AutowireIterator`` attribute and pass the tag used by those services::
+
+    // src/CommandBus.php
+    namespace App;
+
+    use App\CommandHandler\BarHandler;
+    use App\CommandHandler\FooHandler;
+    use Psr\Container\ContainerInterface;
+    use Symfony\Component\DependencyInjection\Attribute\AutowireIterator;
+
+    class CommandBus
+    {
+        public function __construct(
+            #[AutowireIterator('command_handler')]
+            private iterable $handlers, // collects all services tagged with 'command_handler'
+        ) {
+        }
+
+        public function handle(Command $command): mixed
+        {
+            foreach ($this->handlers as $handler) {
+                if ($handler->supports($command)) {
+                    return $handler->handle($command);
+                }
+            }
+        }
+    }
 
 .. _service-subscribers-locators_defining-service-locator:
 
@@ -975,8 +1005,8 @@ You can use the ``attributes`` argument of ``SubscribedService`` to add any
 of the following dependency injection attributes:
 
 * :class:`Symfony\\Component\\DependencyInjection\\Attribute\\Autowire`
-* :class:`Symfony\\Component\\DependencyInjection\\Attribute\\TaggedIterator`
-* :class:`Symfony\\Component\\DependencyInjection\\Attribute\\TaggedLocator`
+* :class:`Symfony\\Component\\DependencyInjection\\Attribute\\AutowireIterator`
+* :class:`Symfony\\Component\\DependencyInjection\\Attribute\\AutowireLocator`
 * :class:`Symfony\\Component\\DependencyInjection\\Attribute\\Target`
 * :class:`Symfony\\Component\\DependencyInjection\\Attribute\\AutowireDecorated`
 
diff --git a/service_container/tags.rst b/service_container/tags.rst
index 270d6702f5a..3a547042de7 100644
--- a/service_container/tags.rst
+++ b/service_container/tags.rst
@@ -155,22 +155,30 @@ In a Symfony application, call this method in your kernel class::
         }
     }
 
-In a Symfony bundle, call this method in the ``load()`` method of the
-:doc:`bundle extension class </bundles/extension>`::
+In bundles extending the :class:`Symfony\\Component\\HttpKernel\\Bundle\\AbstractBundle`
+class, call this method in the ``loadExtension()`` method of the main bundle class::
 
-    // src/DependencyInjection/MyBundleExtension.php
-    class MyBundleExtension extends Extension
-    {
-        // ...
+    // ...
+    use Symfony\Component\DependencyInjection\ContainerBuilder;
+    use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
+    use Symfony\Component\HttpKernel\Bundle\AbstractBundle;
 
-        public function load(array $configs, ContainerBuilder $container): void
+    class MyBundle extends AbstractBundle
+    {
+        public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void
         {
-            $container->registerForAutoconfiguration(CustomInterface::class)
+            $builder
+                ->registerForAutoconfiguration(CustomInterface::class)
                 ->addTag('app.custom_tag')
             ;
         }
     }
 
+.. note::
+
+    For bundles not extending the ``AbstractBundle`` class, call this method in
+    the ``load()`` method of the :doc:`bundle extension class </bundles/extension>`.
+
 Autoconfiguration registering is not limited to interfaces. It is possible
 to use PHP attributes to autoconfigure services by using the
 :method:`Symfony\\Component\\DependencyInjection\\ContainerBuilder::registerAttributeForAutoconfiguration`
@@ -750,7 +758,7 @@ directly via PHP attributes:
 
 .. note::
 
-    Some IDEs will show an error when using ``#[TaggedIterator]`` together
+    Some IDEs will show an error when using ``#[AutowireIterator]`` together
     with the `PHP constructor promotion`_:
     *"Attribute cannot be applied to a property because it does not contain the 'Attribute::TARGET_PROPERTY' flag"*.
     The reason is that those constructor arguments are both parameters and class
@@ -1281,4 +1289,19 @@ be used directly on the class of the service you want to configure::
         // ...
     }
 
+You can apply the ``#[AsTaggedItem]`` attribute multiple times to register the
+same service under different indexes::
+
+    #[AsTaggedItem(index: 'handler_one', priority: 5)]
+    #[AsTaggedItem(index: 'handler_two', priority: 20)]
+    class SomeService
+    {
+        // ...
+    }
+
+.. versionadded:: 7.3
+
+    The feature to apply the ``#[AsTaggedItem]`` attribute multiple times was
+    introduced in Symfony 7.3.
+
 .. _`PHP constructor promotion`: https://www.php.net/manual/en/language.oop5.decon.php#language.oop5.decon.constructor.promotion
diff --git a/session.rst b/session.rst
index 9ddf3eb028d..1594cc7eebe 100644
--- a/session.rst
+++ b/session.rst
@@ -115,7 +115,7 @@ sessions for anonymous users, you must *completely* avoid accessing the session.
 .. note::
 
     Sessions will also be started when using features that rely on them internally,
-    such as the :ref:`CSRF protection in forms <csrf-protection-forms>`.
+    such as the :ref:`stateful CSRF protection in forms <csrf-protection-forms>`.
 
 .. _flash-messages:
 
@@ -425,6 +425,11 @@ Check out the Symfony config reference to learn more about the other available
     ``session.auto_start = 1`` This directive should be turned off in
     ``php.ini``, in the web server directives or in ``.htaccess``.
 
+.. deprecated:: 7.2
+
+    The ``sid_length`` and ``sid_bits_per_character`` options were deprecated
+    in Symfony 7.2 and will be ignored in Symfony 8.0.
+
 The session cookie is also available in :ref:`the Response object <component-http-foundation-response>`.
 This is useful to get that cookie in the CLI context or when using PHP runners
 like Roadrunner or Swoole.
@@ -487,12 +492,11 @@ the ``php.ini`` directive ``session.gc_maxlifetime``. The meaning in this contex
 that any stored session that was saved more than ``gc_maxlifetime`` ago should be
 deleted. This allows one to expire records based on idle time.
 
-However, some operating systems (e.g. Debian) do their own session handling and set
-the ``session.gc_probability`` variable to ``0`` to stop PHP doing garbage
-collection. That's why Symfony now overwrites this value to ``1``.
-
-If you wish to use the original value set in your ``php.ini``, add the following
-configuration:
+However, some operating systems (e.g. Debian) manage session handling differently
+and set the ``session.gc_probability`` variable to ``0`` to prevent PHP from performing
+garbage collection. By default, Symfony uses the value of the ``gc_probability``
+directive set in the ``php.ini`` file. If you can't modify this PHP setting, you
+can configure it directly in Symfony:
 
 .. code-block:: yaml
 
@@ -500,14 +504,19 @@ configuration:
     framework:
         session:
             # ...
-            gc_probability: null
+            gc_probability: 1
 
-You can configure these settings by passing ``gc_probability``, ``gc_divisor``
-and ``gc_maxlifetime`` in an array to the constructor of
+Alternatively, you can configure these settings by passing ``gc_probability``,
+``gc_divisor`` and ``gc_maxlifetime`` in an array to the constructor of
 :class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\NativeSessionStorage`
 or to the :method:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\NativeSessionStorage::setOptions`
 method.
 
+.. versionadded:: 7.2
+
+    Using the ``php.ini`` directive as the default value for ``gc_probability``
+    was introduced in Symfony 7.2.
+
 .. _session-database:
 
 Store Sessions in a Database
@@ -966,7 +975,7 @@ MariaDB/MySQL
         `sess_data` BLOB NOT NULL,
         `sess_lifetime` INTEGER UNSIGNED NOT NULL,
         `sess_time` INTEGER UNSIGNED NOT NULL,
-        INDEX `sessions_sess_lifetime_idx` (`sess_lifetime`)
+        INDEX `sess_lifetime_idx` (`sess_lifetime`)
     ) COLLATE utf8mb4_bin, ENGINE = InnoDB;
 
 .. note::
@@ -987,7 +996,7 @@ PostgreSQL
         sess_lifetime INTEGER NOT NULL,
         sess_time INTEGER NOT NULL
     );
-    CREATE INDEX sessions_sess_lifetime_idx ON sessions (sess_lifetime);
+    CREATE INDEX sess_lifetime_idx ON sessions (sess_lifetime);
 
 Microsoft SQL Server
 ++++++++++++++++++++
@@ -999,7 +1008,7 @@ Microsoft SQL Server
         sess_data NVARCHAR(MAX) NOT NULL,
         sess_lifetime INTEGER NOT NULL,
         sess_time INTEGER NOT NULL,
-        INDEX sessions_sess_lifetime_idx (sess_lifetime)
+        INDEX sess_lifetime_idx (sess_lifetime)
     );
 
 .. _session-database-mongodb:
@@ -1843,8 +1852,8 @@ the example below:
                 https://symfony.com/schema/dic/services/services-1.0.xsd">
 
             <framework:config>
-                <framework:session storage-id="session.storage.php_bridge"
-                    handler-id="session.storage.native_file"
+                <framework:session storage-id="session.storage.factory.php_bridge"
+                    handler-id="session.handler.native_file"
                 />
             </framework:config>
         </container>
@@ -1857,7 +1866,7 @@ the example below:
         return static function (FrameworkConfig $framework): void {
             $framework->session()
                 ->storageFactoryId('session.storage.factory.php_bridge')
-                ->handlerId('session.storage.native_file')
+                ->handlerId('session.handler.native_file')
             ;
         };
 
diff --git a/setup.rst b/setup.rst
index c8654296986..a7fa8c66826 100644
--- a/setup.rst
+++ b/setup.rst
@@ -4,7 +4,7 @@ Installing & Setting up the Symfony Framework
 .. admonition:: Screencast
     :class: screencast
 
-    Do you prefer video tutorials? Check out the `Harmonious Development with Symfony`_
+    Do you prefer video tutorials? Check out the `Cosmic Coding with Symfony`_
     screencast series.
 
 .. _symfony-tech-requirements:
@@ -48,10 +48,10 @@ application:
 .. code-block:: terminal
 
     # run this if you are building a traditional web application
-    $ symfony new my_project_directory --version="7.1.*" --webapp
+    $ symfony new my_project_directory --version="7.3.x" --webapp
 
     # run this if you are building a microservice, console application or API
-    $ symfony new my_project_directory --version="7.1.*"
+    $ symfony new my_project_directory --version="7.3.x"
 
 The only difference between these two commands is the number of packages
 installed by default. The ``--webapp`` option installs extra packages to give
@@ -63,12 +63,12 @@ Symfony application using Composer:
 .. code-block:: terminal
 
     # run this if you are building a traditional web application
-    $ composer create-project symfony/skeleton:"7.1.*" my_project_directory
+    $ composer create-project symfony/skeleton:"7.3.x" my_project_directory
     $ cd my_project_directory
     $ composer require webapp
 
     # run this if you are building a microservice, console application or API
-    $ composer create-project symfony/skeleton:"7.1.*" my_project_directory
+    $ composer create-project symfony/skeleton:"7.3.x" my_project_directory
 
 No matter which command you run to create the Symfony application. All of them
 will create a new ``my_project_directory/`` directory, download some dependencies
@@ -103,7 +103,7 @@ Git, setup your project with the following commands:
 
 You'll probably also need to customize your :ref:`.env file <config-dot-env>`
 and do a few other project-specific tasks (e.g. creating a database). When
-working on a existing Symfony application for the first time, it may be useful
+working on an existing Symfony application for the first time, it may be useful
 to run this command which displays information about the project:
 
 .. code-block:: terminal
@@ -121,8 +121,8 @@ development.
 .. _symfony-binary-web-server:
 
 However for local development, the most convenient way of running Symfony is by
-using the :doc:`local web server </setup/symfony_server>` provided by the
-``symfony`` binary. This local server provides among other things support for
+using the :ref:`local web server <symfony-cli-server>` provided by the
+Symfony CLI tool. This local server provides among other things support for
 HTTP/2, concurrent requests, TLS/SSL and automatic generation of security
 certificates.
 
@@ -249,9 +249,9 @@ workflows to make them fail when there are vulnerabilities.
 .. tip::
 
     In continuous integration services you can check security vulnerabilities
-    using a different stand-alone project called `Local PHP Security Checker`_.
-    This is the same project used internally by ``check:security`` but much
-    smaller in size than the entire Symfony CLI.
+    by running the ``composer audit`` command. This uses the same data internally
+    as ``check:security`` but does not require installing the entire Symfony CLI
+    during CI or on CI workers.
 
 Symfony LTS Versions
 --------------------
@@ -311,14 +311,13 @@ Learn More
     setup/web_server_configuration
     setup/*
 
-.. _`Harmonious Development with Symfony`: https://symfonycasts.com/screencast/symfony
+.. _`Cosmic Coding with Symfony`: https://symfonycasts.com/screencast/symfony
 .. _`Install Composer`: https://getcomposer.org/download/
 .. _`install the Symfony CLI`: https://symfony.com/download
 .. _`symfony-cli/symfony-cli GitHub repository`: https://github.com/symfony-cli/symfony-cli
 .. _`The Symfony Demo Application`: https://github.com/symfony/demo
 .. _`Symfony Flex`: https://github.com/symfony/flex
 .. _`PHP security advisories database`: https://github.com/FriendsOfPHP/security-advisories
-.. _`Local PHP Security Checker`: https://github.com/fabpot/local-php-security-checker
 .. _`Symfony releases`: https://symfony.com/releases
 .. _`Main recipe repository`: https://github.com/symfony/recipes
 .. _`Contrib recipe repository`: https://github.com/symfony/recipes-contrib
diff --git a/setup/_update_dep_errors.rst.inc b/setup/_update_dep_errors.rst.inc
index 49ae97067e4..5dc7e6745bc 100644
--- a/setup/_update_dep_errors.rst.inc
+++ b/setup/_update_dep_errors.rst.inc
@@ -24,7 +24,7 @@ versions of other libraries. Check your error message to debug.
 Another issue that may happen is that the project dependencies can be installed
 on your local computer but not on the remote server. This usually happens when
 the PHP versions are different on each machine. The solution is to add the
-`platform`_ config option to your `composer.json` file to define the highest
+`platform`_ config option to your ``composer.json`` file to define the highest
 PHP version allowed for the dependencies (set it to the server's PHP version).
 
 .. _`platform`: https://getcomposer.org/doc/06-config.md#platform
diff --git a/setup/symfony_cli.rst b/setup/symfony_cli.rst
new file mode 100644
index 00000000000..7b20c871558
--- /dev/null
+++ b/setup/symfony_cli.rst
@@ -0,0 +1,653 @@
+.. _symfony-server:
+.. _symfony-local-web-server:
+
+Symfony CLI
+===========
+
+The **Symfony CLI** is a free and `open source`_ developer tool to help you build,
+run, and manage your Symfony applications directly from your terminal. It's designed
+to boost your productivity with smart features like:
+
+* **Web server** optimized for development, with **HTTPS support**
+* **Docker** integration and automatic environment variable management
+* Management of multiple **PHP versions**
+* Support for background **workers**
+* Seamless integration with **Symfony Cloud**
+
+Installation
+------------
+
+The Symfony CLI is available as a standalone executable that supports Linux,
+macOS, and Windows. Download and install it following the instructions on
+`symfony.com/download`_.
+
+.. _symfony-cli-autocompletion:
+
+Shell Autocompletion
+~~~~~~~~~~~~~~~~~~~~
+
+The Symfony CLI supports autocompletion for Bash, Zsh, and Fish shells. This
+helps you type commands faster and discover available options:
+
+.. code-block:: terminal
+
+    # install autocompletion (do this only once)
+    $ symfony completion bash | sudo tee /etc/bash_completion.d/symfony
+
+    # for Zsh users
+    $ symfony completion zsh > ~/.symfony_completion && echo "source ~/.symfony_completion" >> ~/.zshrc
+
+    # for Fish users
+    $ symfony completion fish | source
+
+After installation, restart your terminal to enable autocompletion. The CLI will
+also provide autocompletion for ``composer`` and ``console`` commands when it
+detects a Symfony project.
+
+Creating New Symfony Applications
+---------------------------------
+
+The Symfony CLI includes a project creation command that helps you start new
+projects quickly:
+
+.. code-block:: terminal
+
+    # create a new Symfony project based on the latest stable version
+    $ symfony new my_project
+
+    # create a project with the latest LTS (Long Term Support) version
+    $ symfony new my_project --version=lts
+
+    # create a project based on a specific Symfony version
+    $ symfony new my_project --version=6.4
+
+    # create a project using the development version
+    $ symfony new my_project --version=next
+
+    # all the previous commands create minimal projects with the least
+    # amount of dependencies possible; if you are building a website or
+    # web application, add this option to install all the common dependencies
+    $ symfony new my_project --webapp
+
+    # Create a project based on the Symfony Demo application
+    $ symfony new my_project --demo
+
+.. tip::
+
+    Pass the ``--cloud`` option to initialize a Symfony Cloud project at the same
+    time the Symfony project is created.
+
+.. _symfony-cli-server:
+
+Running the Local Web Server
+----------------------------
+
+The Symfony CLI includes a **local web server** designed for development. It's
+not intended for production use, but it provides features that improve the
+developer experience:
+
+* HTTPS support with automatic certificate generation
+* HTTP/2 support
+* Automatic PHP version selection
+* Integration with Docker services
+* Built-in proxy for custom domain names
+
+.. _getting-started:
+
+Serving Your Application
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+To serve a Symfony project with the local server:
+
+.. code-block:: terminal
+
+    $ cd my-project/
+    $ symfony server:start
+
+      [OK] Web server listening on http://127.0.0.1:8000
+      ...
+
+Now browse the given URL or run the following command to open it in the browser:
+
+.. code-block:: terminal
+
+    $ symfony open:local
+
+.. tip::
+
+    If you work on more than one project, you can run multiple instances of the
+    Symfony server on your development machine. Each instance will find a different
+    available port.
+
+The ``server:start`` command blocks the current terminal to output the server
+logs. To run the server in the background:
+
+.. code-block:: terminal
+
+    $ symfony server:start -d
+
+Now you can continue working in the terminal and run other commands:
+
+.. code-block:: terminal
+
+    # view the latest log messages
+    $ symfony server:log
+
+    # stop the background server
+    $ symfony server:stop
+
+.. tip::
+
+    On macOS, when starting the Symfony server you might see a warning dialog asking
+    *"Do you want the application to accept incoming network connections?"*.
+    This happens when running unsigned applications that are not listed in the
+    firewall list. The solution is to run this command to sign the Symfony CLI:
+
+    .. code-block:: terminal
+
+        $ sudo codesign --force --deep --sign - $(whereis -q symfony)
+
+Enabling PHP-FPM
+~~~~~~~~~~~~~~~~
+
+.. note::
+
+    PHP-FPM must be installed locally for the Symfony server to utilize.
+
+When the server starts, it checks for ``web/index_dev.php``, ``web/index.php``,
+``public/app_dev.php``, ``public/app.php``, ``public/index.php`` in that order. If one is found, the
+server will automatically start with PHP-FPM enabled. Otherwise the server will
+start without PHP-FPM and will show a ``Page not found`` page when trying to
+access a ``.php`` file in the browser.
+
+.. tip::
+
+    When an ``index.html`` and a front controller (e.g. ``index.php``) are both
+    present, the server will still start with PHP-FPM enabled, but the
+    ``index.html`` will take precedence. This means that if an ``index.html``
+    file is present in ``public/`` or ``web/``, it will be displayed instead of
+    the ``index.php``, which would otherwise show, for example, the Symfony
+    application.
+
+Enabling HTTPS/TLS
+~~~~~~~~~~~~~~~~~~
+
+Running your application over HTTPS locally helps detect mixed content issues
+early and allows using features that require secure connections. Traditionally,
+this has been painful and complicated to set up, but the Symfony server automates
+everything for you:
+
+.. code-block:: terminal
+
+    # install the certificate authority (run this only once on your machine)
+    $ symfony server:ca:install
+
+    # now start (or restart) your server; it will use HTTPS automatically
+    $ symfony server:start
+
+.. tip::
+
+    For WSL (Windows Subsystem for Linux), the newly created local certificate
+    authority needs to be imported manually:
+
+    .. code-block:: terminal
+
+        $ explorer.exe `wslpath -w $HOME/.symfony5/certs`
+
+    In the file explorer window that just opened, double-click on the file
+    called ``default.p12``.
+
+PHP Management
+--------------
+
+The Symfony CLI provides PHP management features, allowing you to use different
+PHP versions and/or settings for different projects.
+
+Selecting PHP Version
+~~~~~~~~~~~~~~~~~~~~~
+
+If you have multiple PHP versions installed on your computer, you can tell
+Symfony which one to use creating a file called ``.php-version`` at the project
+root directory:
+
+.. code-block:: terminal
+
+    $ cd my-project/
+
+    # use a specific PHP version
+    $ echo 8.2 > .php-version
+
+    # use any PHP 8.x version available
+    $ echo 8 > .php-version
+
+To see all available PHP versions:
+
+.. code-block:: terminal
+
+    $ symfony local:php:list
+
+.. tip::
+
+    You can create a ``.php-version`` file in a parent directory to set the same
+    PHP version for multiple projects.
+
+Custom PHP Configuration
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+Override PHP settings per project by creating a ``php.ini`` file at the project
+root:
+
+.. code-block:: ini
+
+    ; php.ini
+    [Date]
+    date.timezone = Asia/Tokyo
+
+    [PHP]
+    memory_limit = 256M
+
+Using PHP Commands
+~~~~~~~~~~~~~~~~~~
+
+Use ``symfony php`` to ensure commands run with the correct PHP version:
+
+.. code-block:: terminal
+
+    # runs with the system's default PHP
+    $ php -v
+
+    # runs with the project's PHP version
+    $ symfony php -v
+
+    # this also works for Composer
+    $ symfony composer install
+
+Local Domain Names
+------------------
+
+By default, projects are accessible at a random port on the ``127.0.0.1``
+local IP. However, sometimes it is preferable to associate a domain name
+(e.g. ``my-app.wip``) with them:
+
+* it's more convenient when working continuously on the same project because
+  port numbers can change but domains don't;
+* the behavior of some applications depends on their domains/subdomains;
+* to have stable endpoints, such as the local redirection URL for OAuth2.
+
+Setting up the Local Proxy
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The Symfony CLI includes a proxy that allows using custom local domains. The
+first time you use it, you must configure it as follows:
+
+#. Open the **proxy settings** of your operating system:
+
+   * `Proxy settings in Windows`_;
+   * `Proxy settings in macOS`_;
+   * `Proxy settings in Ubuntu`_.
+
+#. Set the following URL as the value of the **Automatic Proxy Configuration**:
+
+   ``http://127.0.0.1:7080/proxy.pac``
+
+Now run this command to start the proxy:
+
+.. code-block:: terminal
+
+    $ symfony proxy:start
+
+If the proxy doesn't work as explained in the following sections, check the following:
+
+* Some browsers (e.g. Chrome) require reapplying proxy settings (clicking on
+  ``Re-apply settings`` button on the ``chrome://net-internals/#proxy`` page)
+  or a full restart after starting the proxy. Otherwise, you'll see a
+  *"This webpage is not available"* error (``ERR_NAME_NOT_RESOLVED``);
+* Some Operating Systems (e.g. macOS) don't apply proxy settings to local hosts
+  and domains by default. You may need to remove ``*.local`` and/or other
+  IP addresses from that list.
+* Windows **requires** using ``localhost`` instead of ``127.0.0.1`` when
+  configuring the automatic proxy, otherwise you won't be able to access
+  your local domain from your browser running in Windows.
+
+Defining the Local Domain
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+By default, Symfony uses ``.wip`` (for *Work in Progress*) as the local TLD for
+custom domains. You can define a local domain for your project as follows:
+
+.. code-block:: terminal
+
+   $ cd my-project/
+   $ symfony proxy:domain:attach my-app
+
+Your application is now available at ``https://my-app.wip``
+
+.. tip::
+
+    View all local domains and their configuration at http://127.0.0.1:7080
+
+You can also use wildcards:
+
+.. code-block:: terminal
+
+    $ symfony proxy:domain:attach "*.my-app"
+
+This allows accessing subdomains like ``https://api.my-app.wip`` or
+``https://admin.my-app.wip``.
+
+When running console commands, set the ``https_proxy`` environment variable
+to make custom domains work:
+
+.. code-block:: terminal
+
+    # example with cURL
+    $ https_proxy=$(symfony proxy:url) curl https://my-domain.wip
+
+    # example with Blackfire and cURL
+    $ https_proxy=$(symfony proxy:url) blackfire curl https://my-domain.wip
+
+    # example with Cypress
+    $ https_proxy=$(symfony proxy:url) ./node_modules/bin/cypress open
+
+.. warning::
+
+    Although environment variable names are typically uppercase, the ``https_proxy``
+    variable `is treated differently`_ and must be written in lowercase.
+
+.. tip::
+
+    If you prefer to use a different TLD, edit the ``~/.symfony5/proxy.json``
+    file (where ``~`` means the path to your user directory) and change the
+    value of the ``tld`` option from ``wip`` to any other TLD.
+
+.. _symfony-server-docker:
+
+Docker Integration
+------------------
+
+The Symfony CLI provides full `Docker`_ integration for projects that
+use it. To learn more about Docker and Symfony, see :doc:`docker`.
+The local server automatically detects Docker services and exposes their
+connection information as environment variables.
+
+Automatic Service Detection
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+With this ``compose.yaml``:
+
+.. code-block:: yaml
+
+    services:
+        database:
+            image: mysql:8
+            ports: [3306]
+
+The web server detects that a service exposing port ``3306`` is running for the
+project. It understands that this is a MySQL service and creates environment
+variables accordingly, using the service name (``database``) as a prefix:
+
+* ``DATABASE_URL``
+* ``DATABASE_HOST``
+* ``DATABASE_PORT``
+
+Here is a list of supported services with their ports and default Symfony prefixes:
+
+============= ========= ======================
+Service       Port      Symfony default prefix
+============= ========= ======================
+MySQL         3306      ``DATABASE_``
+PostgreSQL    5432      ``DATABASE_``
+Redis         6379      ``REDIS_``
+Memcached     11211     ``MEMCACHED_``
+RabbitMQ      5672      ``RABBITMQ_`` (set user and pass via Docker ``RABBITMQ_DEFAULT_USER`` and ``RABBITMQ_DEFAULT_PASS`` env var)
+Elasticsearch 9200      ``ELASTICSEARCH_``
+MongoDB       27017     ``MONGODB_`` (set the database via a Docker ``MONGO_DATABASE`` env var)
+Kafka         9092      ``KAFKA_``
+MailCatcher   1025/1080 ``MAILER_``
+              or 25/80
+Blackfire     8707      ``BLACKFIRE_``
+Mercure       80        Always exposes ``MERCURE_PUBLIC_URL`` and ``MERCURE_URL`` (only works with the ``dunglas/mercure`` Docker image)
+============= ========= ======================
+
+If the service is not supported, generic environment variables are set:
+``PORT``, ``IP``, and ``HOST``.
+
+You can open web management interfaces for the services that expose them
+by clicking on the links in the "Server" section of the web debug toolbar
+or by running these commands:
+
+.. code-block:: bash
+
+    $ symfony open:local:webmail
+    $ symfony open:local:rabbitmq
+
+.. tip::
+
+    To debug and list all exported environment variables, run:
+    ``symfony var:export --debug``.
+
+.. tip::
+
+    For some services, the local web server also exposes environment variables
+    understood by CLI tools related to the service. For instance, running
+    ``symfony run psql`` will connect you automatically to the PostgreSQL server
+    running in a container without having to specify the username, password, or
+    database name.
+
+When Docker services are running, browse a page of your Symfony application and
+check the "Symfony Server" section in the web debug toolbar. You'll see that
+"Docker Compose" is marked as "Up".
+
+.. note::
+
+    If you don't want environment variables to be exposed for a service, set
+    the ``com.symfony.server.service-ignore`` label to ``true``:
+
+    .. code-block:: yaml
+
+        # compose.yaml
+        services:
+            db:
+                ports: [3306]
+                labels:
+                    com.symfony.server.service-ignore: true
+
+If your Docker Compose file is not at the root of the project, use the
+``COMPOSE_FILE`` and ``COMPOSE_PROJECT_NAME`` environment variables to define
+its location, same as for ``docker-compose``:
+
+.. code-block:: bash
+
+    # start your containers:
+    COMPOSE_FILE=docker/compose.yaml COMPOSE_PROJECT_NAME=project_name docker-compose up -d
+
+    # run any Symfony CLI command:
+    COMPOSE_FILE=docker/compose.yaml COMPOSE_PROJECT_NAME=project_name symfony var:export
+
+.. note::
+
+    If you have more than one Docker Compose file, you can provide them all,
+    separated by ``:``, as explained in the `Docker Compose CLI env var reference`_.
+
+.. warning::
+
+    When using the Symfony CLI with ``php bin/console`` (``symfony console ...``),
+    it will **always** use environment variables detected via Docker, ignoring
+    any local environment variables. For example, if you set up a different database
+    name in your ``.env.test`` file (``DATABASE_URL=mysql://db_user:db_password@127.0.0.1:3306/test``)
+    and run ``symfony console doctrine:database:drop --force --env=test``,
+    the command will drop the database defined in your Docker configuration and not the "test" one.
+
+.. warning::
+
+    Similar to other web servers, this tool automatically exposes all environment
+    variables available in the CLI context. Ensure that this local server is not
+    accessible on your local network without your explicit consent, to avoid
+    potential security issues.
+
+Service Naming
+~~~~~~~~~~~~~~
+
+If your service names don't match Symfony conventions, use labels:
+
+.. code-block:: yaml
+
+    services:
+        db:
+            image: postgres:15
+            ports: [5432]
+            labels:
+                com.symfony.server.service-prefix: 'DATABASE'
+
+In this example, the service is named ``db``, so environment variables would be
+prefixed with ``DB_``, but as the ``com.symfony.server.service-prefix`` is set
+to ``DATABASE``, the web server creates environment variables starting with
+``DATABASE_`` instead as expected by the default Symfony configuration.
+
+Managing Long-Running Processes
+-------------------------------
+
+Use the ``run`` command provided by the Symfony CLI to manage long-running
+processes like Webpack watchers:
+
+.. code-block:: terminal
+
+    # start webpack watcher in the background to not block the terminal
+    $ symfony run -d npx encore dev --watch
+
+    # continue working and running other commands...
+
+    # view logs
+    $ symfony server:log
+
+    # check status
+    $ symfony server:status
+
+.. _symfony-server_configuring-workers:
+
+Configuring Workers
+~~~~~~~~~~~~~~~~~~~
+
+Define processes that should start automatically with the server in
+``.symfony.local.yaml``:
+
+.. code-block:: yaml
+
+    # .symfony.local.yaml
+    workers:
+        # Built-in Encore integration
+        npm_encore_watch: ~
+
+        # Messenger consumer with file watching
+        messenger_consume_async:
+            cmd: ['symfony', 'console', 'messenger:consume', 'async']
+            watch: ['config', 'src', 'templates', 'vendor']
+
+        # Custom commands
+        build_spa:
+            cmd: ['npm', 'run', 'watch']
+
+        # Auto-start Docker Compose
+        docker_compose: ~
+
+Advanced Configuration
+----------------------
+
+The ``.symfony.local.yaml`` file provides advanced configuration options:
+
+.. code-block:: yaml
+
+    # sets app.wip and admin.app.wip for the current project
+    proxy:
+        domains:
+            - app
+            - admin.app
+
+    # HTTP server settings
+    http:
+        document_root: public/
+        passthru: index.php
+        # forces the port that will be used to run the server
+        port: 8000
+        # sets the HTTP port you prefer for this project [default: 8000]
+        # (only will be used if it's available; otherwise a random port is chosen)
+        preferred_port: 8001
+        # used to disable the default auto-redirection from HTTP to HTTPS
+        allow_http: true
+        # force the use of HTTP instead of HTTPS
+        no_tls: false
+        # path to the file containing the TLS certificate to use in p12 format
+        p12: path/to/custom-cert.p12
+        # toggle GZIP compression
+        use_gzip: true
+        # run the server in the background
+        daemon: true
+
+.. warning::
+
+    Setting domains in this configuration file will override any domains you set
+    using the ``proxy:domain:attach`` command for the current project when you start
+    the server.
+
+.. _platform-sh-integration:
+
+Symfony Cloud Integration
+-------------------------
+
+The Symfony CLI provides seamless integration with `Symfony Cloud`_ (powered by
+`Platform.sh`_):
+
+.. code-block:: terminal
+
+    # open Platform.sh web UI
+    $ symfony cloud:web
+
+    # deploy your project to production
+    $ symfony cloud:deploy
+
+    # create a new environment
+    $ symfony cloud:env:create feature-xyz
+
+For more features, see the `Symfony Cloud documentation`_.
+
+Troubleshooting
+---------------
+
+**Server doesn't start**: Check if the port is already in use:
+
+.. code-block:: terminal
+
+    $ symfony server:status
+    $ symfony server:stop  # If a server is already running
+
+**HTTPS not working**: Ensure the CA is installed:
+
+.. code-block:: terminal
+
+    $ symfony server:ca:install
+
+**Docker services not detected**: Check that Docker is running and environment
+variables are properly exposed:
+
+.. code-block:: terminal
+
+    $ docker compose ps
+    $ symfony var:export --debug
+
+**Proxy domains not working**:
+
+* Clear your browser cache
+* Check proxy settings in your system
+* For Chrome, visit ``chrome://net-internals/#proxy`` and click "Re-apply settings"
+
+.. _`open source`: https://github.com/symfony-cli/symfony-cli
+.. _`symfony.com/download`: https://symfony.com/download
+.. _`Docker`: https://en.wikipedia.org/wiki/Docker_(software)
+.. _`Symfony Cloud`: https://symfony.com/cloud/
+.. _`Platform.sh`: https://platform.sh/
+.. _`Symfony Cloud documentation`: https://docs.platform.sh/frameworks/symfony.html
+.. _`Proxy settings in Windows`: https://www.dummies.com/computers/operating-systems/windows-10/how-to-set-up-a-proxy-in-windows-10/
+.. _`Proxy settings in macOS`: https://support.apple.com/guide/mac-help/enter-proxy-server-settings-on-mac-mchlp2591/mac
+.. _`Proxy settings in Ubuntu`: https://help.ubuntu.com/stable/ubuntu-help/net-proxy.html.en
+.. _`is treated differently`: https://superuser.com/a/1799209
+.. _`Docker Compose CLI env var reference`: https://docs.docker.com/compose/reference/envvars/
diff --git a/setup/symfony_server.rst b/setup/symfony_server.rst
deleted file mode 100644
index 2ea4da543fe..00000000000
--- a/setup/symfony_server.rst
+++ /dev/null
@@ -1,563 +0,0 @@
-Symfony Local Web Server
-========================
-
-You can run Symfony applications with any web server (Apache, nginx, the
-internal PHP web server, etc.). However, Symfony provides its own web server to
-make you more productive while developing your applications.
-
-Although this server is not intended for production use, it supports HTTP/2,
-TLS/SSL, automatic generation of security certificates, local domains, and many
-other features that sooner or later you'll need when developing web projects.
-Moreover, the server is not tied to Symfony and you can also use it with any
-PHP application and even with HTML or single page applications.
-
-Installation
-------------
-
-The Symfony server is part of the ``symfony`` binary created when you
-`install Symfony`_ and has support for Linux, macOS and Windows.
-
-.. tip::
-
-    The Symfony CLI supports auto completion for Bash, Zsh, or Fish shells. You
-    have to install the completion script *once*. Run ``symfony completion
-    --help`` for the installation instructions for your shell. After installing
-    and restarting your terminal, you're all set to use completion (by default,
-    by pressing the Tab key).
-
-    The Symfony CLI will also provide completion for the ``composer`` command
-    and for the ``console`` command if it detects a Symfony project.
-
-.. note::
-
-   You can view and contribute to the Symfony CLI source in the
-   `symfony-cli/symfony-cli GitHub repository`_.
-
-Getting Started
----------------
-
-The Symfony server is started once per project, so you may end up with several
-instances (each of them listening to a different port). This is the common
-workflow to serve a Symfony project:
-
-.. code-block:: terminal
-
-    $ cd my-project/
-    $ symfony server:start
-
-      [OK] Web server listening on http://127.0.0.1:....
-      ...
-
-    # Now, browse the given URL, or run this command:
-    $ symfony open:local
-
-Running the server this way makes it display the log messages in the console, so
-you won't be able to run other commands at the same time. If you prefer, you can
-run the Symfony server in the background:
-
-.. code-block:: terminal
-
-    $ cd my-project/
-
-    # start the server in the background
-    $ symfony server:start -d
-
-    # continue working and running other commands...
-
-    # show the latest log messages
-    $ symfony server:log
-
-.. tip::
-
-    On macOS, when starting the Symfony server you might see a warning dialog asking
-    *"Do you want the application to accept incoming network connections?"*.
-    This happens when running unsigned applications that are not listed in the
-    firewall list. The solution is to run this command that signs the Symfony binary:
-
-    .. code-block:: terminal
-
-        $ sudo codesign --force --deep --sign - $(whereis -q symfony)
-
-Enabling PHP-FPM
-----------------
-
-.. note::
-
-    PHP-FPM must be installed locally for the Symfony server to utilize.
-
-When the server starts, it checks for ``web/index_dev.php``, ``web/index.php``,
-``public/app_dev.php``, ``public/app.php`` in that order. If one is found, the
-server will automatically start with PHP-FPM enabled. Otherwise the server will
-start without PHP-FPM and will show a ``Page not found`` page when trying to
-access a ``.php`` file in the browser.
-
-.. tip::
-
-    When an ``index.html`` and a front controller like e.g. ``index.php`` are
-    both present the server will still start with PHP-FPM enabled but the
-    ``index.html`` will take precedence over the front controller. This means
-    when an ``index.html`` file is present in ``public`` or ``web``, it will be
-    displayed instead of the ``index.php`` which would show e.g. the Symfony
-    application.
-
-Enabling TLS
-------------
-
-Browsing the secure version of your applications locally is important to detect
-problems with mixed content early, and to run libraries that only run in HTTPS.
-Traditionally this has been painful and complicated to set up, but the Symfony
-server automates everything. First, run this command:
-
-.. code-block:: terminal
-
-    $ symfony server:ca:install
-
-This command creates a local certificate authority, registers it in your system
-trust store, registers it in Firefox (this is required only for that browser)
-and creates a default certificate for ``localhost`` and ``127.0.0.1``. In other
-words, it does everything for you.
-
-.. tip::
-
-    If you are doing this in WSL (Windows Subsystem for Linux), the newly created
-    local certificate authority needs to be manually imported in Windows. The file
-    is located in ``wsl`` at ``~/.symfony5/certs/default.p12``. The easiest way to
-    do so is to run the following command from ``wsl``:
-
-    .. code-block:: terminal
-
-        $ explorer.exe `wslpath -w $HOME/.symfony5/certs`
-
-    In the file explorer window that just opened, double-click on the file
-    called ``default.p12``.
-
-Before browsing your local application with HTTPS instead of HTTP, restart its
-server stopping and starting it again.
-
-Different PHP Settings Per Project
-----------------------------------
-
-Selecting a Different PHP Version
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-If you have multiple PHP versions installed on your computer, you can tell
-Symfony which one to use creating a file called ``.php-version`` at the project
-root directory:
-
-.. code-block:: terminal
-
-    $ cd my-project/
-
-    # use a specific PHP version
-    $ echo 7.4 > .php-version
-
-    # use any PHP 8.x version available
-    $ echo 8 > .php-version
-
-.. tip::
-
-    The Symfony server traverses the directory structure up to the root
-    directory, so you can create a ``.php-version`` file in some parent
-    directory to set the same PHP version for a group of projects under that
-    directory.
-
-Run the command below if you don't remember all the PHP versions installed on your
-computer:
-
-.. code-block:: terminal
-
-    $ symfony local:php:list
-
-      # You'll see all supported SAPIs (CGI, FastCGI, etc.) for each version.
-      # FastCGI (php-fpm) is used when possible; then CGI (which acts as a FastCGI
-      # server as well), and finally, the server falls back to plain CGI.
-
-Overriding PHP Config Options Per Project
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-You can change the value of any PHP runtime config option per project by creating a
-file called ``php.ini`` at the project root directory. Add only the options you want
-to override:
-
-.. code-block:: terminal
-
-    $ cd my-project/
-
-    # this project only overrides the default PHP timezone
-    $ cat php.ini
-    [Date]
-    date.timezone = Asia/Tokyo
-
-Running Commands with Different PHP Versions
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-When running different PHP versions, it is useful to use the main ``symfony``
-command as a wrapper for the ``php`` command. This allows you to always select
-the most appropriate PHP version according to the project which is running the
-commands. It also loads the env vars automatically, which is important when
-running non-Symfony commands:
-
-.. code-block:: terminal
-
-    # runs the command with the default PHP version
-    $ php -r "..."
-
-    # runs the command with the PHP version selected by the project
-    # (or the default PHP version if the project didn't select one)
-    $ symfony php -r "..."
-
-Local Domain Names
-------------------
-
-By default, projects are accessible at some random port of the ``127.0.0.1``
-local IP. However, sometimes it is preferable to associate a domain name to them:
-
-* It's more convenient when you work continuously on the same project because
-  port numbers can change but domains don't;
-* The behavior of some applications depend on their domains/subdomains;
-* To have stable endpoints, such as the local redirection URL for OAuth2.
-
-Setting up the Local Proxy
-~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Local domains are possible thanks to a local proxy provided by the Symfony server.
-If this is the first time you run the proxy, you must configure it as follows:
-
-#. Open the **proxy settings** of your operating system:
-
-   * `Proxy settings in Windows`_;
-   * `Proxy settings in macOS`_;
-   * `Proxy settings in Ubuntu`_.
-
-#. Set the following URL as the value of the **Automatic Proxy Configuration**:
-
-   ``http://127.0.0.1:7080/proxy.pac``
-
-Now run this command to start the proxy:
-
-.. code-block:: terminal
-
-    $ symfony proxy:start
-
-If the proxy doesn't work as explained in the following sections, check these:
-
-* Some browsers (e.g. Chrome) require to re-apply proxy settings (clicking on
-  ``Re-apply settings`` button on the ``chrome://net-internals/#proxy`` page)
-  or a full restart after starting the proxy. Otherwise, you'll see a
-  *"This webpage is not available"* error (``ERR_NAME_NOT_RESOLVED``);
-* Some Operating Systems (e.g. macOS) don't apply by default the proxy settings
-  to local hosts and domains. You may need to remove ``*.local`` and/or other
-  IP addresses from that list.
-* Windows Operating System **requires** ``localhost`` instead of ``127.0.0.1``
-  when configuring the automatic proxy, otherwise you won't be able to access
-  your local domain from your browser running in Windows.
-
-Defining the Local Domain
-~~~~~~~~~~~~~~~~~~~~~~~~~
-
-By default, Symfony proposes ``.wip`` (for *Work in Progress*) for the local
-domains. You can define a local domain for your project as follows:
-
-.. code-block:: terminal
-
-    $ cd my-project/
-    $ symfony proxy:domain:attach my-domain
-
-If you have installed the local proxy as explained in the previous section, you
-can now browse ``https://my-domain.wip`` to access your local project with the
-new custom domain.
-
-.. tip::
-
-    Browse the http://127.0.0.1:7080 URL to get the full list of local project
-    directories, their custom domains, and port numbers.
-
-You can also add a wildcard domain:
-
-.. code-block:: terminal
-
-    $ symfony proxy:domain:attach "*.my-domain"
-
-So it will match all subdomains like ``https://admin.my-domain.wip``, ``https://other.my-domain.wip``...
-
-When running console commands, add the ``https_proxy`` env var to make custom
-domains work:
-
-.. code-block:: terminal
-
-    # Example with curl
-    $ https_proxy=$(symfony proxy:url) curl https://my-domain.wip
-
-    # Example with Blackfire and curl
-    $ https_proxy=$(symfony proxy:url) blackfire curl https://my-domain.wip
-
-    # Example with Cypress
-    $ https_proxy=$(symfony proxy:url) ./node_modules/bin/cypress open
-
-.. warning::
-
-    Although env var names are always defined in uppercase, the ``https_proxy``
-    env var `is treated differently`_ than other env vars and its name must be
-    spelled in lowercase.
-
-.. tip::
-
-    If you prefer to use a different TLD, edit the ``~/.symfony5/proxy.json``
-    file (where ``~`` means the path to your user directory) and change the
-    value of the ``tld`` option from ``wip`` to any other TLD.
-
-Long-Running Commands
----------------------
-
-Long-running commands, such as the ones that compile front-end web assets, block
-the terminal and you can't run other commands at the same time. The Symfony
-server provides a ``run`` command to wrap them as follows:
-
-.. code-block:: terminal
-
-    # compile Webpack assets using Symfony Encore ... but do that in the
-    # background to not block the terminal
-    $ symfony run -d npx encore dev --watch
-
-    # continue working and running other commands...
-
-    # from time to time, check the command logs if you want
-    $ symfony server:log
-
-    # and you can also check if the command is still running
-    $ symfony server:status
-    Web server listening on ...
-    Command "npx ..." running with PID ...
-
-    # stop the web server (and all the associated commands) when you are finished
-    $ symfony server:stop
-
-Configuration file
-------------------
-
-There are several options that you can set using a ``.symfony.local.yaml`` config file:
-
-.. code-block:: yaml
-
-    # Sets domain1.wip and domain2.wip for the current project
-    proxy:
-        domains:
-            - domain1
-            - domain2
-
-    http:
-        document_root: public/ # Path to the project document root
-        passthru: index.php # Project passthru index
-        port: 8000 # Force the port that will be used to run the server
-        preferred_port: 8001 # Preferred HTTP port [default: 8000]
-        p12: path/to/p12_cert # Name of the file containing the TLS certificate to use in p12 format
-        allow_http: true # Prevent auto-redirection from HTTP to HTTPS
-        no_tls: true # Use HTTP instead of HTTPS
-        daemon: true # Run the server in the background
-        use_gzip: true # Toggle GZIP compression
-        no_workers: true # Do not start workers
-
-.. warning::
-
-    Setting domains in this configuration file will override any domains you set
-    using the ``proxy:domain:attach`` command for the current project when you start
-    the server.
-
-.. _symfony-server_configuring-workers:
-
-Configuring Workers
-~~~~~~~~~~~~~~~~~~~
-
-If you like some processes to start automatically, along with the webserver
-(``symfony server:start``), you can set them in the YAML configuration file:
-
-.. code-block:: yaml
-
-    # .symfony.local.yaml
-    workers:
-        # built-in command that builds and watches front-end assets
-        # npm_encore_watch:
-        #     cmd: ['npx', 'encore', 'dev', '--watch']
-        npm_encore_watch: ~
-
-        # built-in command that starts messenger consumer
-        # messenger_consume_async:
-        #     cmd: ['symfony', 'console', 'messenger:consume', 'async']
-        #     watch: ['config', 'src', 'templates', 'vendor']
-        messenger_consume_async: ~
-
-        # you can also add your own custom commands
-        build_spa:
-            cmd: ['npm', '--cwd', './spa/', 'dev']
-
-        # auto start Docker compose when starting server (available since Symfony CLI 5.7.0)
-        docker_compose: ~
-
-.. tip::
-
-    You may want to not start workers on some environments like CI. You can use the
-    ``--no-workers`` option to start the server without starting workers.
-
-.. _symfony-server-docker:
-
-Docker Integration
-------------------
-
-The local Symfony server provides full `Docker`_ integration for projects that
-use it. To learn more about Docker & Symfony, see :doc:`docker`.
-
-When the web server detects that Docker Compose is running for the project, it
-automatically exposes some environment variables.
-
-Via the ``docker-compose`` API, it looks for exposed ports used for common
-services. When it detects one it knows about, it uses the service name to
-expose environment variables.
-
-Consider the following configuration:
-
-.. code-block:: yaml
-
-    # compose.yaml
-    services:
-        database:
-            ports: [3306]
-
-The web server detects that a service exposing port ``3306`` is running for the
-project. It understands that this is a MySQL service and creates environment
-variables accordingly with the service name (``database``) as a prefix:
-``DATABASE_URL``, ``DATABASE_HOST``, ...
-
-If the service is not in the supported list below, generic environment
-variables are set: ``PORT``, ``IP``, and ``HOST``.
-
-If the ``compose.yaml`` names do not match Symfony's conventions, add a
-label to override the environment variables prefix:
-
-.. code-block:: yaml
-
-    # compose.yaml
-    services:
-        db:
-            ports: [3306]
-            labels:
-                com.symfony.server.service-prefix: 'DATABASE'
-
-In this example, the service is named ``db``, so environment variables would be
-prefixed with ``DB_``, but as the ``com.symfony.server.service-prefix`` is set
-to ``DATABASE``, the web server creates environment variables starting with
-``DATABASE_`` instead as expected by the default Symfony configuration.
-
-Here is the list of supported services with their ports and default Symfony
-prefixes:
-
-============= ========= ======================
-Service       Port      Symfony default prefix
-============= ========= ======================
-MySQL         3306      ``DATABASE_``
-PostgreSQL    5432      ``DATABASE_``
-Redis         6379      ``REDIS_``
-Memcached     11211     ``MEMCACHED_``
-RabbitMQ      5672      ``RABBITMQ_`` (set user and pass via Docker ``RABBITMQ_DEFAULT_USER`` and ``RABBITMQ_DEFAULT_PASS`` env var)
-Elasticsearch 9200      ``ELASTICSEARCH_``
-MongoDB       27017     ``MONGODB_`` (set the database via a Docker ``MONGO_DATABASE`` env var)
-Kafka         9092      ``KAFKA_``
-MailCatcher   1025/1080 ``MAILER_``
-              or 25/80
-Blackfire     8707      ``BLACKFIRE_``
-Mercure       80        Always exposes ``MERCURE_PUBLIC_URL`` and ``MERCURE_URL`` (only works with the ``dunglas/mercure`` Docker image)
-============= ========= ======================
-
-You can open web management interfaces for the services that expose them:
-
-.. code-block:: bash
-
-    $ symfony open:local:webmail
-    $ symfony open:local:rabbitmq
-
-Or click on the links in the "Server" section of the web debug toolbar.
-
-.. tip::
-
-    To debug and list all exported environment variables, run ``symfony
-    var:export --debug``.
-
-.. tip::
-
-    For some services, the web server also exposes environment variables
-    understood by CLI tools related to the service. For instance, running
-    ``symfony run psql`` will connect you automatically to the PostgreSQL server
-    running in a container without having to specify the username, password, or
-    database name.
-
-When Docker services are running, browse a page of your Symfony application and
-check the "Symfony Server" section in the web debug toolbar; you'll see that
-"Docker Compose" is "Up".
-
-.. note::
-
-    If you don't want environment variables to be exposed for a service, set
-    the ``com.symfony.server.service-ignore`` label to ``true``:
-
-    .. code-block:: yaml
-
-        # compose.yaml
-        services:
-            db:
-                ports: [3306]
-                labels:
-                    com.symfony.server.service-ignore: true
-
-If your Docker Compose file is not at the root of the project, use the
-``COMPOSE_FILE`` and ``COMPOSE_PROJECT_NAME`` environment variables to define
-its location, same as for ``docker-compose``:
-
-.. code-block:: bash
-
-    # start your containers:
-    COMPOSE_FILE=docker/compose.yaml COMPOSE_PROJECT_NAME=project_name docker-compose up -d
-
-    # run any Symfony CLI command:
-    COMPOSE_FILE=docker/compose.yaml COMPOSE_PROJECT_NAME=project_name symfony var:export
-
-.. note::
-
-    If you have more than one Docker Compose file, you can provide them all
-    separated by ``:`` as explained in the `Docker compose CLI env var reference`_.
-
-.. warning::
-
-    When using the Symfony binary with ``php bin/console`` (``symfony console ...``),
-    the binary will **always** use environment variables detected via Docker and will
-    ignore local environment variables.
-    For example if you set up a different database name in your ``.env.test`` file
-    (``DATABASE_URL=mysql://db_user:db_password@127.0.0.1:3306/test``) and if you run
-    ``symfony console doctrine:database:drop --force --env=test``, the command will drop the database
-    defined in your Docker configuration and not the "test" one.
-
-.. warning::
-
-    Similar to other web servers, this tool automatically exposes all environment
-    variables available in the CLI context. Ensure that this local server is not
-    accessible on your local network without consent to avoid security issues.
-
-Platform.sh Integration
------------------------
-
-The local Symfony server provides full, but optional, integration with
-`Platform.sh`_, a service optimized to run your Symfony applications on the
-cloud. It provides features such as creating environments, backups/snapshots,
-and even access to a copy of the production data from your local machine to
-help debug any issues.
-
-`Read Platform.sh for Symfony technical docs`_.
-
-.. _`install Symfony`: https://symfony.com/download
-.. _`symfony-cli/symfony-cli GitHub repository`: https://github.com/symfony-cli/symfony-cli
-.. _`Docker`: https://en.wikipedia.org/wiki/Docker_(software)
-.. _`Platform.sh`: https://symfony.com/cloud/
-.. _`Read Platform.sh for Symfony technical docs`: https://symfony.com/doc/current/cloud/index.html
-.. _`Proxy settings in Windows`: https://www.dummies.com/computers/operating-systems/windows-10/how-to-set-up-a-proxy-in-windows-10/
-.. _`Proxy settings in macOS`: https://support.apple.com/guide/mac-help/enter-proxy-server-settings-on-mac-mchlp2591/mac
-.. _`Proxy settings in Ubuntu`: https://help.ubuntu.com/stable/ubuntu-help/net-proxy.html.en
-.. _`is treated differently`: https://superuser.com/a/1799209
-.. _`Docker compose CLI env var reference`: https://docs.docker.com/compose/reference/envvars/
diff --git a/setup/web_server_configuration.rst b/setup/web_server_configuration.rst
index 58935bf5352..4b562d4f79e 100644
--- a/setup/web_server_configuration.rst
+++ b/setup/web_server_configuration.rst
@@ -2,7 +2,7 @@ Configuring a Web Server
 ========================
 
 The preferred way to develop your Symfony application is to use
-:doc:`Symfony Local Web Server </setup/symfony_server>`.
+:ref:`Symfony local web server <symfony-cli-server>`.
 
 However, when running the application in the production environment, you'll need
 to use a fully-featured web server. This article describes how to use Symfony
@@ -178,24 +178,35 @@ directive to pass requests for PHP files to PHP FPM:
         #     Options FollowSymlinks
         # </Directory>
 
+        # optionally disable the fallback resource for the asset directories
+        # which will allow Apache to return a 404 error when files are
+        # not found instead of passing the request to Symfony
+        # <Directory /var/www/project/public/bundles>
+        #     DirectoryIndex disabled
+        #     FallbackResource disabled
+        # </Directory>
+
         ErrorLog /var/log/apache2/project_error.log
         CustomLog /var/log/apache2/project_access.log combined
     </VirtualHost>
 
 .. note::
 
-    If you are doing some quick tests with Apache, you can also run
-    ``composer require symfony/apache-pack``. This package creates an ``.htaccess``
-    file in the ``public/`` directory with the necessary rewrite rules needed to serve
-    the Symfony application. However, in production, it's recommended to move these
-    rules to the main Apache configuration file (as shown above) to improve performance.
+    If you're running some quick tests with Apache, you can run
+    ``composer require symfony/apache-pack`` to create an ``.htaccess`` file in
+    the ``public/`` directory with the rewrite rules needed to serve the Symfony
+    application. Make sure Apache's ``AllowOverride`` setting is set to ``All``
+    for that directory; otherwise, the ``.htaccess`` file will be ignored.
+
+    In production, however, it's recommended to move these rules to the main
+    Apache configuration file (as shown above) to improve performance.
 
 Caddy
 -----
 
 When using Caddy on the server, you can use a configuration like this:
 
-.. code-block:: text
+.. code-block:: nginx
 
     # /etc/caddy/Caddyfile
     example.com, www.example.com {
diff --git a/string.rst b/string.rst
index 667dcd06010..e51e7d1b502 100644
--- a/string.rst
+++ b/string.rst
@@ -232,14 +232,26 @@ Methods to Change Case
     u('Foo: Bar-baz.')->camel(); // 'fooBarBaz'
     // changes all graphemes/code points to snake_case
     u('Foo: Bar-baz.')->snake(); // 'foo_bar_baz'
-    // other cases can be achieved by chaining methods. E.g. PascalCase:
-    u('Foo: Bar-baz.')->camel()->title(); // 'FooBarBaz'
+    // changes all graphemes/code points to kebab-case
+    u('Foo: Bar-baz.')->kebab(); // 'foo-bar-baz'
+    // changes all graphemes/code points to PascalCase
+    u('Foo: Bar-baz.')->pascal(); // 'FooBarBaz'
+    // other cases can be achieved by chaining methods, e.g. :
+    u('Foo: Bar-baz.')->camel()->upper(); // 'FOOBARBAZ'
 
 .. versionadded:: 7.1
 
     The ``localeLower()``, ``localeUpper()`` and ``localeTitle()`` methods were
     introduced in Symfony 7.1.
 
+.. versionadded:: 7.2
+
+    The ``kebab()`` method was introduced in Symfony 7.2.
+
+.. versionadded:: 7.3
+
+    The ``pascal()`` method was introduced in Symfony 7.3.
+
 The methods of all string classes are case-sensitive by default. You can perform
 case-insensitive operations with the ``ignoreCase()`` method::
 
@@ -395,10 +407,19 @@ Methods to Join, Split, Truncate and Reverse
     u('Lorem Ipsum')->truncate(80);            // 'Lorem Ipsum'
     // the second argument is the character(s) added when a string is cut
     // (the total length includes the length of this character(s))
-    u('Lorem Ipsum')->truncate(8, '…');             // 'Lorem I…'
-    // if the third argument is false, the last word before the cut is kept
-    // even if that generates a string longer than the desired length
-    u('Lorem Ipsum')->truncate(8, '…', cut: false); // 'Lorem Ipsum'
+    // (note that '…' is a single character that includes three dots; it's not '...')
+    u('Lorem Ipsum')->truncate(8, '…');        // 'Lorem I…'
+    // the third optional argument defines how to cut words when the length is exceeded
+    // the default value is TruncateMode::Char which cuts the string at the exact given length
+    u('Lorem ipsum dolor sit amet')->truncate(8, cut: TruncateMode::Char);       // 'Lorem ip'
+    // returns up to the last complete word that fits in the given length without surpassing it
+    u('Lorem ipsum dolor sit amet')->truncate(8, cut: TruncateMode::WordBefore); // 'Lorem'
+    // returns up to the last complete word that fits in the given length, surpassing it if needed
+    u('Lorem ipsum dolor sit amet')->truncate(8, cut: TruncateMode::WordAfter);   // 'Lorem ipsum'
+
+.. versionadded:: 7.2
+
+    The ``TruncateMode`` parameter for truncate function was introduced in Symfony 7.2.
 
 ::
 
@@ -645,11 +666,28 @@ class to convert English words from/to singular/plural with confidence::
 The value returned by both methods is always an array because sometimes it's not
 possible to determine a unique singular/plural form for the given word.
 
+Symfony also provides inflectors for other languages::
+
+    use Symfony\Component\String\Inflector\FrenchInflector;
+
+    $inflector = new FrenchInflector();
+    $result = $inflector->singularize('souris'); // ['souris']
+    $result = $inflector->pluralize('hôpital');  // ['hôpitaux']
+
+    use Symfony\Component\String\Inflector\SpanishInflector;
+
+    $inflector = new SpanishInflector();
+    $result = $inflector->singularize('aviones'); // ['avión']
+    $result = $inflector->pluralize('miércoles'); // ['miércoles']
+
+.. versionadded:: 7.2
+
+    The ``SpanishInflector`` class was introduced in Symfony 7.2.
+
 .. note::
 
-    Symfony also provides a :class:`Symfony\\Component\\String\\Inflector\\FrenchInflector`
-    and an :class:`Symfony\\Component\\String\\Inflector\\InflectorInterface` if
-    you need to implement your own inflector.
+    Symfony provides an :class:`Symfony\\Component\\String\\Inflector\\InflectorInterface`
+    in case you need to implement your own inflector.
 
 .. _`ASCII`: https://en.wikipedia.org/wiki/ASCII
 .. _`Unicode`: https://en.wikipedia.org/wiki/Unicode
diff --git a/templates.rst b/templates.rst
index 5815dbb56c4..fc353384202 100644
--- a/templates.rst
+++ b/templates.rst
@@ -304,35 +304,33 @@ You can now use the ``asset()`` function:
 .. code-block:: html+twig
 
     {# the image lives at "public/images/logo.png" #}
-    <img src="{{ asset('images/logo.png') }}" alt="Symfony!"/>
+    <img src="{{ asset('images/logo.png') }}" alt="Symfony!">
 
     {# the CSS file lives at "public/css/blog.css" #}
-    <link href="{{ asset('css/blog.css') }}" rel="stylesheet"/>
+    <link href="{{ asset('css/blog.css') }}" rel="stylesheet">
 
     {# the JS file lives at "public/bundles/acme/js/loader.js" #}
     <script src="{{ asset('bundles/acme/js/loader.js') }}"></script>
 
-The ``asset()`` function's main purpose is to make your application more portable.
-If your application lives at the root of your host (e.g. ``https://example.com``),
-then the rendered path should be ``/images/logo.png``. But if your application
-lives in a subdirectory (e.g. ``https://example.com/my_app``), each asset path
-should render with the subdirectory (e.g. ``/my_app/images/logo.png``). The
-``asset()`` function takes care of this by determining how your application is
-being used and generating the correct paths accordingly.
+Using the ``asset()`` function is recommended for these reasons:
 
-.. tip::
+* **Asset versioning**: ``asset()`` appends a version hash to asset URLs for
+  cache busting. This works both via :doc:`AssetMapper </frontend>` and the
+  :doc:`Asset component </components/asset>` (see also the
+  :ref:`assets configuration options <reference-assets>`, such as ``version``
+  and ``version_format``).
 
-    The ``asset()`` function supports various cache busting techniques via the
-    :ref:`version <reference-framework-assets-version>`,
-    :ref:`version_format <reference-assets-version-format>`, and
-    :ref:`json_manifest_path <reference-assets-json-manifest-path>` configuration options.
+* **Application portability**: whether your app is hosted at the root
+  (e.g. ``https://example.com``) or in a subdirectory (e.g. ``https://example.com/my_app``),
+  ``asset()`` generates the correct path (e.g. ``/images/logo.png`` vs ``/my_app/images/logo.png``)
+  automatically based on your app's base URL.
 
 If you need absolute URLs for assets, use the ``absolute_url()`` Twig function
 as follows:
 
 .. code-block:: html+twig
 
-    <img src="{{ absolute_url(asset('images/logo.png')) }}" alt="Symfony!"/>
+    <img src="{{ absolute_url(asset('images/logo.png')) }}" alt="Symfony!">
 
     <link rel="shortcut icon" href="{{ absolute_url('favicon.png') }}">
 
@@ -497,8 +495,8 @@ in container parameters <service-container-parameters>`:
     .. code-block:: php
 
         // config/packages/twig.php
-        use function Symfony\Component\DependencyInjection\Loader\Configurator\service;
         use Symfony\Config\TwigConfig;
+        use function Symfony\Component\DependencyInjection\Loader\Configurator\service;
 
         return static function (TwigConfig $twig): void {
             // ...
@@ -642,6 +640,31 @@ This might come handy when dealing with blocks in
 :ref:`templates inheritance <template_inheritance-layouts>` or when using
 `Turbo Streams`_.
 
+Similarly, you can use the ``#[Template]`` attribute on the controller to specify
+a block to render::
+
+    // src/Controller/ProductController.php
+    namespace App\Controller;
+
+    use Symfony\Bridge\Twig\Attribute\Template;
+    use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+    use Symfony\Component\HttpFoundation\Response;
+
+    class ProductController extends AbstractController
+    {
+        #[Template('product.html.twig', block: 'price_block')]
+        public function price(): array
+        {
+            return [
+                // ...
+            ];
+        }
+    }
+
+.. versionadded:: 7.2
+
+    The ``#[Template]`` attribute's ``block`` argument was introduced in Symfony 7.2.
+
 Rendering a Template in Services
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -715,6 +738,11 @@ provided by Symfony:
                     site_name: 'ACME'
                     theme: 'dark'
 
+                # optionally you can define HTTP headers to add to the response
+                headers:
+                    Content-Type: 'text/html'
+                    foo: 'bar'
+
     .. code-block:: xml
 
         <!-- config/routes.xml -->
@@ -744,6 +772,11 @@ provided by Symfony:
                     <default key="site_name">ACME</default>
                     <default key="theme">dark</default>
                 </default>
+
+                <!-- optionally you can define HTTP headers to add to the response -->
+                <default key="headers">
+                    <default key="Content-Type">text/html</default>
+                </default>
             </route>
         </routes>
 
@@ -774,11 +807,20 @@ provided by Symfony:
                     'context' => [
                         'site_name' => 'ACME',
                         'theme' => 'dark',
+                        ],
+
+                    // optionally you can define HTTP headers to add to the response
+                    'headers' => [
+                        'Content-Type' => 'text/html',
                     ]
                 ])
             ;
         };
 
+.. versionadded:: 7.2
+
+    The ``headers`` option was introduced in Symfony 7.2.
+
 Checking if a Template Exists
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -835,6 +877,11 @@ errors. It's useful to run it before deploying your application to production
 
     The option to exclude directories was introduced in Symfony 7.1.
 
+.. versionadded:: 7.3
+
+    Before Symfony 7.3, the ``--show-deprecations`` option only displayed the
+    first deprecation found, so you had to run the command repeatedly.
+
 When running the linter inside `GitHub Actions`_, the output is automatically
 adapted to the format required by GitHub, but you can force that format too:
 
@@ -924,7 +971,7 @@ following code to display the user information is repeated in several places:
 
     {# ... #}
     <div class="user-profile">
-        <img src="{{ user.profileImageUrl }}" alt="{{ user.fullName }}"/>
+        <img src="{{ user.profileImageUrl }}" alt="{{ user.fullName }}">
         <p>{{ user.fullName }} - {{ user.email }}</p>
     </div>
 
@@ -1209,7 +1256,7 @@ In practice, the ``base.html.twig`` template would look like this:
             <meta charset="UTF-8">
             <title>{% block title %}My Application{% endblock %}</title>
             {% block stylesheets %}
-                <link rel="stylesheet" type="text/css" href="/css/base.css"/>
+                <link rel="stylesheet" type="text/css" href="/css/base.css">
             {% endblock %}
         </head>
         <body>
@@ -1464,8 +1511,8 @@ Bundle Templates
 ~~~~~~~~~~~~~~~~
 
 If you :ref:`install packages/bundles <symfony-flex>` in your application, they
-may include their own Twig templates (in the ``Resources/views/`` directory of
-each bundle). To avoid messing with your own templates, Symfony adds bundle
+may include their own Twig templates (in the ``templates/`` directory of each
+bundle). To avoid messing with your own templates, Symfony adds bundle
 templates under an automatic namespace created after the bundle name.
 
 For example, the templates of a bundle called ``AcmeBlogBundle`` are available
@@ -1504,23 +1551,20 @@ as currency:
     {# pass in the 3 optional arguments #}
     {{ product.price|price(2, ',', '.') }}
 
-Create a class that extends ``AbstractExtension`` and fill in the logic::
+.. _templates-twig-filter-attribute:
+
+Create a regular PHP class with a method that contains the filter logic. Then,
+add the ``#[AsTwigFilter]`` attribute to define the name and options of
+the Twig filter::
 
     // src/Twig/AppExtension.php
     namespace App\Twig;
 
-    use Twig\Extension\AbstractExtension;
-    use Twig\TwigFilter;
+    use Twig\Attribute\AsTwigFilter;
 
-    class AppExtension extends AbstractExtension
+    class AppExtension
     {
-        public function getFilters(): array
-        {
-            return [
-                new TwigFilter('price', [$this, 'formatPrice']),
-            ];
-        }
-
+        #[AsTwigFilter('price')]
         public function formatPrice(float $number, int $decimals = 0, string $decPoint = '.', string $thousandsSep = ','): string
         {
             $price = number_format($number, $decimals, $decPoint, $thousandsSep);
@@ -1530,24 +1574,19 @@ Create a class that extends ``AbstractExtension`` and fill in the logic::
         }
     }
 
-If you want to create a function instead of a filter, define the
-``getFunctions()`` method::
+.. _templates-twig-function-attribute:
+
+If you want to create a function instead of a filter, use the
+``#[AsTwigFunction]`` attribute::
 
     // src/Twig/AppExtension.php
     namespace App\Twig;
 
-    use Twig\Extension\AbstractExtension;
-    use Twig\TwigFunction;
+    use Twig\Attribute\AsTwigFunction;
 
-    class AppExtension extends AbstractExtension
+    class AppExtension
     {
-        public function getFunctions(): array
-        {
-            return [
-                new TwigFunction('area', [$this, 'calculateArea']),
-            ];
-        }
-
+        #[AsTwigFunction('area')]
         public function calculateArea(int $width, int $length): int
         {
             return $width * $length;
@@ -1559,6 +1598,18 @@ If you want to create a function instead of a filter, define the
     Along with custom filters and functions, you can also register
     `global variables`_.
 
+.. versionadded:: 7.3
+
+    Support for the ``#[AsTwigFilter]``, ``#[AsTwigFunction]`` and ``#[AsTwigTest]``
+    attributes was introduced in Symfony 7.3. Previously, you had to extend the
+    ``AbstractExtension`` class, and override the ``getFilters()`` and ``getFunctions()``
+    methods.
+
+If you're using the :ref:`default services.yaml configuration <service-container-services-load-example>`,
+the :ref:`service autoconfiguration <services-autoconfigure>` feature will enable
+this class as a Twig extension. Otherwise, you need to define a service manually
+and :doc:`tag it </service_container/tags>` with the ``twig.attribute_extension`` tag.
+
 Register an Extension as a Service
 ..................................
 
@@ -1582,10 +1633,11 @@ this command to confirm that your new filter was successfully registered:
 Creating Lazy-Loaded Twig Extensions
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-Including the code of the custom filters/functions in the Twig extension class
-is the simplest way to create extensions. However, Twig must initialize all
-extensions before rendering any template, even if the template doesn't use an
-extension.
+When :ref:`using attributes to extend Twig <templates-twig-filter-attribute>`,
+the **Twig extensions are already lazy-loaded** and you don't have to do anything
+else. However, if your Twig extensions follow the **legacy approach** of extending
+the ``AbstractExtension`` class, Twig initializes all the extensions before
+rendering any template, even if they are not used.
 
 If extensions don't define dependencies (i.e. if you don't inject services in
 them) performance is not affected. However, if extensions define lots of complex
diff --git a/testing.rst b/testing.rst
index 30c0e87ab77..2174e7cf874 100644
--- a/testing.rst
+++ b/testing.rst
@@ -5,14 +5,38 @@ Whenever you write a new line of code, you also potentially add new bugs.
 To build better and more reliable applications, you should test your code
 using both functional and unit tests.
 
-.. _testing-installation:
+Symfony integrates with an independent library called `PHPUnit`_ to give you a
+rich testing framework. This article covers the PHPUnit basics you'll need to
+write Symfony tests. To learn everything about PHPUnit and its features, read
+the `official PHPUnit documentation`_.
+
+Types of Tests
+--------------
+
+There are many types of automated tests and precise definitions often
+differ from project to project. In Symfony, the following definitions are
+used. If you have learned something different, that is not necessarily
+wrong, merely different from what the Symfony documentation is using.
+
+`Unit Tests`_
+    These tests ensure that *individual* units of source code (e.g. a single
+    class) behave as intended.
 
-The PHPUnit Testing Framework
------------------------------
+`Integration Tests`_
+    These tests test a combination of classes and commonly interact with
+    Symfony's service container. These tests do not yet cover the fully
+    working application, those are called *Application tests*.
+
+`Application Tests`_
+    Application tests (also known as functional tests) test the behavior of a
+    complete application. They make HTTP requests (both real and simulated ones)
+    and test that the response is as expected.
 
-Symfony integrates with an independent library called `PHPUnit`_ to give
-you a rich testing framework. This article won't cover PHPUnit itself,
-which has its own excellent `documentation`_.
+.. _testing-installation:
+.. _the-phpunit-testing-framework:
+
+Installation
+------------
 
 Before creating your first test, install ``symfony/test-pack``, which installs
 some other packages needed for testing (such as ``phpunit/phpunit``):
@@ -44,28 +68,6 @@ your test into multiple "test suites").
     missing, you can try running the recipe again using
     ``composer recipes:install phpunit/phpunit --force -v``.
 
-Types of Tests
---------------
-
-There are many types of automated tests and precise definitions often
-differ from project to project. In Symfony, the following definitions are
-used. If you have learned something different, that is not necessarily
-wrong, just different from what the Symfony documentation is using.
-
-`Unit Tests`_
-    These tests ensure that *individual* units of source code (e.g. a single
-    class) behave as intended.
-
-`Integration Tests`_
-    These tests test a combination of classes and commonly interact with
-    Symfony's service container. These tests do not yet cover the fully
-    working application, those are called *Application tests*.
-
-`Application Tests`_
-    Application tests test the behavior of a complete application. They
-    make HTTP requests (both real and simulated ones) and test that the
-    response is as expected.
-
 Unit Tests
 ----------
 
@@ -111,7 +113,7 @@ to use the Symfony Kernel to fetch a service from the dependency injection
 container.
 
 Symfony provides a :class:`Symfony\\Bundle\\FrameworkBundle\\Test\\KernelTestCase`
-class to help you creating and booting the kernel in your tests using
+class to help you create and boot the kernel in your tests using
 ``bootKernel()``::
 
     // tests/Service/NewsletterGeneratorTest.php
@@ -389,6 +391,9 @@ Now, enable it as a PHPUnit extension:
         <!-- ... -->
 
         <extensions>
+            <!-- use this with PHPUnit 10 or newer -->
+            <bootstrap class="DAMA\DoctrineTestBundle\PHPUnit\PHPUnitExtension"/>
+            <!-- use this with legacy PHPUnit versions older than 10 -->
             <extension class="DAMA\DoctrineTestBundle\PHPUnit\PHPUnitExtension"/>
         </extensions>
     </phpunit>
@@ -400,11 +405,11 @@ test finishes to undo all changes. Read more in the documentation of the
 
 .. _doctrine-fixtures:
 
-Load Dummy Data Fixtures
-........................
+Load Test Data Fixtures
+.......................
 
 Instead of using the real data from the production database, it's common to
-use fake or dummy data in the test database. This is usually called
+use fake or test data in the test database. This is usually called
 *"fixtures data"* and Doctrine provides a library to create and load them.
 Install it with:
 
@@ -712,6 +717,29 @@ stores in the session of the test client. If you need to define custom
 attributes in this token, you can use the ``tokenAttributes`` argument of the
 :method:`Symfony\\Bundle\\FrameworkBundle\\KernelBrowser::loginUser` method.
 
+You can also use an :ref:`in-memory user <security-memory-user-provider>` in your tests
+by instantiating :class:`Symfony\\Component\\Security\\Core\\User\\InMemoryUser` directly::
+
+    // tests/Controller/ProfileControllerTest.php
+    use Symfony\Component\Security\Core\User\InMemoryUser;
+
+    $client = static::createClient();
+    $testUser = new InMemoryUser('admin', 'password', ['ROLE_ADMIN']);
+    $client->loginUser($testUser);
+
+Before doing this, you must define the in-memory user in your test environment
+configuration to ensure it exists and can be authenticated::
+
+.. code-block:: yaml
+
+    # config/packages/security.yaml
+    when@test:
+        security:
+            users_in_memory:
+                memory:
+                    users:
+                        admin: { password: password, roles: ROLE_ADMIN }
+
 To set a specific firewall (``main`` is set by default)::
 
     $client->loginUser($testUser, 'my_firewall');
@@ -732,8 +760,10 @@ a shortcut to make AJAX requests::
     // the required HTTP_X_REQUESTED_WITH header is added automatically
     $client->xmlHttpRequest('POST', '/submit', ['name' => 'Fabien']);
 
-Sending Custom Headers
-......................
+.. _sending-custom-headers:
+
+Sending Custom HTTP Headers
+...........................
 
 If your application behaves according to some HTTP headers, pass them as the
 second argument of ``createClient()``::
@@ -862,8 +892,8 @@ Use the ``submitForm()`` method to submit the form that contains the given butto
         'comment_form[content]' => '...',
     ]);
 
-The first argument of ``submitForm()`` is the text content, ``id``, ``value`` or
-``name`` of any ``<button>`` or ``<input type="submit">`` included in the form.
+The first argument of ``submitForm()`` is the text content, ``id`` or ``name``
+of any ``<button>`` or ``<input type="submit">`` included in the form.
 The second optional argument is used to override the default form field values.
 
 .. note::
@@ -1159,7 +1189,7 @@ Learn more
     /components/css_selector
 
 .. _`PHPUnit`: https://phpunit.de/
-.. _`documentation`: https://docs.phpunit.de/
+.. _`official PHPUnit documentation`: https://docs.phpunit.de/
 .. _`Writing Tests for PHPUnit`: https://docs.phpunit.de/en/10.5/writing-tests-for-phpunit.html
 .. _`PHPUnit documentation`: https://docs.phpunit.de/en/10.5/configuration.html
 .. _`unit test`: https://en.wikipedia.org/wiki/Unit_testing
diff --git a/testing/dom_crawler.rst b/testing/dom_crawler.rst
index 139d94efdd9..9f419bf9b63 100644
--- a/testing/dom_crawler.rst
+++ b/testing/dom_crawler.rst
@@ -10,38 +10,40 @@ Traversing
 
 Like jQuery, the Crawler has methods to traverse the DOM of an HTML/XML
 document. For example, the following finds all ``input[type=submit]`` elements,
-selects the last one on the page, and then selects its immediate parent element::
+selects the last one on the page, and then selects its immediate ancestor element::
 
     $newCrawler = $crawler->filter('input[type=submit]')
         ->last()
-        ->parents()
+        ->ancestors()
         ->first()
     ;
 
 Many other methods are also available:
 
 ``filter('h1.title')``
-    Nodes that match the CSS selector.
+    Finds nodes that match the given CSS selector (which must be supported by
+    Symfony's :doc:`CSS Selector component </components/css_selector>`).
 ``filterXpath('h1')``
-    Nodes that match the XPath expression.
+    Finds nodes matching the given `XPath expression`_.
 ``eq(1)``
-    Node for the specified index.
+    Returns the node at the given index (``0`` is the first node).
 ``first()``
-    First node.
+    Returns the first node (equivalent to ``eq(0)``).
 ``last()``
-    Last node.
+    Returns the last node.
 ``siblings()``
-    Siblings.
+    Returns all sibling nodes (nodes with the same parent, excluding the current node).
 ``nextAll()``
-    All following siblings.
+    Returns all following siblings (same parent, after the current node).
 ``previousAll()``
-    All preceding siblings.
-``parents()``
-    Returns the parent nodes.
+    Returns all preceding siblings (same parent, before the current node).
+``ancestors()``
+    Returns all ancestor nodes (parents, grandparents, etc., up to the ``<html>``
+    element).
 ``children()``
-    Returns children nodes.
+    Returns all direct child nodes of the current node.
 ``reduce($lambda)``
-    Nodes for which the callable does not return false.
+    Filters the nodes using a callback; keeps only those for which it returns ``true``.
 
 Since each of these methods returns a new ``Crawler`` instance, you can
 narrow down your node selection by chaining the method calls::
@@ -91,3 +93,5 @@ The Crawler can extract information from the nodes::
     $data = $crawler->each(function ($node, int $i): string {
         return $node->attr('href');
     });
+
+.. _`XPath expression`: https://developer.mozilla.org/en-US/docs/Web/XML/XPath
diff --git a/testing/end_to_end.rst b/testing/end_to_end.rst
index 8a624ae884e..9b3829cd1bc 100644
--- a/testing/end_to_end.rst
+++ b/testing/end_to_end.rst
@@ -1,41 +1,35 @@
 End-to-End Testing
 ==================
 
-    The Panther component allows to drive a real web browser with PHP to create
-    end-to-end tests.
+End-to-end tests simulate how real users interact with your application through
+a browser. They focus on verifying your user interface and the outcomes of user
+actions (like confirming that clicking a button sends an email).
+
+Unlike :ref:`application tests <functional-tests>`, these tests run in a real
+browser that can work in headless mode (without a graphical interface) for CI
+environments or with a graphical interface for debugging.
+
+Symfony provides a component called **Panther** to run end-to-end tests. Panther
+lets you run tests in a real browser and offers unique features not available in
+other test types:
+
+* Taking screenshots at any point during the test;
+* Executing JavaScript on your pages;
+* Supporting everything Chrome or Firefox does;
+* Simpler testing of real-time applications (e.g. WebSockets, Server-Sent Events with Mercure).
 
 Installation
 ------------
 
+Before creating and running your first end-to-end tests, run the following command
+to install the needed dependencies:
+
 .. code-block:: terminal
 
-    $ composer require symfony/panther
+    $ composer require --dev symfony/panther
 
 .. include:: /components/require_autoload.rst.inc
 
-Introduction
-------------
-
-End to end tests are a special type of application tests that
-simulate a real user interacting with your application. They are
-typically used to test the user interface (UI) of your application
-and the effects of these interactions (e.g. when I click on this button, a mail
-must be sent). The difference with functional tests detailed above is
-that End-to-End tests use a real browser instead of a simulated one. This
-browser can run in headless mode (without a graphical interface) or not.
-The first option is convenient for running tests in a Continuous Integration
-(CI), while the second one is useful for debugging purpose.
-
-This is the purpose of Panther, a component that provides a real browser
-to run your tests. Here are a few things that make Panther special, compared
-to other testing tools provided by Symfony:
-
-* Possibility to take screenshots of the browser at any time during the test
-* The JavaScript code contained in webpages is executed
-* Panther supports everything that Chrome (or Firefox) implements
-* Convenient way to test real-time applications (e.g. WebSockets, Server-Sent Events
-  with Mercure, etc.)
-
 Installing Web Drivers
 ~~~~~~~~~~~~~~~~~~~~~~
 
@@ -82,12 +76,16 @@ When using the extension in conjunction with the ``PANTHER_ERROR_SCREENSHOT_DIR`
 environment variable, tests using the Panther client that fail or error (after the
 client is created) will automatically get a screenshot taken to help debugging.
 
-To register the Panther extension, add the following lines to ``phpunit.xml.dist``:
+To register the Panther extension, add the following lines to ``phpunit.dist.xml``
+(in legacy PHPUnit versions older than 10, the file is named ``phpunit.xml.dist``):
 
 .. code-block:: xml
 
-    <!-- phpunit.xml.dist -->
+    <!-- phpunit.dist.xml -->
     <extensions>
+        <!-- use this with PHPUnit 10 or newer -->
+        <bootstrap class="Symfony\Component\Panther\ServerExtension"/>
+        <!-- use this with legacy PHPUnit versions older than 10 -->
         <extension class="Symfony\Component\Panther\ServerExtension"/>
     </extensions>
 
@@ -884,7 +882,7 @@ Then declare it as a router for Panther server in ``phpunit.xml.dist`` using the
         <!-- ... -->
         <php>
             <!-- ... -->
-            <server name="PANTHER_WEB_SERVER_ROUTER" value="./tests/router.php"/>
+            <server name="PANTHER_WEB_SERVER_ROUTER" value="../tests/router.php"/>
         </php>
     </phpunit>
 
diff --git a/translation.rst b/translation.rst
index 2300f9ef2f6..ed656c62848 100644
--- a/translation.rst
+++ b/translation.rst
@@ -416,6 +416,84 @@ You can also specify the message domain and pass some additional variables:
     major difference: automatic output escaping is **not** applied to translations
     using a tag.
 
+Global Translation Parameters
+-----------------------------
+
+.. versionadded:: 7.3
+
+    The global translation parameters feature was introduced in Symfony 7.3.
+
+If the content of a translation parameter is repeated across multiple
+translation messages (e.g. a company name, or a version number), you can define
+it as a global translation parameter. This helps you avoid repeating the same
+values manually in each message.
+
+You can configure these global parameters in the ``translations.globals`` option
+of your main configuration file using either ``%...%`` or ``{...}`` syntax:
+
+.. configuration-block::
+
+    .. code-block:: yaml
+
+        # config/packages/translator.yaml
+        translator:
+            # ...
+            globals:
+                # when using the '%' wrapping characters, you must escape them
+                '%%app_name%%': 'My application'
+                '{app_version}': '1.2.3'
+                '{url}': { message: 'url', parameters: { scheme: 'https://' }, domain: 'global' }
+
+    .. code-block:: xml
+
+           <!-- config/packages/translation.xml -->
+           <?xml version="1.0" encoding="UTF-8" ?>
+           <container xmlns="http://symfony.com/schema/dic/services"
+               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+               xmlns:framework="http://symfony.com/schema/dic/symfony"
+               xsi:schemaLocation="http://symfony.com/schema/dic/services
+                   https://symfony.com/schema/dic/services/services-1.0.xsd
+                   http://symfony.com/schema/dic/symfony
+                   https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
+
+               <framework:config>
+                   <framework:translator>
+                       <!-- ... -->
+                        <!-- when using the '%' wrapping characters, you must escape them -->
+                       <framework:global name="%%app_name%%">My application</framework:global>
+                       <framework:global name="{app_version}" value="1.2.3"/>
+                       <framework:global name="{url}" message="url" domain="global">
+                            <framework:parameter name="scheme">https://</framework:parameter>
+                        </framework:global>
+                   </framework:translator>
+               </framework:config>
+           </container>
+
+    .. code-block:: php
+
+        // config/packages/translator.php
+        use Symfony\Config\TwigConfig;
+
+        return static function (TwigConfig $translator): void {
+            // ...
+            // when using the '%' wrapping characters, you must escape them
+            $translator->globals('%%app_name%%')->value('My application');
+            $translator->globals('{app_version}')->value('1.2.3');
+            $translator->globals('{url}')->value(['message' => 'url', 'parameters' => ['scheme' => 'https://']]);
+        };
+
+Once defined, you can use these parameters in translation messages anywhere in
+your application:
+
+.. code-block:: twig
+
+    {{ 'Application version: {app_version}'|trans }}
+    {# output: "Application version: 1.2.3" #}
+
+    {# parameters passed to the message override global parameters #}
+    {{ 'Package version: {app_version}'|trans({'{app_version}': '2.3.4'}) }}
+    # Displays "Package version: 2.3.4"
+
 Forcing the Translator Locale
 -----------------------------
 
@@ -476,6 +554,20 @@ translation. The only difference is that the pending translation is prefixed by
 
     $ php bin/console translation:extract --force --prefix="NEW_" fr
 
+Alternatively, you can use the ``--no-fill`` option to leave the pending translation
+completely empty when creating new entries in the translation catalog. This is
+particularly useful when using external translation tools, as it makes it easier
+to spot untranslated strings:
+
+.. code-block:: terminal
+
+    # when using the --no-fill option, the --prefix option is ignored
+    $ php bin/console translation:extract --force --no-fill fr
+
+.. versionadded:: 7.2
+
+    The ``--no-fill`` option was introduced in Symfony 7.2.
+
 .. _translation-resource-locations:
 
 Translation Resource/File Names and Locations
@@ -488,7 +580,9 @@ Symfony looks for message files (i.e. translations) in the following default loc
   ``Resources/translations/`` directory, which is no longer recommended for bundles).
 
 The locations are listed here with the highest priority first. That is, you can
-override the translation messages of a bundle in the first directory.
+override the translation messages of a bundle in the first directory. Bundles are
+processed in the order in which they are listed in the ``config/bundles.php`` file,
+so bundles appearing earlier have higher priority.
 
 The override mechanism works at a key level: only the overridden keys need
 to be listed in a higher priority message file. When a key is not found
@@ -583,8 +677,7 @@ Translations of Doctrine Entities
 
 Unlike the contents of templates, it's not practical to translate the contents
 stored in Doctrine Entities using translation catalogs. Instead, use the
-Doctrine `Translatable Extension`_ or the `Translatable Behavior`_. For more
-information, read the documentation of those libraries.
+Doctrine `Translatable Extension`_.
 
 Custom Translation Resources
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -640,7 +733,7 @@ You'll now have a new line in your ``.env`` file that you can uncomment:
 
 The ``LOCO_DSN`` isn't a *real* address: it's a convenient format that offloads
 most of the configuration work to Symfony. The ``loco`` scheme activates the
-Loco provider that you just installed, which knows all about how to push and
+Loco provider that you installed, which knows all about how to push and
 pull translations via Loco. The *only* part you need to change is the
 ``API_KEY`` placeholder.
 
@@ -1083,18 +1176,11 @@ checks translation resources for several locales:
 Switch Locale Programmatically
 ------------------------------
 
-Sometimes you need to change the locale of the application dynamically
-just to run some code. Imagine a console command that renders Twig templates
-of emails in different languages. You need to change the locale only to
-render those templates.
-
-The ``LocaleSwitcher`` class allows you to change at once the locale
-of:
+Sometimes you need to change the application's locale dynamically while running
+some code. For example, a console command that renders email templates in
+different languages. In such cases, you only need to switch the locale temporarily.
 
-* All the services that are tagged with ``kernel.locale_aware``;
-* ``\Locale::setDefault()``;
-* If the ``RequestContext`` service is available, the ``_locale``
-  parameter (so urls are generated with the new locale)::
+The ``LocaleSwitcher`` class allows you to do that::
 
     use Symfony\Component\Translation\LocaleSwitcher;
 
@@ -1107,28 +1193,23 @@ of:
 
         public function someMethod(): void
         {
-            // you can get the current application locale like this:
             $currentLocale = $this->localeSwitcher->getLocale();
 
-            // you can set the locale for the entire application like this:
-            // (from now on, the application will use 'fr' (French) as the
-            // locale; including the default locale used to translate Twig templates)
+            // set the application locale programmatically to 'fr' (French):
+            // this affects translation, URL generation, etc.
             $this->localeSwitcher->setLocale('fr');
 
-            // reset the current locale of your application to the configured default locale
-            // in config/packages/translation.yaml, by option 'default_locale'
+            // reset the locale to the default one configured via the
+            // 'default_locale' option in config/packages/translation.yaml
             $this->localeSwitcher->reset();
 
-            // you can also run some code with a certain locale, without
+            // run some code with a specific locale, temporarily, without
             // changing the locale for the rest of the application
             $this->localeSwitcher->runWithLocale('es', function() {
-
-                // e.g. render here some Twig templates using 'es' (Spanish) locale
-
+                // e.g. render templates, send emails, etc. using the 'es' (Spanish) locale
             });
 
-            // you can optionally declare an argument in your callback to receive the
-            // injected locale
+            // optionally, receive the current locale as an argument:
             $this->localeSwitcher->runWithLocale('es', function(string $locale) {
 
                 // here, the $locale argument will be set to 'es'
@@ -1139,6 +1220,20 @@ of:
         }
     }
 
+The ``LocaleSwitcher`` class changes the locale of:
+
+* All services tagged with ``kernel.locale_aware``;
+* The default locale set via ``\Locale::setDefault()``;
+* The ``_locale`` parameter of the ``RequestContext`` service (if available),
+  so generated URLs reflect the new locale.
+
+.. note::
+
+    The LocaleSwitcher applies the new locale only for the current request,
+    and its effect is lost on subsequent requests, such as after a redirect.
+
+    See :ref:`how to make the locale persist across requests <locale-sticky-session>`.
+
 When using :ref:`autowiring <services-autowire>`, type-hint any controller or
 service argument with the :class:`Symfony\\Component\\Translation\\LocaleSwitcher`
 class to inject the locale switcher service. Otherwise, configure your services
@@ -1363,7 +1458,7 @@ Symfony processes all the application translation files as part of the process
 that compiles the application code before executing it. If there's an error in
 any translation file, you'll see an error message explaining the problem.
 
-If you prefer, you can also validate the contents of any YAML and XLIFF
+If you prefer, you can also validate the syntax of any YAML and XLIFF
 translation file using the ``lint:yaml`` and ``lint:xliff`` commands:
 
 .. code-block:: terminal
@@ -1404,6 +1499,22 @@ adapted to the format required by GitHub, but you can force that format too:
 
         $ php vendor/bin/yaml-lint translations/
 
+The ``lint:yaml`` and ``lint:xliff`` commands validate the YAML and XML syntax
+of the translation files, but not their contents. Use the following command
+to check that the translation contents are also correct:
+
+    .. code-block:: terminal
+
+        # checks the contents of all the translation catalogues in all locales
+        $ php bin/console lint:translations
+
+        # checks the contents of the translation catalogues for Italian (it) and Japanese (ja) locales
+        $ php bin/console lint:translations --locale=it --locale=ja
+
+.. versionadded:: 7.2
+
+    The ``lint:translations`` command was introduced in Symfony 7.2.
+
 Pseudo-localization translator
 ------------------------------
 
@@ -1565,7 +1676,6 @@ Learn more
 .. _`ISO 3166-1 alpha-2`: https://en.wikipedia.org/wiki/ISO_3166-1#Current_codes
 .. _`ISO 639-1`: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
 .. _`Translatable Extension`: https://github.com/doctrine-extensions/DoctrineExtensions/blob/main/doc/translatable.md
-.. _`Translatable Behavior`: https://github.com/KnpLabs/DoctrineBehaviors
 .. _`Custom Language Name setting`: https://docs.lokalise.com/en/articles/1400492-uploading-files#custom-language-codes
 .. _`ICU resource bundle`: https://github.com/unicode-org/icu-docs/blob/main/design/bnf_rb.txt
 .. _`Portable object format`: https://www.gnu.org/software/gettext/manual/html_node/PO-Files.html
diff --git a/validation.rst b/validation.rst
index 4905283b18b..cfa8154b627 100644
--- a/validation.rst
+++ b/validation.rst
@@ -327,99 +327,13 @@ literature genre mostly associated with the author, which can be set to either
             {
                 // ...
 
-                $metadata->addPropertyConstraint('genre', new Assert\Choice([
-                    'choices' => ['fiction', 'non-fiction'],
-                    'message' => 'Choose a valid genre.',
-                ]));
+                $metadata->addPropertyConstraint('genre', new Assert\Choice(
+                    choices: ['fiction', 'non-fiction'],
+                    message: 'Choose a valid genre.',
+                ));
             }
         }
 
-.. _validation-default-option:
-
-The options of a constraint can always be passed in as an array. Some constraints,
-however, also allow you to pass the value of one, "*default*", option in place
-of the array. In the case of the ``Choice`` constraint, the ``choices``
-options can be specified in this way.
-
-.. configuration-block::
-
-    .. code-block:: php-attributes
-
-        // src/Entity/Author.php
-        namespace App\Entity;
-
-        // ...
-        use Symfony\Component\Validator\Constraints as Assert;
-
-        class Author
-        {
-            #[Assert\Choice(['fiction', 'non-fiction'])]
-            private string $genre;
-
-            // ...
-        }
-
-    .. code-block:: yaml
-
-        # config/validator/validation.yaml
-        App\Entity\Author:
-            properties:
-                genre:
-                    - Choice: [fiction, non-fiction]
-                # ...
-
-    .. code-block:: xml
-
-        <!-- config/validator/validation.xml -->
-        <?xml version="1.0" encoding="UTF-8" ?>
-        <constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping"
-            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-            xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping
-                https://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd">
-
-            <class name="App\Entity\Author">
-                <property name="genre">
-                    <constraint name="Choice">
-                        <value>fiction</value>
-                        <value>non-fiction</value>
-                    </constraint>
-                </property>
-
-                <!-- ... -->
-            </class>
-        </constraint-mapping>
-
-    .. code-block:: php
-
-        // src/Entity/Author.php
-        namespace App\Entity;
-
-        // ...
-        use Symfony\Component\Validator\Constraints as Assert;
-        use Symfony\Component\Validator\Mapping\ClassMetadata;
-
-        class Author
-        {
-            private string $genre;
-
-            public static function loadValidatorMetadata(ClassMetadata $metadata): void
-            {
-                // ...
-
-                $metadata->addPropertyConstraint(
-                    'genre',
-                    new Assert\Choice(['fiction', 'non-fiction'])
-                );
-            }
-        }
-
-This is purely meant to make the configuration of the most common option of
-a constraint shorter and quicker.
-
-If you're ever unsure of how to specify an option, either check the namespace
-``Symfony\Component\Validator\Constraints`` for the constraint or play it safe
-by always passing in an array of options (the first method shown above).
-
 Constraints in Form Classes
 ---------------------------
 
@@ -520,7 +434,7 @@ class to have at least 3 characters.
                 $metadata->addPropertyConstraint('firstName', new Assert\NotBlank());
                 $metadata->addPropertyConstraint(
                     'firstName',
-                    new Assert\Length(['min' => 3])
+                    new Assert\Length(min: 3)
                 );
             }
         }
@@ -603,9 +517,9 @@ this method must return ``true``:
         {
             public static function loadValidatorMetadata(ClassMetadata $metadata): void
             {
-                $metadata->addGetterConstraint('passwordSafe', new Assert\IsTrue([
-                    'message' => 'The password cannot match your first name',
-                ]));
+                $metadata->addGetterConstraint('passwordSafe', new Assert\IsTrue(
+                    message: 'The password cannot match your first name',
+                ));
             }
         }
 
diff --git a/validation/custom_constraint.rst b/validation/custom_constraint.rst
index 40d19e0caa0..10584a36383 100644
--- a/validation/custom_constraint.rst
+++ b/validation/custom_constraint.rst
@@ -176,6 +176,13 @@ message as its argument and returns an instance of
 :class:`Symfony\\Component\\Validator\\Violation\\ConstraintViolationBuilderInterface`.
 The ``addViolation()`` method call finally adds the violation to the context.
 
+.. tip::
+
+    Validation error messages are automatically translated to the current application
+    locale. If your application doesn't use translations, you can disable this behavior
+    by calling the ``disableTranslation()`` method of ``ConstraintViolationBuilderInterface``.
+    See also the :ref:`framework.validation.disable_translation option <reference-validation-disable_translation>`.
+
 Using the new Validator
 -----------------------
 
@@ -248,7 +255,7 @@ You can use custom validators like the ones provided by Symfony itself:
             public static function loadValidatorMetadata(ClassMetadata $metadata): void
             {
                 $metadata->addPropertyConstraint('name', new NotBlank());
-                $metadata->addPropertyConstraint('name', new ContainsAlphanumeric(['mode' => 'loose']));
+                $metadata->addPropertyConstraint('name', new ContainsAlphanumeric(mode: 'loose'));
             }
         }
 
@@ -273,6 +280,7 @@ define those options as public properties on the constraint class::
     // src/Validator/Foo.php
     namespace App\Validator;
 
+    use Symfony\Component\Validator\Attribute\HasNamedArguments;
     use Symfony\Component\Validator\Constraint;
 
     #[\Attribute]
@@ -282,6 +290,7 @@ define those options as public properties on the constraint class::
         public $message = 'This value is invalid';
         public $optionalBarOption = false;
 
+        #[HasNamedArguments]
         public function __construct(
             $mandatoryFooOption,
             ?string $message = null,
@@ -314,7 +323,7 @@ define those options as public properties on the constraint class::
     }
 
 Then, inside the validator class you can access these options directly via the
-constraint class passes to the ``validate()`` method::
+constraint class passed to the ``validate()`` method::
 
     class FooValidator extends ConstraintValidator
     {
@@ -402,10 +411,10 @@ the custom options like you pass any other option in built-in constraints:
             public static function loadValidatorMetadata(ClassMetadata $metadata)
             {
                 $metadata->addPropertyConstraint('name', new NotBlank());
-                $metadata->addPropertyConstraint('name', new Foo([
-                    'mandatoryFooOption' => 'bar',
-                    'optionalBarOption' => true,
-                ]));
+                $metadata->addPropertyConstraint('name', new Foo(
+                    mandatoryFooOption: 'bar',
+                    optionalBarOption: true,
+                ));
             }
         }
 
@@ -543,6 +552,9 @@ A class constraint validator must be applied to the class itself:
 Testing Custom Constraints
 --------------------------
 
+Atomic Constraints
+~~~~~~~~~~~~~~~~~~
+
 Use the :class:`Symfony\\Component\\Validator\\Test\\ConstraintValidatorTestCase`
 class to simplify writing unit tests for your custom constraints::
 
@@ -586,3 +598,74 @@ class to simplify writing unit tests for your custom constraints::
             // ...
         }
     }
+
+Compound Constraints
+~~~~~~~~~~~~~~~~~~~~
+
+Consider the following compound constraint that checks if a string meets
+the minimum requirements for your password policy::
+
+    // src/Validator/PasswordRequirements.php
+    namespace App\Validator;
+
+    use Symfony\Component\Validator\Constraints as Assert;
+
+    #[\Attribute]
+    class PasswordRequirements extends Assert\Compound
+    {
+        protected function getConstraints(array $options): array
+        {
+            return [
+                new Assert\NotBlank(allowNull: false),
+                new Assert\Length(min: 8, max: 255),
+                new Assert\NotCompromisedPassword(),
+                new Assert\Type('string'),
+                new Assert\Regex('/[A-Z]+/'),
+            ];
+        }
+    }
+
+You can use the :class:`Symfony\\Component\\Validator\\Test\\CompoundConstraintTestCase`
+class to check precisely which of the constraints failed to pass::
+
+    // tests/Validator/PasswordRequirementsTest.php
+    namespace App\Tests\Validator;
+
+    use App\Validator\PasswordRequirements;
+    use Symfony\Component\Validator\Constraints as Assert;
+    use Symfony\Component\Validator\Test\CompoundConstraintTestCase;
+
+    /**
+     * @extends CompoundConstraintTestCase<PasswordRequirements>
+     */
+    class PasswordRequirementsTest extends CompoundConstraintTestCase
+    {
+        public function createCompound(): Assert\Compound
+        {
+            return new PasswordRequirements();
+        }
+
+        public function testInvalidPassword(): void
+        {
+            $this->validateValue('azerty123');
+
+            // check all constraints pass except for the
+            // password leak and the uppercase letter checks
+            $this->assertViolationsRaisedByCompound([
+                new Assert\NotCompromisedPassword(),
+                new Assert\Regex('/[A-Z]+/'),
+            ]);
+        }
+
+        public function testValid(): void
+        {
+            $this->validateValue('VERYSTR0NGP4$$WORD#%!');
+
+            $this->assertNoViolation();
+        }
+    }
+
+.. versionadded:: 7.2
+
+    The :class:`Symfony\\Component\\Validator\\Test\\CompoundConstraintTestCase`
+    class was introduced in Symfony 7.2.
diff --git a/validation/groups.rst b/validation/groups.rst
index 8d84e52c0da..55625be702d 100644
--- a/validation/groups.rst
+++ b/validation/groups.rst
@@ -101,21 +101,21 @@ user registers and when a user updates their contact information later:
         {
             public static function loadValidatorMetadata(ClassMetadata $metadata): void
             {
-                $metadata->addPropertyConstraint('email', new Assert\Email([
-                    'groups' => ['registration'],
-                ]));
-
-                $metadata->addPropertyConstraint('password', new Assert\NotBlank([
-                    'groups' => ['registration'],
-                ]));
-                $metadata->addPropertyConstraint('password', new Assert\Length([
-                    'min'    => 7,
-                    'groups' => ['registration'],
-                ]));
-
-                $metadata->addPropertyConstraint('city', new Assert\Length([
-                    'min' => 2,
-                ]));
+                $metadata->addPropertyConstraint('email', new Assert\Email(
+                    groups: ['registration'],
+                ));
+
+                $metadata->addPropertyConstraint('password', new Assert\NotBlank(
+                    groups: ['registration'],
+                ));
+                $metadata->addPropertyConstraint('password', new Assert\Length(
+                    min: 7,
+                    groups: ['registration'],
+                ));
+
+                $metadata->addPropertyConstraint('city', new Assert\Length(
+                    min: 2,
+                ));
             }
         }
 
diff --git a/validation/sequence_provider.rst b/validation/sequence_provider.rst
index 836568c2327..c316a85d249 100644
--- a/validation/sequence_provider.rst
+++ b/validation/sequence_provider.rst
@@ -104,10 +104,10 @@ username and the password are different only if all other validation passes
                 $metadata->addPropertyConstraint('username', new Assert\NotBlank());
                 $metadata->addPropertyConstraint('password', new Assert\NotBlank());
 
-                $metadata->addGetterConstraint('passwordSafe', new Assert\IsTrue([
-                    'message' => 'The password cannot match your first name',
-                    'groups'  => ['Strict'],
-                ]));
+                $metadata->addGetterConstraint('passwordSafe', new Assert\IsTrue(
+                    message: 'The password cannot match your first name',
+                    groups: ['Strict'],
+                ));
 
                 $metadata->setGroupSequence(['User', 'Strict']);
             }
@@ -249,10 +249,10 @@ entity and a new constraint group called ``Premium``:
             public static function loadValidatorMetadata(ClassMetadata $metadata): void
             {
                 $metadata->addPropertyConstraint('name', new Assert\NotBlank());
-                $metadata->addPropertyConstraint('creditCard', new Assert\CardScheme([
-                    'schemes' => [Assert\CardScheme::VISA],
-                    'groups'  => ['Premium'],
-                ]));
+                $metadata->addPropertyConstraint('creditCard', new Assert\CardScheme(
+                    schemes: [Assert\CardScheme::VISA],
+                    groups: ['Premium'],
+                ));
             }
         }
 
diff --git a/validation/severity.rst b/validation/severity.rst
index 632a99519d9..154c13d5e3e 100644
--- a/validation/severity.rst
+++ b/validation/severity.rst
@@ -105,15 +105,15 @@ Use the ``payload`` option to configure the error level for each constraint:
 
             public static function loadValidatorMetadata(ClassMetadata $metadata): void
             {
-                $metadata->addPropertyConstraint('username', new Assert\NotBlank([
-                    'payload' => ['severity' => 'error'],
-                ]));
-                $metadata->addPropertyConstraint('password', new Assert\NotBlank([
-                    'payload' => ['severity' => 'error'],
-                ]));
-                $metadata->addPropertyConstraint('bankAccountNumber', new Assert\Iban([
-                    'payload' => ['severity' => 'warning'],
-                ]));
+                $metadata->addPropertyConstraint('username', new Assert\NotBlank(
+                    payload: ['severity' => 'error'],
+                ));
+                $metadata->addPropertyConstraint('password', new Assert\NotBlank(
+                    payload: ['severity' => 'error'],
+                ));
+                $metadata->addPropertyConstraint('bankAccountNumber', new Assert\Iban(
+                    payload: ['severity' => 'warning'],
+                ));
             }
         }
 
diff --git a/validation/translations.rst b/validation/translations.rst
index 9a4ece17736..db2cd518eb7 100644
--- a/validation/translations.rst
+++ b/validation/translations.rst
@@ -83,9 +83,9 @@ property is not empty, add the following:
 
             public static function loadValidatorMetadata(ClassMetadata $metadata): void
             {
-                $metadata->addPropertyConstraint('name', new NotBlank([
-                    'message' => 'author.name.not_blank',
-                ]));
+                $metadata->addPropertyConstraint('name', new NotBlank(
+                    message: 'author.name.not_blank',
+                ));
             }
         }
 
@@ -136,13 +136,13 @@ You can also use :class:`Symfony\\Component\\Translation\\TranslatableMessage` t
     use Symfony\Component\Translation\TranslatableMessage;
     use Symfony\Component\Validator\Constraints as Assert;
     use Symfony\Component\Validator\Context\ExecutionContextInterface;
-    
+
     #[Assert\Callback]
     public function validate(ExecutionContextInterface $context, mixed $payload): void
     {
         // somehow you have an array of "fake names"
         $fakeNames = [/* ... */];
-    
+
         // check if the name is actually a fake name
         if (in_array($this->getFirstName(), $fakeNames, true)) {
             $context->buildViolation(new TranslatableMessage('author.name.fake', [], 'validators'))
diff --git a/web_link.rst b/web_link.rst
index 8602445313f..2f2f96d106b 100644
--- a/web_link.rst
+++ b/web_link.rst
@@ -146,7 +146,8 @@ The WebLink component provides the following Twig functions to send those hints:
 * ``prefetch()``: "identifies a resource that might be required by the next
   navigation, and that the user agent *should* fetch, such that the user agent
   can deliver a faster response once the resource is requested in the future".
-* ``prerender()``: "identifies a resource that might be required by the next
+* ``prerender()``: " **deprecated** and superseded by the `Speculation Rules API`_,
+  identifies a resource that might be required by the next
   navigation, and that the user agent *should* fetch and execute, such that the
   user agent can deliver a faster response once the resource is requested later".
 
@@ -194,6 +195,12 @@ You can also add links to the HTTP response directly from controllers and servic
         }
     }
 
+.. tip::
+
+    The possible values of link relations (``'preload'``, ``'preconnect'``, etc.)
+    are also defined as constants in the :class:`Symfony\\Component\\WebLink\\Link`
+    class (e.g. ``Link::REL_PRELOAD``, ``Link::REL_PRECONNECT``, etc.).
+
 .. _`WebLink`: https://github.com/symfony/web-link
 .. _`HTTP/2 Server Push`: https://tools.ietf.org/html/rfc7540#section-8.2
 .. _`Resource Hints`: https://www.w3.org/TR/resource-hints/
@@ -206,3 +213,4 @@ You can also add links to the HTTP response directly from controllers and servic
 .. _`Akamai`: https://http2.akamai.com/
 .. _`link defined in the HTML specification`: https://html.spec.whatwg.org/dev/links.html#linkTypes
 .. _`PSR-13`: https://www.php-fig.org/psr/psr-13/
+.. _`Speculation Rules API`: https://developer.mozilla.org/docs/Web/API/Speculation_Rules_API
diff --git a/webhook.rst b/webhook.rst
index 38f3a4b1004..d27a6e6d906 100644
--- a/webhook.rst
+++ b/webhook.rst
@@ -15,6 +15,11 @@ Installation
 Usage in Combination with the Mailer Component
 ----------------------------------------------
 
+.. admonition:: Screencast
+    :class: screencast
+
+    Like video tutorials? Check out the `Webhook Component for Email Events screencast`_.
+
 When using a third-party mailer provider, you can use the Webhook component to
 receive webhook calls from this provider.
 
@@ -23,19 +28,33 @@ Currently, the following third-party mailer providers support webhooks:
 ============== ============================================
 Mailer Service Parser service name
 ============== ============================================
+AhaSend        ``mailer.webhook.request_parser.ahasend``
 Brevo          ``mailer.webhook.request_parser.brevo``
+Mandrill       ``mailer.webhook.request_parser.mailchimp``
 MailerSend     ``mailer.webhook.request_parser.mailersend``
 Mailgun        ``mailer.webhook.request_parser.mailgun``
 Mailjet        ``mailer.webhook.request_parser.mailjet``
+Mailomat       ``mailer.webhook.request_parser.mailomat``
+Mailtrap       ``mailer.webhook.request_parser.mailtrap``
 Postmark       ``mailer.webhook.request_parser.postmark``
 Resend         ``mailer.webhook.request_parser.resend``
 Sendgrid       ``mailer.webhook.request_parser.sendgrid``
+Sweego         ``mailer.webhook.request_parser.sweego``
 ============== ============================================
 
 .. versionadded:: 7.1
 
     The support for ``Resend`` and ``MailerSend`` were introduced in Symfony 7.1.
 
+.. versionadded:: 7.2
+
+    The ``Mandrill``, ``Mailomat``, ``Mailtrap``, and ``Sweego`` integrations were introduced in
+    Symfony 7.2.
+
+.. versionadded:: 7.3
+
+    The ``AhaSend`` integration was introduced in Symfony 7.3.
+
 .. note::
 
     Install the third-party mailer provider you want to use as described in the
@@ -98,7 +117,7 @@ webhook consumer code.
 The webhook routing name is part of the URL you need to configure at the
 third-party mailer provider. The URL is the concatenation of your domain name
 and the routing name you chose in the configuration (like
-``https://example.com/webhook/mailer_mailgun``.
+``https://example.com/webhook/mailer_mailgun``).
 
 For Mailgun, you will get a secret for the webhook. Store this secret as
 MAILER_MAILGUN_SECRET (in the :doc:`secrets management system
@@ -157,6 +176,8 @@ Currently, the following third-party SMS transports support webhooks:
 SMS service  Parser service name
 ============ ==========================================
 Twilio       ``notifier.webhook.request_parser.twilio``
+Smsbox       ``notifier.webhook.request_parser.smsbox``
+Sweego       ``notifier.webhook.request_parser.sweego``
 Vonage       ``notifier.webhook.request_parser.vonage``
 ============ ==========================================
 
@@ -197,3 +218,4 @@ Creating a Custom Webhook
     Webhook.
 
 .. _`MakerBundle`: https://symfony.com/doc/current/bundles/SymfonyMakerBundle/index.html
+.. _`Webhook Component for Email Events screencast`: https://symfonycasts.com/screencast/mailtrap/email-event-webhook
diff --git a/workflow.rst b/workflow.rst
index 11b4005bb10..54a1b06313e 100644
--- a/workflow.rst
+++ b/workflow.rst
@@ -1,7 +1,7 @@
 Workflow
 ========
 
-Using the Workflow component inside a Symfony application requires knowing first
+Using the Workflow component inside a Symfony application requires first knowing
 some basic theory and concepts about workflows and state machines.
 :doc:`Read this article </workflow/workflow-and-state-machine>` for a quick overview.
 
@@ -826,7 +826,7 @@ transition. The value of this option is any valid expression created with the
                             from: draft
                             to:   reviewed
                         publish:
-                            # or "is_anonymous", "is_remember_me", "is_fully_authenticated", "is_granted", "is_valid"
+                            # or "is_remember_me", "is_fully_authenticated", "is_granted", "is_valid"
                             guard: "is_authenticated"
                             from: reviewed
                             to:   published
@@ -861,7 +861,7 @@ transition. The value of this option is any valid expression created with the
                     </framework:transition>
 
                     <framework:transition name="publish">
-                        <!-- or "is_anonymous", "is_remember_me", "is_fully_authenticated", "is_granted" -->
+                        <!-- or "is_remember_me", "is_fully_authenticated", "is_granted" -->
                         <framework:guard>is_authenticated</framework:guard>
                         <framework:from>reviewed</framework:from>
                         <framework:to>published</framework:to>
@@ -897,7 +897,7 @@ transition. The value of this option is any valid expression created with the
 
             $blogPublishing->transition()
                 ->name('publish')
-                    // or "is_anonymous", "is_remember_me", "is_fully_authenticated", "is_granted"
+                    // or "is_remember_me", "is_fully_authenticated", "is_granted"
                     ->guard('is_authenticated')
                     ->from(['reviewed'])
                     ->to(['published']);
@@ -1306,6 +1306,87 @@ In Twig templates, metadata is available via the ``workflow_metadata()`` functio
         </ul>
     </p>
 
+Validating Workflow Definitions
+-------------------------------
+
+Symfony allows you to validate workflow definitions using your own custom logic.
+To do so, create a class that implements the
+:class:`Symfony\\Component\\Workflow\\Validator\\DefinitionValidatorInterface`::
+
+    namespace App\Workflow\Validator;
+
+    use Symfony\Component\Workflow\Definition;
+    use Symfony\Component\Workflow\Exception\InvalidDefinitionException;
+    use Symfony\Component\Workflow\Validator\DefinitionValidatorInterface;
+
+    final class BlogPublishingValidator implements DefinitionValidatorInterface
+    {
+        public function validate(Definition $definition, string $name): void
+        {
+            if (!$definition->getMetadataStore()->getMetadata('title')) {
+                throw new InvalidDefinitionException(sprintf('The workflow metadata title is missing in Workflow "%s".', $name));
+            }
+
+            // ...
+        }
+    }
+
+After implementing your validator, configure your workflow to use it:
+
+.. configuration-block::
+
+    .. code-block:: yaml
+
+        # config/packages/workflow.yaml
+        framework:
+            workflows:
+                blog_publishing:
+                    # ...
+
+                    definition_validators:
+                        - App\Workflow\Validator\BlogPublishingValidator
+
+    .. code-block:: xml
+
+        <!-- config/packages/workflow.xml -->
+        <?xml version="1.0" encoding="UTF-8" ?>
+        <container xmlns="http://symfony.com/schema/dic/services"
+            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+            xmlns:framework="http://symfony.com/schema/dic/symfony"
+            xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd
+                http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd"
+        >
+            <framework:config>
+                <framework:workflow name="blog_publishing">
+                    <!-- ... -->
+                    <framework:definition-validators>App\Workflow\Validator\BlogPublishingValidator</framework:definition-validators>
+                </framework:workflow>
+            </framework:config>
+        </container>
+
+    .. code-block:: php
+
+        // config/packages/workflow.php
+        use Symfony\Config\FrameworkConfig;
+
+        return static function (FrameworkConfig $framework): void {
+            $blogPublishing = $framework->workflows()->workflows('blog_publishing');
+            // ...
+
+            $blogPublishing->definitionValidators([
+                App\Workflow\Validator\BlogPublishingValidator::class
+            ]);
+
+            // ...
+        };
+
+The ``BlogPublishingValidator`` will be executed during container compilation
+to validate the workflow definition.
+
+.. versionadded:: 7.3
+
+    Support for workflow definition validators was introduced in Symfony 7.3.
+
 Learn more
 ----------
 
