From 6ae48a88672d49b781535a02f73ad971d4ca87b7 Mon Sep 17 00:00:00 2001 From: Jesse Rushlow Date: Sun, 21 Apr 2024 04:11:06 -0400 Subject: [PATCH 001/120] [security] `make:security:custom` Since MakerBundle `v1.59.0` - you can call `make:security:custom` to generate a simple, no-frills, custom authenticator based off the example in the docs. --- security/custom_authenticator.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/security/custom_authenticator.rst b/security/custom_authenticator.rst index c57a2d84c23..3ec158d4e76 100644 --- a/security/custom_authenticator.rst +++ b/security/custom_authenticator.rst @@ -5,7 +5,8 @@ Symfony comes with :ref:`many authenticators ` and third party bundles also implement more complex cases like JWT and oAuth 2.0. However, sometimes you need to implement a custom authentication mechanism that doesn't exist yet or you need to customize one. In such -cases, you must create and use your own authenticator. +cases, you can use the ``make:security:custom`` command to create your own +authenticator. Authenticators should implement the :class:`Symfony\\Component\\Security\\Http\\Authenticator\\AuthenticatorInterface`. From f9046f9abf1d47145d29d592f36fdad7f3db3a56 Mon Sep 17 00:00:00 2001 From: Vincent Chalamon <407859+vincentchalamon@users.noreply.github.com> Date: Fri, 17 Jan 2025 10:30:22 +0100 Subject: [PATCH 002/120] docs: add OIDC Discovery documentation --- security/access_token.rst | 145 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 145 insertions(+) diff --git a/security/access_token.rst b/security/access_token.rst index c0ff4692676..0370949e584 100644 --- a/security/access_token.rst +++ b/security/access_token.rst @@ -411,6 +411,72 @@ and retrieve the user info: ; }; +To enable the `OpenID Connect Discovery`_, the ``OidcUserInfoTokenHandler`` +requires the ``symfony/cache`` package to store the OIDC configuration in +cache. If you haven't installed it yet, run this command: + +.. code-block:: terminal + + $ composer require symfony/cache + +Then, configure the ``base_uri`` and ``discovery`` keys: + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/security.yaml + security: + firewalls: + main: + access_token: + token_handler: + oidc_user_info: + base_uri: https://www.example.com/realms/demo/ + discovery: + cache: cache.app + + .. code-block:: xml + + + + + + + + + + + + + + + + + + + .. code-block:: php + + // config/packages/security.php + use Symfony\Config\SecurityConfig; + + return static function (SecurityConfig $security) { + $security->firewall('main') + ->accessToken() + ->tokenHandler() + ->oidcUserInfo() + ->baseUri('https://www.example.com/realms/demo/') + ->discovery() + ->cache('cache.app') + ; + }; + Following the `OpenID Connect Specification`_, the ``sub`` claim is used as user identifier by default. To use another claim, specify it on the configuration: @@ -625,6 +691,84 @@ it and retrieve the user info from it: The support of multiple algorithms to sign the JWS was introduced in Symfony 7.1. In previous versions, only the ``ES256`` algorithm was supported. +To enable the `OpenID Connect Discovery`_, the ``OidcTokenHandler`` +requires the ``symfony/cache`` package to store the OIDC configuration in +cache. If you haven't installed it yet, run this command: + +.. code-block:: terminal + + $ composer require symfony/cache + +Then, you can remove the ``keyset`` configuration key (it will be imported from +the OpenID Connect Discovery), and configure the ``discovery`` key: + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/security.yaml + security: + firewalls: + main: + access_token: + token_handler: + oidc: + claim: email + algorithms: ['ES256', 'RS256'] + audience: 'api-example' + issuers: ['https://oidc.example.com'] + discovery: + base_uri: https://www.example.com/realms/demo/ + cache: cache.app + + .. code-block:: xml + + + + + + + + + + + ES256 + RS256 + https://oidc.example.com + + + + + + + + + .. code-block:: php + + // config/packages/security.php + use Symfony\Config\SecurityConfig; + + return static function (SecurityConfig $security) { + $security->firewall('main') + ->accessToken() + ->tokenHandler() + ->oidc() + ->claim('email') + ->algorithms(['ES256', 'RS256']) + ->audience('api-example') + ->issuers(['https://oidc.example.com']) + ->discovery() + ->baseUri('https://www.example.com/realms/demo/') + ->cache('cache.app') + ; + }; + Following the `OpenID Connect Specification`_, the ``sub`` claim is used by default as user identifier. To use another claim, specify it on the configuration: @@ -925,5 +1069,6 @@ for :ref:`stateless firewalls `. .. _`JSON Web Tokens (JWT)`: https://datatracker.ietf.org/doc/html/rfc7519 .. _`OpenID Connect (OIDC)`: https://en.wikipedia.org/wiki/OpenID#OpenID_Connect_(OIDC) .. _`OpenID Connect Specification`: https://openid.net/specs/openid-connect-core-1_0.html +.. _`OpenID Connect Discovery`: https://openid.net/specs/openid-connect-discovery-1_0.html .. _`RFC6750`: https://datatracker.ietf.org/doc/html/rfc6750 .. _`SAML2 (XML structures)`: https://docs.oasis-open.org/security/saml/Post2.0/sstc-saml-tech-overview-2.0.html From 5ca3039e8bf1344368e098a83ac47749aeff7748 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C4=9Bj=20Hump=C3=A1l?= Date: Mon, 14 Apr 2025 12:04:35 +0200 Subject: [PATCH 003/120] Specify explicitly what a priority of message handlers mean --- messenger.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messenger.rst b/messenger.rst index 484a5cbdaff..f504a470336 100644 --- a/messenger.rst +++ b/messenger.rst @@ -2468,7 +2468,7 @@ Possible options to configure with tags are: Name of the method that will process the message. ``priority`` - Priority of the handler when multiple handlers can process the same message. + Priority of the handler when multiple handlers can process the same message; higher values will be processed first. .. _handler-subscriber-options: From 454536494fc670d15c38df55290d76246cad6798 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Mon, 5 May 2025 09:16:56 +0200 Subject: [PATCH 004/120] fix default value for the "collect_serializer_data" option --- reference/configuration/framework.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reference/configuration/framework.rst b/reference/configuration/framework.rst index 56a7dfe54b1..b9ce997e128 100644 --- a/reference/configuration/framework.rst +++ b/reference/configuration/framework.rst @@ -2354,7 +2354,7 @@ Combine it with the ``collect`` option to enable/disable the profiler on demand: collect_serializer_data ....................... -**type**: ``boolean`` **default**: ``true`` +**type**: ``boolean`` **default**: ``false`` When this option is ``true``, all normalizers and encoders are decorated by traceable implementations that collect profiling information about them. From ac6a2587b2e32adf557aaccf86f040f8668d4041 Mon Sep 17 00:00:00 2001 From: Florent Morselli Date: Thu, 8 May 2025 13:54:11 +0200 Subject: [PATCH 005/120] Introduce user identifier normalization for enhanced consistency Added support for normalizing user identifiers in Symfony 7.3 to ensure consistent comparison and storage. Updated documentation with implementation examples, highlighting best practices like converting identifiers to lowercase and handling various formats. This improvement reduces the risk of duplicates and improves reliability in user authentication. --- security/custom_authenticator.rst | 98 ++++++++++++++++++++++++++++++- 1 file changed, 95 insertions(+), 3 deletions(-) diff --git a/security/custom_authenticator.rst b/security/custom_authenticator.rst index 8b2ec9d7f34..5877194ab4e 100644 --- a/security/custom_authenticator.rst +++ b/security/custom_authenticator.rst @@ -209,13 +209,20 @@ requires a user and some sort of "credentials" (e.g. a password). Use the :class:`Symfony\\Component\\Security\\Http\\Authenticator\\Passport\\Badge\\UserBadge` to attach the user to the passport. The ``UserBadge`` requires a user -identifier (e.g. the username or email), which is used to load the user -using :ref:`the user provider `:: +identifier (e.g. the username or email):: use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; // ... - $passport = new Passport(new UserBadge($email), $credentials); + $passport = new Passport(new UserBadge($userIdentifier), $credentials); + +User Identifier +~~~~~~~~~~~~~~~ + +The user identifier is a unique string that identifies the user. It is used +to load the user using :ref:`the user provider `. +This identifier is often something like the user's email address or username, +but it could be any unique value associated with the user. .. note:: @@ -255,6 +262,91 @@ using :ref:`the user provider `:: } } +It is a good practice to normalize the user identifier before using it. +For example, this ensures that variations such as "john.doe", "John.Doe", +or "JOHN.DOE" refer to the same user. +Normalization can include converting the identifier to lowercase +and trimming unnecessary spaces. +You can optionally pass a user identifier normalizer as third argument to the +``UserBadge``. This callable receives the ``$userIdentifier`` +and must return a normalized user identifier as a string. + +.. versionadded:: 7.3 + + The support of the user identifier normalizer was introduced in Symfony 7.3. + +For instance, the example below uses a normalizer that converts usernames to a normalized, ASCII-only, lowercase format, +suitable for consistent comparison and storage. + + // src/Security/NormalizedUserBadge.php + namespace App\Security; + + use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; + use Symfony\Component\String\UnicodeString; + use function Symfony\Component\String\u; + + final class NormalizedUserBadge extends UserBadge + { + public function __construct(string $identifier) + { + $callback = static fn (string $identifier) => u($identifier)->normalize(UnicodeString::NFKC)->ascii()->lower()->toString(); + + parent::__construct($identifier, null, $callback); + } + } + + // src/Security/PasswordAuthenticator.php + namespace App\Security; + + final class PasswordAuthenticator extends AbstractLoginFormAuthenticator + { + // Simplified for brievety + public function authenticate(Request $request): Passport + { + $username = (string) $request->request->get('username', ''); + $password = (string) $request->request->get('password', ''); + + $request->getSession() + ->set(SecurityRequestAttributes::LAST_USERNAME, $username); + + return new Passport( + new NormalizedUserBadge($username), + new PasswordCredentials($password), + [ + //All other useful badges + ] + ); + } + } + +.. note:: + + For example, Google treats the following email addresses as equivalent: + ``john.doe@gmail.com``, ``j.hon.d.oe@gmail.com``, and ``johndoe@gmail.com``. + This is because Google applies normalization rules that remove dots + and convert the address to lowercase (though behavior varies across services). + +.. note:: + + In enterprise environments, a user may authenticate using different formats + of their identifier, such as: + + - ``john.doe@acme.com`` + - ``acme.com\jdoe`` + - ``https://acme.com/+jdoe`` + - ``acct:jdoe@acme.com`` + + Applying normalization (e.g., trimming, lowercasing, or format unification) + helps ensure consistent identity recognition across systems and prevents + duplicates caused by format variations. + +User Credential +~~~~~~~~~~~~~~~ + +The user credential is used to authenticate the user i.e. to verify +the validity of the provided information (such as a password, an API token, +or other custom credentials). + The following credential classes are supported by default: :class:`Symfony\\Component\\Security\\Http\\Authenticator\\Passport\\Credentials\\PasswordCredentials` From 280cd5a506ee06549d8e072f806208a56dd30d73 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sun, 11 May 2025 18:15:53 +0200 Subject: [PATCH 006/120] [Cache] Mention Valkey is Redis --- components/cache/adapters/redis_adapter.rst | 6 ++++++ configuration/secrets.rst | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/components/cache/adapters/redis_adapter.rst b/components/cache/adapters/redis_adapter.rst index 3362f4cc2db..e029b51b0d3 100644 --- a/components/cache/adapters/redis_adapter.rst +++ b/components/cache/adapters/redis_adapter.rst @@ -21,6 +21,11 @@ to utilize a cluster of servers to provide redundancy and/or fail-over is also a adapter. Additionally, this adapter requires a compatible extension or library that implements ``\Redis``, ``\RedisArray``, ``RedisCluster``, ``\Relay\Relay`` or ``\Predis``. +.. note:: + + This adapter also works with `Valkey`_ servers and as of Symfony 7.3, you can use the ``valkey[s]:`` schemes + instead of the ``redis[s]:`` ones in your DSNs. + This adapter expects a `Redis`_, `RedisArray`_, `RedisCluster`_, `Relay`_ or `Predis`_ instance to be passed as the first parameter. A namespace and default cache lifetime can optionally be passed as the second and third parameters:: @@ -348,6 +353,7 @@ Supports key rotation, ensuring secure decryption with both old and new keys:: .. _`Data Source Name (DSN)`: https://en.wikipedia.org/wiki/Data_source_name .. _`Redis server`: https://redis.io/ +.. _`Valkey`: https://valkey.io/ .. _`Redis`: https://github.com/phpredis/phpredis .. _`RedisArray`: https://github.com/phpredis/phpredis/blob/develop/arrays.md .. _`RedisCluster`: https://github.com/phpredis/phpredis/blob/develop/cluster.md diff --git a/configuration/secrets.rst b/configuration/secrets.rst index f717456a22c..285b89d521e 100644 --- a/configuration/secrets.rst +++ b/configuration/secrets.rst @@ -311,7 +311,7 @@ The secrets system is enabled by default and some of its behavior can be configu xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd http://symfony.com/schema/dic/framework https://symfony.com/schema/dic/framework/framework-1.0.xsd" > - + Date: Sat, 10 May 2025 13:25:03 +0200 Subject: [PATCH 007/120] [TwigBundle] Describe the new behaviour of twig.cache config Signed-off-by: Quentin Devos <4972091+Okhoshi@users.noreply.github.com> --- reference/configuration/twig.rst | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/reference/configuration/twig.rst b/reference/configuration/twig.rst index 3c4dc1b30ac..13d0463970a 100644 --- a/reference/configuration/twig.rst +++ b/reference/configuration/twig.rst @@ -71,7 +71,7 @@ application harder to maintain. cache ~~~~~ -**type**: ``string`` | ``false`` **default**: ``%kernel.cache_dir%/twig`` +**type**: ``string`` | ``boolean`` **default**: ``true`` Before using the Twig templates to render some contents, they are compiled into regular PHP code. Compilation is a costly process, so the result is cached in @@ -82,6 +82,16 @@ is not recommended; not even in the ``dev`` environment, because the ``auto_reload`` option ensures that cached templates which have changed get compiled again. +Specify the path where the cache should be stored. If set to ``true``, the cache +defaults to ``%kernel.cache_dir%/twig``. However, if the ``auto_reload`` option is +disabled and ``%kernel.build_dir%`` is different from ``%kernel.cache_dir%``, +the cache will instead be stored in ``%kernel.build_dir%/twig``. + +.. versionadded:: 7.3 + + Support for ``true`` value was added in Symfony 7.3, and it became the default + value for this option instead of ``%kernel.cache_dir%/twig``. + charset ~~~~~~~ From 0ba6600aca5f21dddaeb8d3c68188de10c770b12 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Mon, 12 May 2025 08:06:02 +0200 Subject: [PATCH 008/120] Minor tweak --- components/cache/adapters/redis_adapter.rst | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/components/cache/adapters/redis_adapter.rst b/components/cache/adapters/redis_adapter.rst index e029b51b0d3..0d099bf66e8 100644 --- a/components/cache/adapters/redis_adapter.rst +++ b/components/cache/adapters/redis_adapter.rst @@ -8,7 +8,8 @@ Redis Cache Adapter :ref:`Symfony Cache configuration ` article if you are using it in a Symfony application. -This adapter stores the values in-memory using one (or more) `Redis server`_ instances. +This adapter stores the values in-memory using one (or more) `Redis server`_ +of `Valkey`_ server instances. Unlike the :doc:`APCu adapter `, and similarly to the :doc:`Memcached adapter `, it is not limited to the current server's @@ -21,11 +22,6 @@ to utilize a cluster of servers to provide redundancy and/or fail-over is also a adapter. Additionally, this adapter requires a compatible extension or library that implements ``\Redis``, ``\RedisArray``, ``RedisCluster``, ``\Relay\Relay`` or ``\Predis``. -.. note:: - - This adapter also works with `Valkey`_ servers and as of Symfony 7.3, you can use the ``valkey[s]:`` schemes - instead of the ``redis[s]:`` ones in your DSNs. - This adapter expects a `Redis`_, `RedisArray`_, `RedisCluster`_, `Relay`_ or `Predis`_ instance to be passed as the first parameter. A namespace and default cache lifetime can optionally be passed as the second and third parameters:: @@ -66,6 +62,11 @@ helper method allows creating and configuring the Redis client class instance us 'redis://localhost' ); +.. versionadded:: 7.3 + + Starting in Symfony 7.3, when using Valkey servers you can use the + ``valkey[s]:`` scheme instead of the ``redis[s]:`` one in your DSNs. + The DSN can specify either an IP/host (and an optional port) or a socket path, as well as a password and a database index. To enable TLS for connections, the scheme ``redis`` must be replaced by ``rediss`` (the second ``s`` means "secure"). From fcf4ecbd598f66748ee16bd108ee013a444ea285 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Mon, 12 May 2025 08:30:12 +0200 Subject: [PATCH 009/120] Minor reword --- reference/configuration/twig.rst | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/reference/configuration/twig.rst b/reference/configuration/twig.rst index 13d0463970a..360309fef8f 100644 --- a/reference/configuration/twig.rst +++ b/reference/configuration/twig.rst @@ -77,20 +77,21 @@ Before using the Twig templates to render some contents, they are compiled into regular PHP code. Compilation is a costly process, so the result is cached in the directory defined by this configuration option. -Set this option to ``false`` to disable Twig template compilation. However, this -is not recommended; not even in the ``dev`` environment, because the -``auto_reload`` option ensures that cached templates which have changed get -compiled again. +You can either specify a custom path where the cache should be stored (as a +string) or use ``true`` to let Symfony decide the default path. When set to +``true``, the cache is stored in ``%kernel.cache_dir%/twig`` by default. However, +if ``auto_reload`` is disabled and ``%kernel.build_dir%`` differs from +``%kernel.cache_dir%``, the cache will be stored in ``%kernel.build_dir%/twig`` instead. -Specify the path where the cache should be stored. If set to ``true``, the cache -defaults to ``%kernel.cache_dir%/twig``. However, if the ``auto_reload`` option is -disabled and ``%kernel.build_dir%`` is different from ``%kernel.cache_dir%``, -the cache will instead be stored in ``%kernel.build_dir%/twig``. +Set this option to ``false`` to disable Twig template compilation. However, this +is not recommended, not even in the ``dev`` environment, because the ``auto_reload`` +option ensures that cached templates which have changed get compiled again. .. versionadded:: 7.3 - Support for ``true`` value was added in Symfony 7.3, and it became the default - value for this option instead of ``%kernel.cache_dir%/twig``. + Support for using ``true`` as a value was introduced in Symfony 7.3. It also + became the default value for this option, replacing the explicit path + ``%kernel.cache_dir%/twig``. charset ~~~~~~~ From 88b4a70dc2c3cd498f7bd69c1511eb9232cda1e8 Mon Sep 17 00:00:00 2001 From: Anton Date: Sat, 10 May 2025 10:53:28 +0300 Subject: [PATCH 010/120] Update asset_mapper.rst Adds tip about configuration proxy when connection to jsDelivr is failed --- frontend/asset_mapper.rst | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/frontend/asset_mapper.rst b/frontend/asset_mapper.rst index 061c4598bfa..a3c4eda2d84 100644 --- a/frontend/asset_mapper.rst +++ b/frontend/asset_mapper.rst @@ -225,6 +225,25 @@ This adds the ``bootstrap`` package to your ``importmap.php`` file:: `package.json configuration file`_. Try to contact the package maintainer to ask them to fix those issues. +.. tip:: + + If you get a error like as ``Connection was reset for "https://cdn.jsdelivr.net/npm/...".``, + than you can temporaly configure the proxy for connection to the ``jsDelivr`` CDN: + + .. code-block:: yaml + + # config/packages/framework.yaml + framework: + # ... + http_client: + default_options: + proxy: '185.250.180.238:8080' + # If you use CURL, add extra options: + extra: + curl: + # 61 is value of constant CURLOPT_HTTPPROXYTUNNEL + '61': true + Now you can import the ``bootstrap`` package like usual: .. code-block:: javascript From 621b57b99e822f049e8af80240f5525ef665cfa7 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Mon, 12 May 2025 09:15:23 +0200 Subject: [PATCH 011/120] Minor tweak --- frontend/asset_mapper.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/frontend/asset_mapper.rst b/frontend/asset_mapper.rst index a3c4eda2d84..94362b217ef 100644 --- a/frontend/asset_mapper.rst +++ b/frontend/asset_mapper.rst @@ -227,8 +227,9 @@ This adds the ``bootstrap`` package to your ``importmap.php`` file:: .. tip:: - If you get a error like as ``Connection was reset for "https://cdn.jsdelivr.net/npm/...".``, - than you can temporaly configure the proxy for connection to the ``jsDelivr`` CDN: + If you see a network error like *Connection was reset for "https://cdn.jsdelivr.net/npm/..."*, + it may be caused by a proxy or firewall restriction. In that case, you can + temporarily configure a proxy to connect to the ``jsDelivr`` CDN: .. code-block:: yaml @@ -238,7 +239,7 @@ This adds the ``bootstrap`` package to your ``importmap.php`` file:: http_client: default_options: proxy: '185.250.180.238:8080' - # If you use CURL, add extra options: + # if you use CURL, add extra options: extra: curl: # 61 is value of constant CURLOPT_HTTPPROXYTUNNEL From ca203cfe621fac81b29b3a7f256f14bfbaaa29b7 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 12 May 2025 10:39:13 +0200 Subject: [PATCH 012/120] [Security] Tell about erasing credentials when the user is stored in the session --- reference/configuration/security.rst | 5 ++++ security.rst | 35 +++++++++++++++++++++------- 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/reference/configuration/security.rst b/reference/configuration/security.rst index 6f4fcd8db33..72970a845d8 100644 --- a/reference/configuration/security.rst +++ b/reference/configuration/security.rst @@ -53,6 +53,11 @@ erase_credentials If ``true``, the ``eraseCredentials()`` method of the user object is called after authentication. +.. deprecated:: 7.3 + + Since Symfony 7.3, ``eraseCredentials()`` methods are deprecated and are + not called if they have the ``#[\Deprecated]`` attribute. + hide_user_not_found ------------------- diff --git a/security.rst b/security.rst index 847f90a1e2c..36b56e115ee 100644 --- a/security.rst +++ b/security.rst @@ -193,14 +193,7 @@ from the `MakerBundle`_: return $this; } - /** - * @see UserInterface - */ - public function eraseCredentials(): void - { - // If you store any temporary, sensitive data on the user, clear it here - // $this->plainPassword = null; - } + // [...] } .. tip:: @@ -2786,7 +2779,31 @@ object) are "compared" to see if they are "equal". By default, the core your user will be logged out. This is a security measure to make sure that malicious users can be de-authenticated if core user data changes. -However, in some cases, this process can cause unexpected authentication problems. +Note that storing the (plain or hashed) password in the session storage can be seen +as a security risk. In order to address this risk, the ``__serialize()`` magic method +can be implemented on the user class to filter out the password before storing the +serialized user object in the session. +Two strategies are supported while serializing: + +#. Removing the password entirely. In this case, ``getPassword()`` will return ``null`` + after unserialization and Symfony will refresh the user without checking the + password. Use this strategy if you store plaintext passwords (not recommended.) +#. Hashing the password using the ``crc32c`` algorithm. In this case Symfony will + compare the password of the refreshed user after crc32c-hashing it. This is a good + strategy if you use hashed passwords since it allows invalidating concurrent + sessions when a password changes without storing the password hash in the session. + + Here is an example of how to implement this, assuming the password is found in a + private property named ``password``:: + + public function __serialize(): array + { + $data = (array) $this; + $data["\0".self::class."\0password"] = hash('crc32c', $this->password); + + return $data; + } + If you're having problems authenticating, it could be that you *are* authenticating successfully, but you immediately lose authentication after the first redirect. From 51200b2758387d9ef9ef6d689f3e750e3881fbf8 Mon Sep 17 00:00:00 2001 From: Maxime Pinot Date: Mon, 12 May 2025 11:08:17 +0200 Subject: [PATCH 013/120] [Security] [Best practices] Remove mention of `anonymous` --- best_practices.rst | 4 ---- 1 file changed, 4 deletions(-) diff --git a/best_practices.rst b/best_practices.rst index 2c393cae9c6..b887d4d289a 100644 --- a/best_practices.rst +++ b/best_practices.rst @@ -362,10 +362,6 @@ Unless you have two legitimately different authentication systems and users (e.g. form login for the main site and a token system for your API only), it's recommended to have only one firewall to keep things simple. -Additionally, you should use the ``anonymous`` key under your firewall. If you -require users to be logged in for different sections of your site, use the -:doc:`access_control ` option. - Use the ``auto`` Password Hasher ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From 33bd9a8976e2b25fa909a16695a0dcb5ab9cefd4 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 12 May 2025 12:14:44 +0200 Subject: [PATCH 014/120] Update doc about lazy objects --- components/var_exporter.rst | 42 +++++++++++++++++++++++++++-- service_container/lazy_services.rst | 8 ------ 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/components/var_exporter.rst b/components/var_exporter.rst index fc6b34868db..dc83763d579 100644 --- a/components/var_exporter.rst +++ b/components/var_exporter.rst @@ -177,8 +177,34 @@ populated by using the special ``"\0"`` property name to define their internal v "\0" => [$inputArray], ]); -Creating Lazy Objects ---------------------- +Creating Lazy Objects on PHP ≥ 8.4 +---------------------------------- + +Since version 8.4, PHP provides support for lazy objects via the reflection API. +This native API works with concrete classes. It doesn't with abstracts nor with +internal ones. + +This components provides helpers to generate lazy objects using the decorator +pattern, which works with abstract or internal classes and with interfaces:: + + $proxyCode = ProxyHelper::generateLazyProxy(new \ReflectionClass(SomeInterface::class)); + // $proxyCode should be dumped into a file in production envs + eval('class ProxyDecorator'.$proxyCode); + + $proxy = ProxyDecorator::createLazyProxy(initializer: function (): SomeInterface { + // [...] Use whatever heavy logic you need here + // to compute the $dependencies of the proxied class + $instance = new SomeHeavyClass(...$dependencies); + // [...] Call setters, etc. if needed + + return $instance; + }); + +Use this mechanism only when native lazy objects cannot be leveraged +(or you'll get a deprecation notice.) + +Creating Lazy Objects on PHP < 8.3 +---------------------------------- Lazy-objects are objects instantiated empty and populated on-demand. This is particularly useful when you have for example properties in your classes that @@ -193,6 +219,12 @@ you implement such mechanism easily in your classes. LazyGhostTrait ~~~~~~~~~~~~~~ +.. deprecated:: 7.3 + + ``LazyGhostTrait`` is deprecated since Symfony 7.3; use PHP 8.4's native lazy + objects instead (note that using the trait with PHP < 8.4 triggers no deprecation + to help with the transition.) + Ghost objects are empty objects, which see their properties populated the first time any method is called. Thanks to :class:`Symfony\\Component\\VarExporter\\LazyGhostTrait`, the implementation of the lazy mechanism is eased. The ``MyLazyObject::populateHash()`` @@ -273,6 +305,12 @@ of :ref:`Virtual Proxies `. LazyProxyTrait ~~~~~~~~~~~~~~ +.. deprecated:: 7.3 + + ``LazyProxyTrait`` is deprecated since Symfony 7.3; use PHP 8.4's native lazy + objects instead (note that using the trait with PHP < 8.4 triggers no deprecation + to help with the transition.) + The purpose of virtual proxies in the same one as :ref:`ghost objects `, but their internal behavior is totally different. Where ghost objects requires to extend a base class, virtual diff --git a/service_container/lazy_services.rst b/service_container/lazy_services.rst index 23d76a4cfbf..abb3c2cca7f 100644 --- a/service_container/lazy_services.rst +++ b/service_container/lazy_services.rst @@ -26,9 +26,6 @@ until you interact with the proxy in some way. Lazy services do not support `final`_ or ``readonly`` classes, but you can use `Interface Proxifying`_ to work around this limitation. - In PHP versions prior to 8.0 lazy services do not support parameters with - default values for built-in PHP classes (e.g. ``PDO``). - .. _lazy-services_configuration: Configuration @@ -78,11 +75,6 @@ same signature of the class representing the service should be injected. A lazy itself when being accessed for the first time). The same happens when calling ``Container::get()`` directly. -To check if your lazy service works you can check the interface of the received object:: - - dump(class_implements($service)); - // the output should include "Symfony\Component\VarExporter\LazyObjectInterface" - You can also configure your service's laziness thanks to the :class:`Symfony\\Component\\DependencyInjection\\Attribute\\Autoconfigure` attribute. For example, to define your service as lazy use the following:: From 8459dff249cf3e83ad19957a9bf9664e0e7411e2 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Mon, 12 May 2025 15:42:25 +0200 Subject: [PATCH 015/120] Minor tweaks --- reference/configuration/security.rst | 15 +++++++++++- security.rst | 36 +++++++++++++++------------- 2 files changed, 34 insertions(+), 17 deletions(-) diff --git a/reference/configuration/security.rst b/reference/configuration/security.rst index 72970a845d8..c8609c20c9c 100644 --- a/reference/configuration/security.rst +++ b/reference/configuration/security.rst @@ -51,7 +51,20 @@ erase_credentials **type**: ``boolean`` **default**: ``true`` If ``true``, the ``eraseCredentials()`` method of the user object is called -after authentication. +after authentication:: + + use Symfony\Component\Security\Core\User\UserInterface; + + class User implements UserInterface + { + // ... + + public function eraseCredentials(): void + { + // If you store any temporary, sensitive data on the user, clear it here + // $this->plainPassword = null; + } + } .. deprecated:: 7.3 diff --git a/security.rst b/security.rst index 36b56e115ee..82f26957f02 100644 --- a/security.rst +++ b/security.rst @@ -2779,22 +2779,21 @@ object) are "compared" to see if they are "equal". By default, the core your user will be logged out. This is a security measure to make sure that malicious users can be de-authenticated if core user data changes. -Note that storing the (plain or hashed) password in the session storage can be seen -as a security risk. In order to address this risk, the ``__serialize()`` magic method -can be implemented on the user class to filter out the password before storing the -serialized user object in the session. -Two strategies are supported while serializing: - -#. Removing the password entirely. In this case, ``getPassword()`` will return ``null`` - after unserialization and Symfony will refresh the user without checking the - password. Use this strategy if you store plaintext passwords (not recommended.) -#. Hashing the password using the ``crc32c`` algorithm. In this case Symfony will - compare the password of the refreshed user after crc32c-hashing it. This is a good - strategy if you use hashed passwords since it allows invalidating concurrent - sessions when a password changes without storing the password hash in the session. - - Here is an example of how to implement this, assuming the password is found in a - private property named ``password``:: +Storing the (plain or hashed) password in the session can be a security risk. +To mitigate this, implement the ``__serialize()`` magic method in your user class +to exclude or transform the password before storing the serialized user object +in the session. + +Two strategies are supported: + +#. Remove the password completely. After unserialization, ``getPassword()`` returns + ``null`` and Symfony refreshes the user without checking the password. Use this + only if you store plaintext passwords (not recommended). +#. Hash the password using the ``crc32c`` algorithm. Symfony will hash the password + of the refreshed user and compare it to the session value. This approach avoids + storing the real hash and lets you invalidate sessions on password change. + + Example (assuming the password is stored in a private property called ``password``):: public function __serialize(): array { @@ -2804,6 +2803,11 @@ Two strategies are supported while serializing: return $data; } +.. versionadded:: 7.3 + + Support for hashing passwords with ``crc32c`` in session serialization was + introduced in Symfony 7.3. + If you're having problems authenticating, it could be that you *are* authenticating successfully, but you immediately lose authentication after the first redirect. From e899545ee62e4954ef4d35d06f46a42e5b659df5 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 12 May 2025 16:18:59 +0200 Subject: [PATCH 016/120] [Cache] Tell about namespace-based invalidation --- components/cache.rst | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/components/cache.rst b/components/cache.rst index 4873fb7abc7..e2bce1cf812 100644 --- a/components/cache.rst +++ b/components/cache.rst @@ -84,6 +84,33 @@ generate and return the value:: Use cache tags to delete more than one key at the time. Read more at :doc:`/components/cache/cache_invalidation`. +Creating Sub-Namespaces +----------------------- + +All cache adapters provided by the component implement the +:method:`Symfony\\Contracts\\Cache\\NamespacedPoolInterface::withSubNamespace` method +from the :class:`Symfony\\Contracts\\Cache\\NamespacedPoolInterface`. + +.. versionadded:: 7.3 + + Support for ``NamespacedPoolInterface`` was added in Symfony 7.3. + +This method allows namespacing cached items by transparently prefixing their keys:: + + $subCache = $cache->withSubNamespace('foo'); + + $subCache->get('my_cache_key', function (ItemInterface $item): string { + $item->expiresAfter(3600); + + return '...'; + }); + +In this example, cache item keyed ``my_cache_key`` will be transparently stored within +the cache pool under a logical namespace called ``foo``. + +Sub-namespacing allows implementing namespace-based cache invalidation, where the name +of a namespace is computed by hashing some context info. + .. _cache_stampede-prevention: Stampede Prevention From daad10ca8112f82376deb6cfe5e3e6790de0a2df Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Tue, 13 May 2025 09:12:55 +0200 Subject: [PATCH 017/120] Tweaks --- components/cache.rst | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/components/cache.rst b/components/cache.rst index e2bce1cf812..3c124c7d85e 100644 --- a/components/cache.rst +++ b/components/cache.rst @@ -87,14 +87,13 @@ generate and return the value:: Creating Sub-Namespaces ----------------------- -All cache adapters provided by the component implement the -:method:`Symfony\\Contracts\\Cache\\NamespacedPoolInterface::withSubNamespace` method -from the :class:`Symfony\\Contracts\\Cache\\NamespacedPoolInterface`. - .. versionadded:: 7.3 - Support for ``NamespacedPoolInterface`` was added in Symfony 7.3. + Cache sub-namespaces were introduced in Symfony 7.3. +All cache adapters provided by the component implement the +:class:`Symfony\\Contracts\\Cache\\NamespacedPoolInterface` to provide the +:method:`Symfony\\Contracts\\Cache\\NamespacedPoolInterface::withSubNamespace` method. This method allows namespacing cached items by transparently prefixing their keys:: $subCache = $cache->withSubNamespace('foo'); @@ -105,11 +104,17 @@ This method allows namespacing cached items by transparently prefixing their key return '...'; }); -In this example, cache item keyed ``my_cache_key`` will be transparently stored within -the cache pool under a logical namespace called ``foo``. +In this example, the cache item will use the ``my_cache_key`` key, but it will be +stored internally under the ``foo`` namespace. This is handled transparently for +you, so you **don't** need to manually prefix keys like ``foo.my_cache_key``. + +This is useful when using namespace-based cache invalidation to isolate or +invalidate a subset of cached data based on some context. Typical examples +include namespacing by user ID, locale, or entity ID and hash:: -Sub-namespacing allows implementing namespace-based cache invalidation, where the name -of a namespace is computed by hashing some context info. + $userCache = $cache->withSubNamespace((string) $userId); + $localeCache = $cache->withSubNamespace($request->getLocale()); + $productCache = $cache->withSubNamespace($productId.'_'.$productChecksum); .. _cache_stampede-prevention: From 849acdebaae132db61e2ede92be3f2c21718bdfc Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 12 May 2025 17:08:31 +0200 Subject: [PATCH 018/120] [DI] Tell about #[AutowireInline] --- service_container/autowiring.rst | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/service_container/autowiring.rst b/service_container/autowiring.rst index 32688fd4921..d76622db281 100644 --- a/service_container/autowiring.rst +++ b/service_container/autowiring.rst @@ -862,6 +862,38 @@ typed properties: } } +Autowiring Anonymous Services Inline +------------------------------------ + +.. versionadded:: 7.1 + + The ``#[AutowireInline]`` attribute was added in Symfony 7.1. + +Similar to how one can define anonymous services inline using configuration files, +the :class:`Symfony\\Component\\DependencyInjection\\Attribute\\AutowireInline` +attribute allows declaring anonymous services inline next to their corresponding +arguments:: + + public function __construct( + #[AutowireInline( + factory: [ScopingHttpClient::class, 'forBaseUri'], + arguments: [ + '$baseUri' => 'https://api.example.com', + '$defaultOptions' => [ + 'auth_bearer' => '%env(EXAMPLE_TOKEN)%', + ], + ] + )] + private HttpClientInterface $client, + ) { + } + +As you might have already figured out, this declaration instructs Symfony to inject an +object created by calling the ``ScopingHttpClient::forBaseUri()`` factory with the +configured base URI and default options. + +Of course, this is just an example and this attribute can be used to construct any kind of objects. + Autowiring Controller Action Methods ------------------------------------ From 7253f1bef0f23c788e83b5d7b36eace90073386f Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Wed, 14 May 2025 09:30:26 +0200 Subject: [PATCH 019/120] Minor tweaks --- service_container/autowiring.rst | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/service_container/autowiring.rst b/service_container/autowiring.rst index d76622db281..ea1bf1b12ff 100644 --- a/service_container/autowiring.rst +++ b/service_container/autowiring.rst @@ -869,10 +869,10 @@ Autowiring Anonymous Services Inline The ``#[AutowireInline]`` attribute was added in Symfony 7.1. -Similar to how one can define anonymous services inline using configuration files, +Similar to how anonymous services can be defined inline in configuration files, the :class:`Symfony\\Component\\DependencyInjection\\Attribute\\AutowireInline` -attribute allows declaring anonymous services inline next to their corresponding -arguments:: +attribute allows you to declare anonymous services inline, directly next to their +corresponding arguments:: public function __construct( #[AutowireInline( @@ -888,11 +888,13 @@ arguments:: ) { } -As you might have already figured out, this declaration instructs Symfony to inject an -object created by calling the ``ScopingHttpClient::forBaseUri()`` factory with the -configured base URI and default options. +This example tells Symfony to inject an object created by calling the +``ScopingHttpClient::forBaseUri()`` factory with the specified base URI and +default options. This is just one example: you can use the ``#[AutowireInline]`` +attribute to define any kind of anonymous service. -Of course, this is just an example and this attribute can be used to construct any kind of objects. +While this approach is convenient for simple service definitions, consider moving +complex or heavily configured services to a configuration file to ease maintenance. Autowiring Controller Action Methods ------------------------------------ From 5de8d6ba7f95c16a7cfea69e540f363927e8f7f9 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Wed, 14 May 2025 17:20:51 +0200 Subject: [PATCH 020/120] Minor tweaks --- components/var_exporter.rst | 56 ++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/components/var_exporter.rst b/components/var_exporter.rst index dc83763d579..c7ec9cd90d0 100644 --- a/components/var_exporter.rst +++ b/components/var_exporter.rst @@ -177,53 +177,53 @@ populated by using the special ``"\0"`` property name to define their internal v "\0" => [$inputArray], ]); -Creating Lazy Objects on PHP ≥ 8.4 ----------------------------------- +Creating Lazy Objects +--------------------- -Since version 8.4, PHP provides support for lazy objects via the reflection API. -This native API works with concrete classes. It doesn't with abstracts nor with -internal ones. +Lazy objects are objects instantiated empty and populated on demand. This is +particularly useful when, for example, a class has properties that require +heavy computation to determine their values. In such cases, you may want to +trigger the computation only when the property is actually accessed. This way, +the expensive processing is avoided entirely if the property is never used. -This components provides helpers to generate lazy objects using the decorator -pattern, which works with abstract or internal classes and with interfaces:: +Since version 8.4, PHP provides support for lazy objects via the reflection API. +This native API works with concrete classes, but not with abstract or internal ones. +This component provides helpers to generate lazy objects using the decorator +pattern, which also works with abstract classes, internal classes, and interfaces:: $proxyCode = ProxyHelper::generateLazyProxy(new \ReflectionClass(SomeInterface::class)); - // $proxyCode should be dumped into a file in production envs + // $proxyCode should be dumped into a file in production environments eval('class ProxyDecorator'.$proxyCode); $proxy = ProxyDecorator::createLazyProxy(initializer: function (): SomeInterface { - // [...] Use whatever heavy logic you need here + // use whatever heavy logic you need here // to compute the $dependencies of the proxied class $instance = new SomeHeavyClass(...$dependencies); - // [...] Call setters, etc. if needed + // call setters, etc. if needed return $instance; }); Use this mechanism only when native lazy objects cannot be leveraged -(or you'll get a deprecation notice.) +(otherwise you'll get a deprecation notice). -Creating Lazy Objects on PHP < 8.3 ----------------------------------- +Legacy Creation of Lazy Objects +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Lazy-objects are objects instantiated empty and populated on-demand. This is -particularly useful when you have for example properties in your classes that -requires some heavy computation to determine their value. In this case, you -may want to trigger the property's value processing only when you actually need -its value. Thanks to this, the heavy computation won't be done if you never use -this property. The VarExporter component is bundled with two traits helping -you implement such mechanism easily in your classes. +When using a PHP version earlier than 8.4, native lazy objects are not available. +In these cases, the VarExporter component provides two traits that help you +implement lazy-loading mechanisms in your classes. .. _var-exporter_ghost-objects: LazyGhostTrait -~~~~~~~~~~~~~~ +.............. .. deprecated:: 7.3 - ``LazyGhostTrait`` is deprecated since Symfony 7.3; use PHP 8.4's native lazy - objects instead (note that using the trait with PHP < 8.4 triggers no deprecation - to help with the transition.) + ``LazyGhostTrait`` is deprecated since Symfony 7.3. Use PHP 8.4's native lazy + objects instead. Note that using the trait with PHP versions earlier than 8.4 + does not trigger a deprecation, to ease the transition. Ghost objects are empty objects, which see their properties populated the first time any method is called. Thanks to :class:`Symfony\\Component\\VarExporter\\LazyGhostTrait`, @@ -303,13 +303,13 @@ of :ref:`Virtual Proxies `. .. _var-exporter_virtual-proxies: LazyProxyTrait -~~~~~~~~~~~~~~ +.............. .. deprecated:: 7.3 - ``LazyProxyTrait`` is deprecated since Symfony 7.3; use PHP 8.4's native lazy - objects instead (note that using the trait with PHP < 8.4 triggers no deprecation - to help with the transition.) + ``LazyProxyTrait`` is deprecated since Symfony 7.3. Use PHP 8.4's native lazy + objects instead. Note that using the trait with PHP versions earlier than 8.4 + does not trigger a deprecation, to ease the transition. The purpose of virtual proxies in the same one as :ref:`ghost objects `, but their internal behavior is From fc017846c4b5ecf1faf728b1f709fa7fef7ff901 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Santiago=20San=20Mart=C3=ADn?= Date: Wed, 14 May 2025 22:35:37 -0300 Subject: [PATCH 021/120] [Validator] avoid broken rendering of inline types in UniqueEntity This PR fixes an issue in the documentation where inline type annotations like | ``string`` | were not rendering correctly due to reStructuredText interpreting the | characters as substitution references. Changes: Replaced | ``string`` | with a more reliable format: boolean, string or array Ensures consistent rendering across Sphinx and other reST parsers This improves readability and avoids confusion when displaying valid type options in inline documentation. --- reference/constraints/UniqueEntity.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reference/constraints/UniqueEntity.rst b/reference/constraints/UniqueEntity.rst index 2c9aeccd755..0e1731371a5 100644 --- a/reference/constraints/UniqueEntity.rst +++ b/reference/constraints/UniqueEntity.rst @@ -277,7 +277,7 @@ each with a single field. ``ignoreNull`` ~~~~~~~~~~~~~~ -**type**: ``boolean`` | ``string`` | ``array`` **default**: ``true`` +**type**: ``boolean``, ``string`` or ``array`` **default**: ``true`` If this option is set to ``true``, then the constraint will allow multiple entities to have a ``null`` value for a field without failing validation. From 90e7bf98639ce9bbd6b60e5f4a7df47d5dc87e10 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Thu, 15 May 2025 08:18:51 +0200 Subject: [PATCH 022/120] Fix some content with wrong RST syntax --- reference/configuration/framework.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reference/configuration/framework.rst b/reference/configuration/framework.rst index e12c321b1fa..83cbc0e1135 100644 --- a/reference/configuration/framework.rst +++ b/reference/configuration/framework.rst @@ -3053,7 +3053,7 @@ php_errors log ... -**type**: ``boolean|int|array`` **default**: ``%kernel.debug%`` +**type**: ``boolean``, ``int`` or ``array`` **default**: ``%kernel.debug%`` Use the application logger instead of the PHP logger for logging PHP errors. When an integer value is used, it defines a bitmask of PHP errors that will From d2ac2ef0aea39441caa2fd871f890590915327f1 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Thu, 15 May 2025 12:56:12 +0200 Subject: [PATCH 023/120] [DependencyInjection] Fix a RST syntax issue --- components/dependency_injection.rst | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/components/dependency_injection.rst b/components/dependency_injection.rst index 93e8af711cf..d146f553a0c 100644 --- a/components/dependency_injection.rst +++ b/components/dependency_injection.rst @@ -180,16 +180,16 @@ You can override this behavior as follows:: These are all the possible behaviors: - * ``ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE``: throws an exception - at compile time (this is the **default** behavior); - * ``ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE``: throws an - exception at runtime, when trying to access the missing service; - * ``ContainerInterface::NULL_ON_INVALID_REFERENCE``: returns ``null``; - * ``ContainerInterface::IGNORE_ON_INVALID_REFERENCE``: ignores the wrapping - command asking for the reference (for instance, ignore a setter if the service - does not exist); - * ``ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE``: ignores/returns - ``null`` for uninitialized services or invalid references. +* ``ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE``: throws an exception + at compile time (this is the **default** behavior); +* ``ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE``: throws an + exception at runtime, when trying to access the missing service; +* ``ContainerInterface::NULL_ON_INVALID_REFERENCE``: returns ``null``; +* ``ContainerInterface::IGNORE_ON_INVALID_REFERENCE``: ignores the wrapping + command asking for the reference (for instance, ignore a setter if the service + does not exist); +* ``ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE``: ignores/returns + ``null`` for uninitialized services or invalid references. Avoiding your Code Becoming Dependent on the Container ------------------------------------------------------ From 5ad20ab99619534ee744c0efc2498fe878536bc7 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Thu, 15 May 2025 15:27:25 +0200 Subject: [PATCH 024/120] [Cache] Improve the docs about cache namespaces --- cache.rst | 2 ++ components/cache.rst | 59 +++++++++++++++++++++++++++++++++----------- 2 files changed, 47 insertions(+), 14 deletions(-) diff --git a/cache.rst b/cache.rst index 83bb5b4cedc..35f1404d42a 100644 --- a/cache.rst +++ b/cache.rst @@ -539,6 +539,8 @@ Symfony stores the item automatically in all the missing pools. ; }; +.. _cache-using-cache-tags: + Using Cache Tags ---------------- diff --git a/components/cache.rst b/components/cache.rst index 3c124c7d85e..b476b23e313 100644 --- a/components/cache.rst +++ b/components/cache.rst @@ -91,30 +91,61 @@ Creating Sub-Namespaces Cache sub-namespaces were introduced in Symfony 7.3. -All cache adapters provided by the component implement the -:class:`Symfony\\Contracts\\Cache\\NamespacedPoolInterface` to provide the -:method:`Symfony\\Contracts\\Cache\\NamespacedPoolInterface::withSubNamespace` method. -This method allows namespacing cached items by transparently prefixing their keys:: +Sometimes you need to create context-dependent variations of data that should be +cached. For example, the data used to render a dashboard page may be expensive +to generate and unique per user, so you can't cache the same data for everyone. - $subCache = $cache->withSubNamespace('foo'); +In such cases, Symfony allows you to create different cache contexts using +namespaces. A cache namespace is an arbitrary string that identifies a set of +related cache items. All cache adapters provided by the component implement the +:class:`Symfony\\Contracts\\Cache\\NamespacedPoolInterface`, which provides the +:method:`Symfony\\Contracts\\Cache\\NamespacedPoolInterface::withSubNamespace` +method. - $subCache->get('my_cache_key', function (ItemInterface $item): string { +This method allows you to namespace cached items by transparently prefixing their keys:: + + $userCache = $cache->withSubNamespace(sprintf('user-%d', $user->getId())); + + $userCache->get('dashboard_data', function (ItemInterface $item): string { $item->expiresAfter(3600); return '...'; }); -In this example, the cache item will use the ``my_cache_key`` key, but it will be -stored internally under the ``foo`` namespace. This is handled transparently for -you, so you **don't** need to manually prefix keys like ``foo.my_cache_key``. +In this example, the cache item uses the ``dashboard_data`` key, but it will be +stored internally under a namespace based on the current user ID. This is handled +automatically, so you **don’t** need to manually prefix keys like ``user-27.dashboard_data``. -This is useful when using namespace-based cache invalidation to isolate or -invalidate a subset of cached data based on some context. Typical examples -include namespacing by user ID, locale, or entity ID and hash:: +There are no guidelines or restrictions on how to define cache namespaces. +You can make them as granular or as generic as your application requires: - $userCache = $cache->withSubNamespace((string) $userId); $localeCache = $cache->withSubNamespace($request->getLocale()); - $productCache = $cache->withSubNamespace($productId.'_'.$productChecksum); + + $flagCache = $cache->withSubNamespace( + $featureToggle->isEnabled('new_checkout') ? 'checkout-v2' : 'checkout-v1' + ); + + $channel = $request->attributes->get('_route')?->startsWith('api_') ? 'api' : 'web'; + $channelCache = $cache->withSubNamespace($channel); + +.. tip:: + + You can even combine cache namespaces with :ref:`cache tags ` + for more advanced needs. + +There is no built-in way to invalidate caches by namespace. Instead, the recommended +approach is to change the namespace itself. For this reason, it's common to include +static or dynamic versioning data in the cache namespace:: + + // for simple applications, an incrementing static version number may be enough + $userCache = $cache->withSubNamespace(sprintf('v1-user-%d', $user->getId())); + + // other applications may use dynamic versioning based on the date (e.g. monthly) + $userCache = $cache->withSubNamespace(sprintf('%s-user-%d', date('Ym'), $user->getId())); + + // or even invalidate the cache when the user data changes + $checksum = hash('xxh128', $user->getUpdatedAt()->format(DATE_ATOM)); + $userCache = $cache->withSubNamespace(sprintf('user-%d-%s', $user->getId(), $checksum)); .. _cache_stampede-prevention: From e0fcd1c3bd46b7bca6bd4a1eeb7e3c687d0d3cf6 Mon Sep 17 00:00:00 2001 From: Romain Monteil Date: Thu, 15 May 2025 15:20:18 +0200 Subject: [PATCH 025/120] [Workflow] Document custom workflow definition validator --- workflow.rst | 79 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/workflow.rst b/workflow.rst index 11b4005bb10..544f775cce0 100644 --- a/workflow.rst +++ b/workflow.rst @@ -1306,6 +1306,85 @@ In Twig templates, metadata is available via the ``workflow_metadata()`` functio

+Adding Custom Definition Validators +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Sometimes, you may want to add custom logics to validate your workflow definition. +To do this, you need to implement the +:class:`Symfony\\Component\\Workflow\\Validator\\DefinitionValidatorInterface`:: + + namespace App\Workflow\Validator; + + use Symfony\Component\Workflow\Definition; + use Symfony\Component\Workflow\Exception\InvalidDefinitionException; + use Symfony\Component\Workflow\Validator\DefinitionValidatorInterface; + + final class BlogPublishingValidator implements DefinitionValidatorInterface + { + public function validate(Definition $definition, string $name): void + { + if (!$definition->getMetadataStore()->getMetadata('title')) { + throw new InvalidDefinitionException(sprintf('The workflow metadata title is missing in Workflow "%s".', $name)); + } + } + } + +Once your definition validator is implemented, you can configure your workflow to use +it: + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/workflow.yaml + framework: + workflows: + blog_publishing: + # ... previous configuration + + definition_validators: + - App\Workflow\Validator\BlogPublishingValidator + + .. code-block:: xml + + + + + + + + App\Workflow\Validator\BlogPublishingValidator + + + + + .. code-block:: php + + // config/packages/workflow.php + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework): void { + $blogPublishing = $framework->workflows()->workflows('blog_publishing'); + // ... previous configuration + + $blogPublishing->definitionValidators([ + App\Workflow\Validator\BlogPublishingValidator::class + ]); + + // ... + }; + +The ``BlogPublishingValidator`` definition validator will be executed during the container compilation. + +.. versionadded:: 7.3 + + Support for defining custom workflow definition validators was introduced in Symfony 7.3. + Learn more ---------- From c32e885e529bc423bacedd93b8949314453642b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Thu, 15 May 2025 17:35:57 +0200 Subject: [PATCH 026/120] [WebLink] Hint that prerender is deprecated --- web_link.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/web_link.rst b/web_link.rst index 8602445313f..79fd51b8d02 100644 --- a/web_link.rst +++ b/web_link.rst @@ -146,7 +146,8 @@ The WebLink component provides the following Twig functions to send those hints: * ``prefetch()``: "identifies a resource that might be required by the next navigation, and that the user agent *should* fetch, such that the user agent can deliver a faster response once the resource is requested in the future". -* ``prerender()``: "identifies a resource that might be required by the next +* ``prerender()``: " **deprecated** and superseded by the `Speculation Rules API`_, + identifies a resource that might be required by the next navigation, and that the user agent *should* fetch and execute, such that the user agent can deliver a faster response once the resource is requested later". @@ -206,3 +207,4 @@ You can also add links to the HTTP response directly from controllers and servic .. _`Akamai`: https://http2.akamai.com/ .. _`link defined in the HTML specification`: https://html.spec.whatwg.org/dev/links.html#linkTypes .. _`PSR-13`: https://www.php-fig.org/psr/psr-13/ +.. _`Speculation Rules API`: https://developer.mozilla.org/docs/Web/API/Speculation_Rules_API From e228ab6178987867ea65dce15acb8e3de311f5bb Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Fri, 16 May 2025 08:09:04 +0200 Subject: [PATCH 027/120] Minor tweaks --- components/cache.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/cache.rst b/components/cache.rst index b476b23e313..44c08920a1e 100644 --- a/components/cache.rst +++ b/components/cache.rst @@ -114,7 +114,7 @@ This method allows you to namespace cached items by transparently prefixing thei In this example, the cache item uses the ``dashboard_data`` key, but it will be stored internally under a namespace based on the current user ID. This is handled -automatically, so you **don’t** need to manually prefix keys like ``user-27.dashboard_data``. +automatically, so you **don't** need to manually prefix keys like ``user-27.dashboard_data``. There are no guidelines or restrictions on how to define cache namespaces. You can make them as granular or as generic as your application requires: @@ -130,7 +130,7 @@ You can make them as granular or as generic as your application requires: .. tip:: - You can even combine cache namespaces with :ref:`cache tags ` + You can combine cache namespaces with :ref:`cache tags ` for more advanced needs. There is no built-in way to invalidate caches by namespace. Instead, the recommended From 4b8a58cfee6cd87fcae7df58f28ccb47a0534830 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Fri, 16 May 2025 08:56:43 +0200 Subject: [PATCH 028/120] Fix a minor synmtax issue --- components/cache.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/cache.rst b/components/cache.rst index 44c08920a1e..44ba8b5c151 100644 --- a/components/cache.rst +++ b/components/cache.rst @@ -117,7 +117,7 @@ stored internally under a namespace based on the current user ID. This is handle automatically, so you **don't** need to manually prefix keys like ``user-27.dashboard_data``. There are no guidelines or restrictions on how to define cache namespaces. -You can make them as granular or as generic as your application requires: +You can make them as granular or as generic as your application requires:: $localeCache = $cache->withSubNamespace($request->getLocale()); From db089701221d929afd3e2fb1dc820148d5e15031 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Fri, 16 May 2025 10:50:33 +0200 Subject: [PATCH 029/120] fix typo "a unsubstantial" -> "an unsubstantial" --- contributing/core_team.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contributing/core_team.rst b/contributing/core_team.rst index 7b3d667a14b..d776cd4ed93 100644 --- a/contributing/core_team.rst +++ b/contributing/core_team.rst @@ -197,7 +197,7 @@ Pull Request Merging Policy A pull request **can be merged** if: -* It is a :ref:`unsubstantial change `; +* It is an :ref:`unsubstantial change `; * 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 From 74ee9c8e8de3c7dd37c49701c62213d93675d1f1 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Fri, 16 May 2025 09:38:16 +0200 Subject: [PATCH 030/120] Use DOCtor-RST 1.68.0 --- .doctor-rst.yaml | 1 + .github/workflows/ci.yaml | 2 +- best_practices.rst | 2 +- components/config/caching.rst | 2 +- contributing/code_of_conduct/code_of_conduct.rst | 6 +++--- contributing/code_of_conduct/concrete_example_document.rst | 2 +- contributing/community/review-comments.rst | 2 +- contributing/diversity/further_reading.rst | 6 +++--- http_client.rst | 2 +- scheduler.rst | 2 +- 10 files changed, 14 insertions(+), 13 deletions(-) diff --git a/.doctor-rst.yaml b/.doctor-rst.yaml index 3e804e4485f..4c3947e4d29 100644 --- a/.doctor-rst.yaml +++ b/.doctor-rst.yaml @@ -49,6 +49,7 @@ rules: no_namespace_after_use_statements: ~ no_php_open_tag_in_code_block_php_directive: ~ no_space_before_self_xml_closing_tag: ~ + no_typographic_quotes: ~ non_static_phpunit_assertions: ~ only_backslashes_in_namespace_in_php_code_block: ~ only_backslashes_in_use_statements_in_php_code_block: ~ diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 497dfd9b430..fa36efbcbcf 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -72,7 +72,7 @@ jobs: key: ${{ runner.os }}-doctor-rst-${{ steps.extract_base_branch.outputs.branch }} - name: "Run DOCtor-RST" - uses: docker://oskarstark/doctor-rst:1.67.0 + uses: docker://oskarstark/doctor-rst:1.68.0 with: args: --short --error-format=github --cache-file=/github/workspace/.cache/doctor-rst.cache diff --git a/best_practices.rst b/best_practices.rst index b887d4d289a..6211d042f0b 100644 --- a/best_practices.rst +++ b/best_practices.rst @@ -95,7 +95,7 @@ Use Secrets for Sensitive Information ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ When your application has sensitive configuration, like an API key, you should -store those securely via :doc:`Symfony’s secrets management system `. +store those securely via :doc:`Symfony's secrets management system `. Use Parameters for Application Configuration ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/components/config/caching.rst b/components/config/caching.rst index 810db48107e..f30128a5f9d 100644 --- a/components/config/caching.rst +++ b/components/config/caching.rst @@ -3,7 +3,7 @@ Caching based on Resources When all configuration resources are loaded, you may want to process the configuration values and combine them all in one file. This file acts -like a cache. Its contents don’t have to be regenerated every time the +like a cache. Its contents don't have to be regenerated every time the application runs – only when the configuration resources are modified. For example, the Symfony Routing component allows you to load all routes, diff --git a/contributing/code_of_conduct/code_of_conduct.rst b/contributing/code_of_conduct/code_of_conduct.rst index 6202fdad424..ce14dd5ad0e 100644 --- a/contributing/code_of_conduct/code_of_conduct.rst +++ b/contributing/code_of_conduct/code_of_conduct.rst @@ -34,7 +34,7 @@ Examples of unacceptable behavior include: any kind * Trolling, insulting or derogatory comments, and personal or political attacks * Public or private harassment -* Publishing others’ private information, such as a physical or email address, +* Publishing others' private information, such as a physical or email address, without their explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting @@ -128,7 +128,7 @@ Attribution This Code of Conduct is adapted from the `Contributor Covenant`_, version 2.1, available at https://www.contributor-covenant.org/version/2/1/code_of_conduct.html -Community Impact Guidelines were inspired by `Mozilla’s code of conduct enforcement ladder`_. +Community Impact Guidelines were inspired by `Mozilla's code of conduct enforcement ladder`_. Related Documents ----------------- @@ -141,4 +141,4 @@ Related Documents concrete_example_document .. _Contributor Covenant: https://www.contributor-covenant.org -.. _Mozilla’s code of conduct enforcement ladder: https://github.com/mozilla/diversity +.. _Mozilla's code of conduct enforcement ladder: https://github.com/mozilla/diversity diff --git a/contributing/code_of_conduct/concrete_example_document.rst b/contributing/code_of_conduct/concrete_example_document.rst index 60ffe2527db..227a41df4a8 100644 --- a/contributing/code_of_conduct/concrete_example_document.rst +++ b/contributing/code_of_conduct/concrete_example_document.rst @@ -9,7 +9,7 @@ according to the Symfony code of conduct. Concrete Examples ----------------- -* Unwelcome comments regarding a person’s lifestyle choices and practices, +* Unwelcome comments regarding a person's lifestyle choices and practices, including those related to food, health, parenting, drugs, and employment; * Deliberate misgendering or use of `dead names`_ (The birth name of a person who has since changed their name, often a transgender person); diff --git a/contributing/community/review-comments.rst b/contributing/community/review-comments.rst index 5b9bc932205..331352bb5fd 100644 --- a/contributing/community/review-comments.rst +++ b/contributing/community/review-comments.rst @@ -28,7 +28,7 @@ constructive, respectful and helpful reviews and replies. welcoming place for everyone. **You are free to disagree with someone's opinions, but don't be disrespectful.** -It’s important to accept that many programming decisions are opinions. +It's important to accept that many programming decisions are opinions. Discuss trade-offs, which you prefer, and reach a resolution quickly. It's not about being right or wrong, but using what works. diff --git a/contributing/diversity/further_reading.rst b/contributing/diversity/further_reading.rst index 8bb07c39c97..b5f44047159 100644 --- a/contributing/diversity/further_reading.rst +++ b/contributing/diversity/further_reading.rst @@ -9,7 +9,7 @@ Diversity in Open Source `Sage Sharp - What makes a good community? `_ `Ashe Dryden - The Ethics of Unpaid Labor and the OSS Community `_ `Model View Culture - The Dehumanizing Myth of the Meritocracy `_ -`Annalee - How “Good Intent” Undermines Diversity and Inclusion `_ +`Annalee - How "Good Intent" Undermines Diversity and Inclusion `_ `Karolina Szczur - Building Inclusive Communities `_ Code of Conduct @@ -22,7 +22,7 @@ Code of Conduct Inclusive language ------------------ -`Jenée Desmond-Harris - Why I’m finally convinced it's time to stop saying "you guys" `_ +`Jenée Desmond-Harris - Why I'm finally convinced it's time to stop saying "you guys" `_ `inclusive language presentations `_ Other talks and Blog Posts @@ -30,7 +30,7 @@ Other talks and Blog Posts `Lena Reinhard – A Talk About Nothing `_ `Lena Reinhard - A Talk about Everything `_ -`Sage Sharp - SCALE: Improving Diversity with Maslow’s hierarchy `_ +`Sage Sharp - SCALE: Improving Diversity with Maslow's hierarchy `_ `UCSF - Unconscious Bias `_ `Responding to harassment reports `_ `Unconscious bias at work `_ diff --git a/http_client.rst b/http_client.rst index 32ef552c64f..f4be8b2460b 100644 --- a/http_client.rst +++ b/http_client.rst @@ -898,7 +898,7 @@ in your requests:: 'extra' => ['trace_content' => false], ]); -This setting won’t affect other clients. +This setting won't affect other clients. Using URI Templates ~~~~~~~~~~~~~~~~~~~ diff --git a/scheduler.rst b/scheduler.rst index e7d855db249..3f2f8d7d2ac 100644 --- a/scheduler.rst +++ b/scheduler.rst @@ -603,7 +603,7 @@ In your handler, you can check a condition and, if affirmative, access the { public function getSchedule(): Schedule { - $this->removeOldReports = RecurringMessage::cron(‘3 8 * * 1’, new CleanUpOldSalesReport()); + $this->removeOldReports = RecurringMessage::cron('3 8 * * 1', new CleanUpOldSalesReport()); return $this->schedule ??= (new Schedule()) ->with( From 5952dd3e9e4c863f3bb0d8926454cccd26e7f8da Mon Sep 17 00:00:00 2001 From: Pjotr Savitski Date: Sat, 17 May 2025 09:41:44 +0300 Subject: [PATCH 031/120] Changed text to mention one Product object instead of many The getOneOrNullResult will not return multiple results. --- doctrine/associations.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doctrine/associations.rst b/doctrine/associations.rst index 8dd9aa7f36b..2279d02a851 100644 --- a/doctrine/associations.rst +++ b/doctrine/associations.rst @@ -501,7 +501,7 @@ following method to the ``ProductRepository`` class:: } } -This will *still* return an array of ``Product`` objects. But now, when you call +This will *still* return a ``Product`` object. But now, when you call ``$product->getCategory()`` and use that data, no second query is made. Now, you can use this method in your controller to query for a ``Product`` From 4579583cb6304e707413f0d4d5abaa1454572231 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 13 May 2025 14:04:30 +0200 Subject: [PATCH 032/120] [Security] Tell about stateless CSRF protection --- http_cache/varnish.rst | 2 +- reference/configuration/framework.rst | 59 ++++++++- security/csrf.rst | 184 ++++++++++++++++++++++++-- session.rst | 2 +- 4 files changed, 233 insertions(+), 14 deletions(-) diff --git a/http_cache/varnish.rst b/http_cache/varnish.rst index a9bb668c100..6fcb7fd766b 100644 --- a/http_cache/varnish.rst +++ b/http_cache/varnish.rst @@ -62,7 +62,7 @@ If you know for sure that the backend never uses sessions or basic authentication, have Varnish remove the corresponding header from requests to prevent clients from bypassing the cache. In practice, you will need sessions at least for some parts of the site, e.g. when using forms with -:doc:`CSRF Protection `. In this situation, make sure to +:doc:`stateful CSRF Protection `. In this situation, make sure to :ref:`only start a session when actually needed ` and clear the session when it is no longer needed. Alternatively, you can look into :ref:`caching pages that contain CSRF protected forms `. diff --git a/reference/configuration/framework.rst b/reference/configuration/framework.rst index 599cbb0ba04..8fec2678c8d 100644 --- a/reference/configuration/framework.rst +++ b/reference/configuration/framework.rst @@ -805,8 +805,6 @@ csrf_protection For more information about CSRF protection, see :doc:`/security/csrf`. -.. _reference-csrf_protection-enabled: - enabled ....... @@ -854,6 +852,42 @@ If you're using forms, but want to avoid starting your session (e.g. using forms in an API-only website), ``csrf_protection`` will need to be set to ``false``. +stateless_token_ids +................... + +**type**: ``array`` **default**: ``[]`` + +The list of CSRF token ids that will use stateless CSRF protection. + +.. versionadded:: 7.2 + + This option was added in Symfony 7.2 to aid in configuring stateless CSRF protection. + +check_header +............ + +**type**: ``integer`` or ``bool`` **default**: ``false`` + +Whether to check the CSRF token in a header in addition to a cookie when using stateless protection. +Can be set to ``2`` (the value of the ``CHECK_ONLY_HEADER`` constant on the +:class:`Symfony\\Component\\Security\\Csrf\\SameOriginCsrfTokenManager` class) to check only the header +and not the cookie. + +.. versionadded:: 7.2 + + This option was added in Symfony 7.2 to aid in configuring stateless CSRF protection. + +cookie_name +........... + +**type**: ``string`` **default**: ``csrf-token`` + +The name of the cookie (and header) to use for the double-submit when using stateless protection. + +.. versionadded:: 7.2 + + This option was added in Symfony 7.2 to aid in configuring stateless CSRF protection. + .. _config-framework-default_locale: default_locale @@ -1164,15 +1198,32 @@ settings is configured. For more details, see :doc:`/forms`. -.. _reference-form-field-name: +csrf_protection +............... field_name -.......... +'''''''''' **type**: ``string`` **default**: ``_token`` This is the field name that you should give to the CSRF token field of your forms. +field_attr +'''''''''' + +**type**: ``array`` **default**: ``['data-controller' => 'csrf-protection']`` + +This is the HTML attributes that should be added to the CSRF token field of your forms. + +token_id +'''''''' + +**type**: ``string`` **default**: ``null`` + +This is the CSRF token id that should be used for validating the CSRF tokens of your forms. +Note that this setting applies only to autoconfigured form types, which usually means only +to your own form types and not to form types registered by third-party bundles. + fragments ~~~~~~~~~ diff --git a/security/csrf.rst b/security/csrf.rst index 772b503ec39..2e7369d170f 100644 --- a/security/csrf.rst +++ b/security/csrf.rst @@ -34,6 +34,10 @@ unique tokens added to forms as hidden fields. The legit server validates them t ensure that the request originated from the expected source and not some other malicious website. +Anti-CSRF tokens can be managed either in a stateful way: they're put in the +session and are unique for each user and for each kind of action, or in a +stateless way: they're generated on the client-side. + Installation ------------ @@ -85,14 +89,14 @@ for more information): ; }; -The tokens used for CSRF protection are meant to be different for every user and -they are stored in the session. That's why a session is started automatically as -soon as you render a form with CSRF protection. +By default, the tokens used for CSRF protection are stored in the session. +That's why a session is started automatically as soon as you render a form +with CSRF protection. .. _caching-pages-that-contain-csrf-protected-forms: -Moreover, this means that you cannot fully cache pages that include CSRF -protected forms. As an alternative, you can: +This leads to many strategies to help with caching pages that include CSRF +protected forms, among them: * Embed the form inside an uncached :doc:`ESI fragment ` and cache the rest of the page contents; @@ -101,6 +105,9 @@ protected forms. As an alternative, you can: load the CSRF token with an uncached AJAX request and replace the form field value with it. +The most effective way to cache pages that need CSRF protected forms is to use +stateless CSRF tokens, see below. + .. _csrf-protection-forms: CSRF Protection in Symfony Forms @@ -183,6 +190,7 @@ method of each form:: 'csrf_field_name' => '_token', // an arbitrary string used to generate the value of the token // using a different string for each form improves its security + // when using stateful tokens (which is the default) 'csrf_token_id' => 'task_item', ]); } @@ -190,7 +198,7 @@ method of each form:: // ... } -You can also customize the rendering of the CSRF form field creating a custom +You can also customize the rendering of the CSRF form field by creating a custom :doc:`form theme ` and using ``csrf_token`` as the prefix of the field (e.g. define ``{% block csrf_token_widget %} ... {% endblock %}`` to customize the entire form field contents). @@ -221,7 +229,7 @@ generate a CSRF token in the template and store it as a hidden form field: .. code-block:: html+twig
- {# the argument of csrf_token() is an arbitrary string used to generate the token #} + {# the argument of csrf_token() is the ID of this token #} @@ -229,7 +237,7 @@ generate a CSRF token in the template and store it as a hidden form field: Then, get the value of the CSRF token in the controller action and use the :method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController::isCsrfTokenValid` -method to check its validity:: +method to check its validity, passing the same token ID used in the template:: use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -302,6 +310,166 @@ targeted parts of the plaintext. To mitigate these attacks, and prevent an attacker from guessing the CSRF tokens, a random mask is prepended to the token and used to scramble it. +Stateless CSRF Tokens +--------------------- + +.. versionadded:: 7.2 + + Stateless anti-CSRF protection was introduced in Symfony 7.2. + +By default CSRF tokens are stateful, which means they're stored in the session. +But some token ids can be declared as stateless using the ``stateless_token_ids`` +option: + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/csrf.yaml + framework: + # ... + csrf_protection: + stateless_token_ids: ['submit', 'authenticate', 'logout'] + + .. code-block:: xml + + + + + + + + submit + authenticate + logout + + + + + .. code-block:: php + + // config/packages/csrf.php + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework): void { + $framework->csrfProtection() + ->statelessTokenIds(['submit', 'authenticate', 'logout']) + ; + }; + +Stateless CSRF tokens use a CSRF protection that doesn't need the session. This +means that you can cache the entire page and still have CSRF protection. + +When a stateless CSRF token is checked for validity, Symfony verifies the +``Origin`` and the ``Referer`` headers of the incoming HTTP request. + +If either of these headers match the target origin of the application (its domain +name), the CSRF token is considered valid. This relies on the app being able to +know its own target origin. Don't miss configuring your reverse proxy if you're +behind one. See :doc:`/deployment/proxies`. + +Using a Default Token ID +~~~~~~~~~~~~~~~~~~~~~~~~ + +While stateful CSRF tokens are better seggregated per form or action, stateless +ones don't need many token identifiers. In the previous example, ``authenticate`` +and ``logout`` are listed because they're the default identifiers used by the +Symfony Security component. The ``submit`` identifier is then listed so that +form types defined by the application can use it by default. The following +configuration - which applies only to form types declared using autofiguration +(the default way to declare *your* services) - will make your form types use the +``submit`` token identifier by default: + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/csrf.yaml + framework: + form: + csrf_protection: + token_id: 'submit' + + .. code-block:: xml + + + + + + + + + + + + + .. code-block:: php + + // config/packages/csrf.php + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework): void { + $framework->form() + ->csrfProtection() + ->tokenId('submit') + ; + }; + +Forms configured with a token identifier listed in the above ``stateless_token_ids`` +option will use the stateless CSRF protection. + +Generating CSRF Token Using Javascript +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In addition to the ``Origin`` and ``Referer`` headers, stateless CSRF protection +also checks a cookie and a header (named ``csrf-token`` by default, see the +:ref:`CSRF configuration reference `). + +These extra checks are part of defense-in-depth strategies provided by the +stateless CSRF protection. They are optional and they require +`some JavaScript`_ to be activated. This JavaScript is responsible for generating +a crypto-safe random token when a form is submitted, then putting the token in +the hidden CSRF field of the form and submitting it also as a cookie and header. +On the server-side, the CSRF token is validated by checking the cookie and header +values. This "double-submit" protection relies on the same-origin policy +implemented by browsers and is strengthened by regenerating the token at every +form submission - which prevents cookie fixation issues - and by using +``samesite=strict`` and ``__Host-`` cookies, which make them domain-bound and +HTTPS-only. + +Note that the default snippet of JavaScript provided by Symfony requires that +the hidden CSRF form field is either named ``_csrf_token``, or that it has the +``data-controller="csrf-protection"`` attribute. You can of course take +inspiration from this snippet to write your own, provided you follow the same +protocol. + +As a last measure, a behavioral check is added on the server-side to ensure that +the validation method cannot be downgraded: if and only if a session is already +available, successful "double-submit" is remembered and is then required for +subsequent requests. This prevents attackers from exploiting potentially reduced +validation checks once cookie and/or header validation has been confirmed as +effective (they're optional by default as explained above). + +.. note:: + + Enforcing successful "double-submit" for every requests is not recommended as + as it could lead to a broken user experience. The opportunistic approach + described above is preferred because it allows the application to gracefully + degrade to ``Origin`` / ``Referer`` checks when JavaScript is not available. + .. _`Cross-site request forgery`: https://en.wikipedia.org/wiki/Cross-site_request_forgery .. _`BREACH`: https://en.wikipedia.org/wiki/BREACH .. _`CRIME`: https://en.wikipedia.org/wiki/CRIME +.. _`some JavaScript`: https://github.com/symfony/recipes/blob/main/symfony/stimulus-bundle/2.20/assets/controllers/csrf_protection_controller.js diff --git a/session.rst b/session.rst index 0c5348ec9e6..4c14638fc68 100644 --- a/session.rst +++ b/session.rst @@ -115,7 +115,7 @@ sessions for anonymous users, you must *completely* avoid accessing the session. .. note:: Sessions will also be started when using features that rely on them internally, - such as the :ref:`CSRF protection in forms `. + such as the :ref:`stateful CSRF protection in forms `. .. _flash-messages: From e591c5903958e63eaf1bc7d9b470431b29904f8c Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 12 May 2025 15:16:58 +0200 Subject: [PATCH 033/120] [Routing] Tell about {foo:bar} mapping syntax --- doctrine.rst | 31 +++++++++++-------------------- routing.rst | 43 ++++++++++++++++++++++++++++++++----------- 2 files changed, 43 insertions(+), 31 deletions(-) diff --git a/doctrine.rst b/doctrine.rst index 171f8a3348a..270c59a08c4 100644 --- a/doctrine.rst +++ b/doctrine.rst @@ -680,7 +680,7 @@ will automatically fetch them:: /** * Perform a findOneBy() where the slug property matches {slug}. */ - #[Route('/product/{slug}')] + #[Route('/product/{slug:product}')] public function showBySlug(Product $product): Response { } @@ -694,14 +694,17 @@ Automatic fetching works in these situations: *all* of the wildcards in your route that are actually properties on your entity (non-properties are ignored). -This behavior is enabled by default on all controllers. If you prefer, you can -restrict this feature to only work on route wildcards called ``id`` to look for -entities by primary key. To do so, set the option -``doctrine.orm.controller_resolver.auto_mapping`` to ``false``. +The ``{slug:product}`` syntax maps the route parameter named ``slug`` to the +controller argument named ``$product``. It also hints the resolver to lookup +by slug when loading the corresponding ``Product`` object from the database. -When ``auto_mapping`` is disabled, you can configure the mapping explicitly for -any controller argument with the ``MapEntity`` attribute. You can even control -the ``EntityValueResolver`` behavior by using the `MapEntity options`_ :: +.. versionadded:: 7.1 + + Route parameter mapping was introduced in Symfony 7.1. + +You can also configure the mapping explicitly for any controller argument +with the ``MapEntity`` attribute. You can even control the +``EntityValueResolver`` behavior by using the `MapEntity options`_ :: // src/Controller/ProductController.php namespace App\Controller; @@ -812,18 +815,6 @@ control behavior: ): Response { } -``exclude`` - Configures the properties that should be used in the ``findOneBy()`` - method by *excluding* one or more properties so that not *all* are used:: - - #[Route('/product/{slug}/{date}')] - public function show( - #[MapEntity(exclude: ['date'])] - Product $product, - \DateTime $date - ): Response { - } - ``stripNull`` If true, then when ``findOneBy()`` is used, any values that are ``null`` will not be used for the query. diff --git a/routing.rst b/routing.rst index 24218eaf74c..587a35f269b 100644 --- a/routing.rst +++ b/routing.rst @@ -22,8 +22,7 @@ Creating Routes as Attributes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PHP attributes allow to define routes next to the code of the -:doc:`controllers ` associated to those routes. Attributes are -native in PHP 8 and higher versions, so you can use them right away. +:doc:`controllers ` associated to those routes. You need to add a bit of configuration to your project before using them. If your project uses :ref:`Symfony Flex `, this file is already created for you. @@ -707,12 +706,6 @@ URL Route Parameters matches any uppercase character in any language, ``\p{Greek}`` matches any Greek characters, etc. -.. note:: - - When using regular expressions in route parameters, you can set the ``utf8`` - route option to ``true`` to make any ``.`` character match any UTF-8 - characters instead of just a single byte. - If you prefer, requirements can be inlined in each parameter using the syntax ``{parameter_name}``. This feature makes configuration more concise, but it can decrease route readability when requirements are complex: @@ -998,7 +991,7 @@ controller action. Instead of ``string $slug``, add ``BlogPost $post``:: { // ... - #[Route('/blog/{slug}', name: 'blog_show')] + #[Route('/blog/{slug:post}', name: 'blog_show')] public function show(BlogPost $post): Response { // $post is the object whose slug matches the routing parameter @@ -1012,9 +1005,37 @@ this case), the "param converter" makes a database request to find the object using the request parameters (``slug`` in this case). If no object is found, Symfony generates a 404 response automatically. +The ``{slug:post}`` syntax maps the route parameter named ``slug`` to the controller +argument named ``$post``. It also hints the "param converter" to lookup by slug +when loading the corresponding ``BlogPost`` object from the database. + +.. versionadded:: 7.1 + + Route parameter mapping was introduced in Symfony 7.1. + +When more than one entity needs to be derived from route parameters, collisions can happen. +In the following example, the route tries to define two mappings: one to load an author by +name, two to load a category by name. But this is not allowed because from the side of the +route definition, this declares a parameter named "name" twice:: + + #[Route('/search-book/{name:author}/{name:category}')] + +Such routes should instead be defined using the following syntax:: + + #[Route('/search-book/{authorName:author.name}/{categoryName:category.name}')] + +This way, the route parameter names are unique (``authorName`` and ``categoryName``) and +the "param converter" can correctly map them to controller arguments (``$author`` and +``$category``), loading them both by their name. + +.. versionadded:: 7.3 + + This more advanced style of route parameter mapping was introduced in Symfony 7.3. + +More advanced mappings can be achieved using the ``#[MapEntity]`` attribute. Check out the :ref:`Doctrine param conversion documentation ` -to learn about the ``#[MapEntity]`` attribute that can be used to customize the -database queries used to fetch the object from the route parameter. +to learn how to customize the database queries used to fetch the object from the route +parameter. Backed Enum Parameters ~~~~~~~~~~~~~~~~~~~~~~ From 62b7149405447a29a11f8f8e93da99dfb4afac85 Mon Sep 17 00:00:00 2001 From: HypeMC Date: Sat, 17 May 2025 17:55:29 +0200 Subject: [PATCH 034/120] [Doctrine] Remove redundant example with attribute --- doctrine/events.rst | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/doctrine/events.rst b/doctrine/events.rst index 9507316eb5b..3f8f93bc2ee 100644 --- a/doctrine/events.rst +++ b/doctrine/events.rst @@ -304,23 +304,6 @@ listener in the Symfony application by creating a new service for it and .. configuration-block:: - .. code-block:: php-attributes - - // src/EventListener/SearchIndexer.php - namespace App\EventListener; - - use Doctrine\Bundle\DoctrineBundle\Attribute\AsDoctrineListener; - use Doctrine\ORM\Event\PostPersistEventArgs; - - #[AsDoctrineListener('postPersist'/*, 500, 'default'*/)] - class SearchIndexer - { - public function postPersist(PostPersistEventArgs $event): void - { - // ... - } - } - .. code-block:: yaml # config/services.yaml From c6ae20a8fcdd5c5452fe4a3321244773dca04e1e Mon Sep 17 00:00:00 2001 From: Nazar Mammedov Date: Fri, 16 May 2025 15:54:01 -0400 Subject: [PATCH 035/120] Update messenger.rst Typo fix from "may looks" to "may look" --- messenger.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messenger.rst b/messenger.rst index bf5a8180adc..18c7be913b5 100644 --- a/messenger.rst +++ b/messenger.rst @@ -1372,7 +1372,7 @@ RabbitMQ. Install it by running: $ composer require symfony/amqp-messenger -The AMQP transport DSN may looks like this: +The AMQP transport DSN may look like this: .. code-block:: env From 3474a09b856214ca239af989d221faa05fe69c4a Mon Sep 17 00:00:00 2001 From: "Benjamin D." Date: Wed, 16 Apr 2025 11:31:41 +0200 Subject: [PATCH 036/120] Highligh 'extensions' instead of 'mimeTypes' for File constraint 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 is consistent with its content (this can be a security issue). By default, the extensions option also checks the media type of the file. --- controller/upload_file.rst | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/controller/upload_file.rst b/controller/upload_file.rst index dff5453509a..941bd911a78 100644 --- a/controller/upload_file.rst +++ b/controller/upload_file.rst @@ -77,11 +77,8 @@ so Symfony doesn't try to get/set its value from the related entity:: 'constraints' => [ new File([ 'maxSize' => '1024k', - 'mimeTypes' => [ - 'application/pdf', - 'application/x-pdf', - ], - 'mimeTypesMessage' => 'Please upload a valid PDF document', + 'extensions' => ['pdf'], + 'extensionsMessage' => 'Please upload a valid PDF document', ]) ], ]) From e8128a60528c45de2833c9eb1bf692daf963fadf Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Tue, 20 May 2025 08:55:56 +0200 Subject: [PATCH 037/120] remove the Slug constraint --- reference/constraints/Slug.rst | 119 ------------------------------ reference/constraints/map.rst.inc | 1 - 2 files changed, 120 deletions(-) delete mode 100644 reference/constraints/Slug.rst diff --git a/reference/constraints/Slug.rst b/reference/constraints/Slug.rst deleted file mode 100644 index 2eb82cd9c10..00000000000 --- a/reference/constraints/Slug.rst +++ /dev/null @@ -1,119 +0,0 @@ -Slug -==== - -.. versionadded:: 7.3 - - The ``Slug`` constraint was introduced in Symfony 7.3. - -Validates that a value is a slug. By default, a slug is a string that matches -the following regular expression: ``/^[a-z0-9]+(?:-[a-z0-9]+)*$/``. - -.. include:: /reference/constraints/_empty-values-are-valid.rst.inc - -========== =================================================================== -Applies to :ref:`property or method ` -Class :class:`Symfony\\Component\\Validator\\Constraints\\Slug` -Validator :class:`Symfony\\Component\\Validator\\Constraints\\SlugValidator` -========== =================================================================== - -Basic Usage ------------ - -The ``Slug`` constraint can be applied to a property or a getter method: - -.. configuration-block:: - - .. code-block:: php-attributes - - // src/Entity/Author.php - namespace App\Entity; - - use Symfony\Component\Validator\Constraints as Assert; - - class Author - { - #[Assert\Slug] - protected string $slug; - } - - .. code-block:: yaml - - # config/validator/validation.yaml - App\Entity\Author: - properties: - slug: - - Slug: ~ - - .. code-block:: xml - - - - - - - - - - - - - .. code-block:: php - - // src/Entity/Author.php - namespace App\Entity; - - use Symfony\Component\Validator\Constraints as Assert; - use Symfony\Component\Validator\Mapping\ClassMetadata; - - class Author - { - // ... - - public static function loadValidatorMetadata(ClassMetadata $metadata): void - { - $metadata->addPropertyConstraint('slug', new Assert\Slug()); - } - } - -Examples of valid values: - -* foobar -* foo-bar -* foo123 -* foo-123bar - -Uppercase characters would result in an violation of this constraint. - -Options -------- - -``regex`` -~~~~~~~~~ - -**type**: ``string`` default: ``/^[a-z0-9]+(?:-[a-z0-9]+)*$/`` - -This option allows you to modify the regular expression pattern that the input -will be matched against via the :phpfunction:`preg_match` PHP function. - -If you need to use it, you might also want to take a look at the :doc:`Regex constraint `. - -``message`` -~~~~~~~~~~~ - -**type**: ``string`` **default**: ``This value is not a valid slug`` - -This is the message that will be shown if this validator fails. - -You can use the following parameters in this message: - -================= ============================================================== -Parameter Description -================= ============================================================== -``{{ value }}`` The current (invalid) value -================= ============================================================== - -.. include:: /reference/constraints/_groups-option.rst.inc - -.. include:: /reference/constraints/_payload-option.rst.inc diff --git a/reference/constraints/map.rst.inc b/reference/constraints/map.rst.inc index c2396ae3af7..06680e42207 100644 --- a/reference/constraints/map.rst.inc +++ b/reference/constraints/map.rst.inc @@ -33,7 +33,6 @@ String Constraints * :doc:`NotCompromisedPassword ` * :doc:`PasswordStrength ` * :doc:`Regex ` -* :doc:`Slug ` * :doc:`Twig ` * :doc:`Ulid ` * :doc:`Url ` From 88a7eb25c60453e95dd9cf782238f2855b9e4738 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Tue, 20 May 2025 09:17:27 +0200 Subject: [PATCH 038/120] Minor tweaks --- doctrine.rst | 8 ++++---- routing.rst | 23 ++--------------------- 2 files changed, 6 insertions(+), 25 deletions(-) diff --git a/doctrine.rst b/doctrine.rst index 270c59a08c4..a55ff8fe600 100644 --- a/doctrine.rst +++ b/doctrine.rst @@ -695,16 +695,16 @@ Automatic fetching works in these situations: on your entity (non-properties are ignored). The ``{slug:product}`` syntax maps the route parameter named ``slug`` to the -controller argument named ``$product``. It also hints the resolver to lookup -by slug when loading the corresponding ``Product`` object from the database. +controller argument named ``$product``. It also hints the resolver to look up +the corresponding ``Product`` object from the database using the slug. .. versionadded:: 7.1 Route parameter mapping was introduced in Symfony 7.1. You can also configure the mapping explicitly for any controller argument -with the ``MapEntity`` attribute. You can even control the -``EntityValueResolver`` behavior by using the `MapEntity options`_ :: +using the ``MapEntity`` attribute. You can even control the behavior of the +``EntityValueResolver`` by using the `MapEntity options`_ :: // src/Controller/ProductController.php namespace App\Controller; diff --git a/routing.rst b/routing.rst index 587a35f269b..810c753ec3a 100644 --- a/routing.rst +++ b/routing.rst @@ -1006,32 +1006,13 @@ using the request parameters (``slug`` in this case). If no object is found, Symfony generates a 404 response automatically. The ``{slug:post}`` syntax maps the route parameter named ``slug`` to the controller -argument named ``$post``. It also hints the "param converter" to lookup by slug -when loading the corresponding ``BlogPost`` object from the database. +argument named ``$post``. It also hints the "param converter" to look up the +corresponding ``BlogPost`` object from the database using the slug. .. versionadded:: 7.1 Route parameter mapping was introduced in Symfony 7.1. -When more than one entity needs to be derived from route parameters, collisions can happen. -In the following example, the route tries to define two mappings: one to load an author by -name, two to load a category by name. But this is not allowed because from the side of the -route definition, this declares a parameter named "name" twice:: - - #[Route('/search-book/{name:author}/{name:category}')] - -Such routes should instead be defined using the following syntax:: - - #[Route('/search-book/{authorName:author.name}/{categoryName:category.name}')] - -This way, the route parameter names are unique (``authorName`` and ``categoryName``) and -the "param converter" can correctly map them to controller arguments (``$author`` and -``$category``), loading them both by their name. - -.. versionadded:: 7.3 - - This more advanced style of route parameter mapping was introduced in Symfony 7.3. - More advanced mappings can be achieved using the ``#[MapEntity]`` attribute. Check out the :ref:`Doctrine param conversion documentation ` to learn how to customize the database queries used to fetch the object from the route From d71927f9e9eb83a697416e76f5928af85bbbe38a Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Tue, 20 May 2025 09:21:53 +0200 Subject: [PATCH 039/120] [Routing] Readd the 7.3 docs about advance param mapping --- routing.rst | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/routing.rst b/routing.rst index 5df9694296d..47a3e9f3190 100644 --- a/routing.rst +++ b/routing.rst @@ -1020,6 +1020,25 @@ corresponding ``BlogPost`` object from the database using the slug. Route parameter mapping was introduced in Symfony 7.1. +When mapping multiple entities from route parameters, name collisions can occur. +In this example, the route tries to define two mappings: one for an author and one +for a category; both using the same ``name`` parameter. This isn't allowed because +the route ends up declaring ``name`` twice:: + + #[Route('/search-book/{name:author}/{name:category}')] + +Such routes should instead be defined using the following syntax:: + + #[Route('/search-book/{authorName:author.name}/{categoryName:category.name}')] + +This way, the route parameter names are unique (``authorName`` and ``categoryName``), +and the "param converter" can correctly map them to controller arguments (``$author`` +and ``$category``), loading them both by their name. + +.. versionadded:: 7.3 + + This more advanced style of route parameter mapping was introduced in Symfony 7.3. + More advanced mappings can be achieved using the ``#[MapEntity]`` attribute. Check out the :ref:`Doctrine param conversion documentation ` to learn how to customize the database queries used to fetch the object from the route From a28b614c27bdca774025babeca5f8393a318ff86 Mon Sep 17 00:00:00 2001 From: Mathieu Santostefano Date: Tue, 25 Feb 2025 10:04:59 +0100 Subject: [PATCH 040/120] [Routing] Clarify route aliases examples with explicit route names --- routing.rst | 123 ++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 100 insertions(+), 23 deletions(-) diff --git a/routing.rst b/routing.rst index a41b9bba6a6..18a25dd3018 100644 --- a/routing.rst +++ b/routing.rst @@ -1336,15 +1336,18 @@ A possible solution is to change the parameter requirements to be more permissiv Route Aliasing -------------- -Route alias allow you to have multiple name for the same route: +Route alias allows you to have multiple names for the same route +and can be used to provide backward compatibility for routes that +have been renamed. Let's say you have a route called ``product_show``: .. configuration-block:: .. code-block:: yaml # config/routes.yaml - new_route_name: - alias: original_route_name + product_show: + path: /product/{id} + controller: App\Controller\ProductController::show .. code-block:: xml @@ -1355,7 +1358,7 @@ Route alias allow you to have multiple name for the same route: xsi:schemaLocation="http://symfony.com/schema/routing https://symfony.com/schema/routing/routing-1.0.xsd"> - + .. code-block:: php @@ -1364,10 +1367,55 @@ Route alias allow you to have multiple name for the same route: use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; return static function (RoutingConfigurator $routes): void { - $routes->alias('new_route_name', 'original_route_name'); + $routes->add('product_show', '/product/{id}') + ->controller('App\Controller\ProductController::show'); }; -In this example, both ``original_route_name`` and ``new_route_name`` routes can +Now, let's say you want to create a new route called ``product_details`` +that acts exactly the same as ``product_show``. + +Instead of duplicating the original route, you can create an alias for it. + +.. configuration-block:: + + .. code-block:: yaml + + # config/routes.yaml + product_show: + path: /product/{id} + controller: App\Controller\ProductController::show + + product_details: + # "alias" option refers to the name of the route declared above + alias: product_show + + .. code-block:: xml + + + + + + + + + + + .. code-block:: php + + // config/routes.php + use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; + + return static function (RoutingConfigurator $routes): void { + $routes->add('product_show', '/product/{id}') + ->controller('App\Controller\ProductController::show'); + // second argument refers to the name of the route declared above + $routes->alias('product_details', 'product_show'); + }; + +In this example, both ``product_show`` and ``product_details`` routes can be used in the application and will produce the same result. .. _routing-alias-deprecation: @@ -1375,27 +1423,45 @@ be used in the application and will produce the same result. Deprecating Route Aliases ~~~~~~~~~~~~~~~~~~~~~~~~~ -If some route alias should no longer be used (because it is outdated or -you decided not to maintain it anymore), you can deprecate its definition: +Route aliases can be used to provide backward compatibility for routes that +have been renamed. + +Now, let's say you want to replace the ``product_show`` route in favor of +``product_details`` and mark the old one as deprecated. + +In the previous example, the alias ``product_details`` was pointing to +``product_show`` route. + +To mark the ``product_show`` route as deprecated, you need to "switch" the alias. +The ``product_show`` become the alias, and will now point to the ``product_details`` route. +This way, the ``product_show`` alias could be deprecated. .. configuration-block:: .. code-block:: yaml - new_route_name: - alias: original_route_name + # Move the concrete route definition under ``product_details`` + product_details: + path: /product/{id} + controller: App\Controller\ProductController::show + + # Define the alias and the deprecation under the ``product_show`` definition + product_show: + alias: product_details # this outputs the following generic deprecation message: - # Since acme/package 1.2: The "new_route_name" route alias is deprecated. You should stop using it, as it will be removed in the future. + # Since acme/package 1.2: The "product_show" route alias is deprecated. You should stop using it, as it will be removed in the future. deprecated: package: 'acme/package' version: '1.2' - # you can also define a custom deprecation message (%alias_id% placeholder is available) + # or + + # you can define a custom deprecation message (%alias_id% placeholder is available) deprecated: package: 'acme/package' version: '1.2' - message: 'The "%alias_id%" route alias is deprecated. Do not use it anymore.' + message: 'The "%alias_id%" route alias is deprecated. Please use "product_details" instead.' .. code-block:: xml @@ -1405,35 +1471,46 @@ you decided not to maintain it anymore), you can deprecate its definition: xsi:schemaLocation="http://symfony.com/schema/routing https://symfony.com/schema/routing/routing-1.0.xsd"> - + + + + + + Since acme/package 1.2: The "product_show" route alias is deprecated. You should stop using it, as it will be removed in the future. --> - + + + - The "%alias_id%" route alias is deprecated. Do not use it anymore. + The "%alias_id%" route alias is deprecated. Please use "product_details" instead. .. code-block:: php - $routes->alias('new_route_name', 'original_route_name') + $routes->add('product_details', '/product/{id}') + ->controller('App\Controller\ProductController::show'); + + $routes->alias('product_show', 'product_details') // this outputs the following generic deprecation message: - // Since acme/package 1.2: The "new_route_name" route alias is deprecated. You should stop using it, as it will be removed in the future. + // Since acme/package 1.2: The "product_show" route alias is deprecated. You should stop using it, as it will be removed in the future. ->deprecate('acme/package', '1.2', '') - // you can also define a custom deprecation message (%alias_id% placeholder is available) + // or + + // you can define a custom deprecation message (%alias_id% placeholder is available) ->deprecate( 'acme/package', '1.2', - 'The "%alias_id%" route alias is deprecated. Do not use it anymore.' + 'The "%alias_id%" route alias is deprecated. Please use "product_details" instead.' ) ; -In this example, every time the ``new_route_name`` alias is used, a deprecation -warning is triggered, advising you to stop using that alias. +In this example, every time the ``product_show`` alias is used, a deprecation +warning is triggered, advising you to stop using this route and prefer using ``product_details``. The message is actually a message template, which replaces occurrences of the ``%alias_id%`` placeholder by the route alias name. You **must** have From def8d6b58f1786121227b053cd884257d742a628 Mon Sep 17 00:00:00 2001 From: Florent Morselli Date: Tue, 20 May 2025 16:11:40 +0200 Subject: [PATCH 041/120] Add runtime schedule modification feature to docs Document the new ability introduced in Symfony 6.4 to modify schedules dynamically at runtime. This includes recalculating the internal trigger heap for better control over recurring tasks. --- scheduler.rst | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/scheduler.rst b/scheduler.rst index 3f2f8d7d2ac..4c5955a9470 100644 --- a/scheduler.rst +++ b/scheduler.rst @@ -890,6 +890,46 @@ code:: use the ``messenger:consume`` command as explained in the previous section. +Modifying the Schedule at Runtime +--------------------------------- + +.. versionadded:: 6.4 + + Modifying the schedule at runtime and recalculating the heap was introduced in Symfony 6.4. + +When a recurring message is added to or removed from the schedule, +the scheduler automatically restarts and recalculates the internal trigger heap. +This allows dynamic control over scheduled tasks during runtime. +code:: + + // src/Scheduler/DynamicScheduleProvider.php + namespace App\Scheduler; + + #[AsSchedule('uptoyou')] + class DynamicScheduleProvider implements ScheduleProviderInterface + { + private ?Schedule $schedule = null; + + public function getSchedule(): Schedule + { + return $this->schedule ??= (new Schedule()) + ->with( + // ... + ) + ; + } + + public function clearAndAddMessages(): void + { + // Clear the current schedule (if any) and add new recurring messages + $this->schedule?->clear(); + $this->schedule?->add( + RecurringMessage::cron('@hourly', new DoActionMessage()), + RecurringMessage::cron('@daily', new DoAnotherActionMessage()), + ); + } + } + Debugging the Schedule ---------------------- From 0d4b75575c53dd9a4bb846051462ece4d95c3c3e Mon Sep 17 00:00:00 2001 From: Jacob Dreesen Date: Tue, 20 May 2025 22:56:29 +0200 Subject: [PATCH 042/120] Try fixing a list in object_mapper.rst --- object_mapper.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/object_mapper.rst b/object_mapper.rst index fe7db2365cf..0eed176c67d 100644 --- a/object_mapper.rst +++ b/object_mapper.rst @@ -149,7 +149,7 @@ You can apply the ``#[Map]`` attribute to properties to customize their mapping * ``target``: Specifies the name of the property in the target object; * ``source``: Specifies the name of the property in the source object (useful - when mapping is defined on the target, see below); + when mapping is defined on the target, see below); * ``if``: Defines a condition for mapping the property; * ``transform``: Applies a transformation to the value before mapping. From 38c015ca7f845deda55076f6738f2fe6c1804091 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9rage=20K=C3=A9vin?= Date: Tue, 20 May 2025 11:15:12 +0200 Subject: [PATCH 043/120] Clarifying LocaleSwitcher's behavior --- translation.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/translation.rst b/translation.rst index 5565c65bdba..8505b916587 100644 --- a/translation.rst +++ b/translation.rst @@ -1110,6 +1110,13 @@ just to run some code. Imagine a console command that renders Twig templates of emails in different languages. You need to change the locale only to render those templates. +.. note:: + + The LocaleSwitcher will apply the locale at a request level + this means that it will be available only for that request. A + redirect, for example, will cancel the LocaleSwitcher's effect. + For a permanent locale switch between requests see https://symfony.com/doc/current/session.html#making-the-locale-sticky-during-a-user-s-session. + The ``LocaleSwitcher`` class allows you to change at once the locale of: From aa698f8069b4bfc73a2bc3b866e368bcd2304e48 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Wed, 21 May 2025 10:12:34 +0200 Subject: [PATCH 044/120] Rewords --- translation.rst | 55 ++++++++++++++++++++++--------------------------- 1 file changed, 25 insertions(+), 30 deletions(-) diff --git a/translation.rst b/translation.rst index 8505b916587..8d66c21f39b 100644 --- a/translation.rst +++ b/translation.rst @@ -1105,25 +1105,11 @@ Switch Locale Programmatically The ``LocaleSwitcher`` was introduced in Symfony 6.1. -Sometimes you need to change the locale of the application dynamically -just to run some code. Imagine a console command that renders Twig templates -of emails in different languages. You need to change the locale only to -render those templates. +Sometimes you need to change the application's locale dynamically while running +some code. For example, a console command that renders email templates in +different languages. In such cases, you only need to switch the locale temporarily. -.. note:: - - The LocaleSwitcher will apply the locale at a request level - this means that it will be available only for that request. A - redirect, for example, will cancel the LocaleSwitcher's effect. - For a permanent locale switch between requests see https://symfony.com/doc/current/session.html#making-the-locale-sticky-during-a-user-s-session. - -The ``LocaleSwitcher`` class allows you to change at once the locale -of: - -* All the services that are tagged with ``kernel.locale_aware``; -* ``\Locale::setDefault()``; -* If the ``RequestContext`` service is available, the ``_locale`` - parameter (so urls are generated with the new locale):: +The ``LocaleSwitcher`` class allows you to do that:: use Symfony\Component\Translation\LocaleSwitcher; @@ -1136,28 +1122,23 @@ of: public function someMethod(): void { - // you can get the current application locale like this: $currentLocale = $this->localeSwitcher->getLocale(); - // you can set the locale for the entire application like this: - // (from now on, the application will use 'fr' (French) as the - // locale; including the default locale used to translate Twig templates) + // set the application locale programmatically to 'fr' (French): + // this affects translation, URL generation, etc. $this->localeSwitcher->setLocale('fr'); - // reset the current locale of your application to the configured default locale - // in config/packages/translation.yaml, by option 'default_locale' + // reset the locale to the default one configured via the + // 'default_locale' option in config/packages/translation.yaml $this->localeSwitcher->reset(); - // you can also run some code with a certain locale, without + // run some code with a specific locale, temporarily, without // changing the locale for the rest of the application $this->localeSwitcher->runWithLocale('es', function() { - - // e.g. render here some Twig templates using 'es' (Spanish) locale - + // e.g. render templates, send emails, etc. using the 'es' (Spanish) locale }); - // you can optionally declare an argument in your callback to receive the - // injected locale + // optionally, receive the current locale as an argument: $this->localeSwitcher->runWithLocale('es', function(string $locale) { // here, the $locale argument will be set to 'es' @@ -1175,6 +1156,20 @@ of: :method:`Symfony\\Component\\Translation\\LocaleSwitcher::runWithLocale` method was introduced in Symfony 6.4. +The ``LocaleSwitcher`` class changes the locale of: + +* All services tagged with ``kernel.locale_aware``; +* The default locale set via ``\Locale::setDefault()``; +* The ``_locale`` parameter of the ``RequestContext`` service (if available), + so generated URLs reflect the new locale. + +.. note:: + + The LocaleSwitcher applies the new locale only for the current request, + and its effect is lost on subsequent requests, such as after a redirect. + + See :ref:`how to make the locale persist across requests `. + When using :ref:`autowiring `, type-hint any controller or service argument with the :class:`Symfony\\Component\\Translation\\LocaleSwitcher` class to inject the locale switcher service. Otherwise, configure your services From 07ef56369b8b519999fa51abf16feb8156804785 Mon Sep 17 00:00:00 2001 From: Antoine M Date: Wed, 21 May 2025 10:28:05 +0200 Subject: [PATCH 045/120] fix: no bundle involved --- doctrine.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doctrine.rst b/doctrine.rst index 103ba869611..6abf5fa7928 100644 --- a/doctrine.rst +++ b/doctrine.rst @@ -649,7 +649,7 @@ automatically! You can simplify the controller to:: } } -That's it! The bundle uses the ``{id}`` from the route to query for the ``Product`` +That's it! The attribute uses the ``{id}`` from the route to query for the ``Product`` by the ``id`` column. If it's not found, a 404 page is generated. .. tip:: From e12256ce2df50fdf97ca28eaa866c28a2255ba97 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Thu, 22 May 2025 10:33:40 +0200 Subject: [PATCH 046/120] Rewords --- reference/configuration/framework.rst | 28 +++---- security/csrf.rst | 108 +++++++++++++------------- 2 files changed, 71 insertions(+), 65 deletions(-) diff --git a/reference/configuration/framework.rst b/reference/configuration/framework.rst index 23baa21642e..464b61dd69b 100644 --- a/reference/configuration/framework.rst +++ b/reference/configuration/framework.rst @@ -857,36 +857,38 @@ stateless_token_ids **type**: ``array`` **default**: ``[]`` -The list of CSRF token ids that will use stateless CSRF protection. +The list of CSRF token ids that will use :ref:`stateless CSRF protection `. .. versionadded:: 7.2 - This option was added in Symfony 7.2 to aid in configuring stateless CSRF protection. + The ``stateless_token_ids`` option was introduced in Symfony 7.2. check_header ............ **type**: ``integer`` or ``bool`` **default**: ``false`` -Whether to check the CSRF token in a header in addition to a cookie when using stateless protection. -Can be set to ``2`` (the value of the ``CHECK_ONLY_HEADER`` constant on the -:class:`Symfony\\Component\\Security\\Csrf\\SameOriginCsrfTokenManager` class) to check only the header -and not the cookie. +Whether to check the CSRF token in an HTTP header in addition to the cookie when +using :ref:`stateless CSRF protection `. You can also set +this to ``2`` (the value of the ``CHECK_ONLY_HEADER`` constant on the +:class:`Symfony\\Component\\Security\\Csrf\\SameOriginCsrfTokenManager` class) +to check only the header and ignore the cookie. .. versionadded:: 7.2 - This option was added in Symfony 7.2 to aid in configuring stateless CSRF protection. + The ``check_header`` option was introduced in Symfony 7.2. cookie_name ........... **type**: ``string`` **default**: ``csrf-token`` -The name of the cookie (and header) to use for the double-submit when using stateless protection. +The name of the cookie (and HTTP header) to use for the double-submit when using +:ref:`stateless CSRF protection `. .. versionadded:: 7.2 - This option was added in Symfony 7.2 to aid in configuring stateless CSRF protection. + The ``cookie_name`` option was introduced in Symfony 7.2. .. _config-framework-default_locale: @@ -1213,16 +1215,16 @@ field_attr **type**: ``array`` **default**: ``['data-controller' => 'csrf-protection']`` -This is the HTML attributes that should be added to the CSRF token field of your forms. +HTML attributes to add to the CSRF token field of your forms. token_id '''''''' **type**: ``string`` **default**: ``null`` -This is the CSRF token id that should be used for validating the CSRF tokens of your forms. -Note that this setting applies only to autoconfigured form types, which usually means only -to your own form types and not to form types registered by third-party bundles. +The CSRF token ID used to validate the CSRF tokens of your forms. This setting +applies only to form types that use :ref:`service autoconfiguration `, +which typically means your own form types, not those registered by third-party bundles. fragments ~~~~~~~~~ diff --git a/security/csrf.rst b/security/csrf.rst index 2e7369d170f..b303af9511b 100644 --- a/security/csrf.rst +++ b/security/csrf.rst @@ -34,9 +34,9 @@ unique tokens added to forms as hidden fields. The legit server validates them t ensure that the request originated from the expected source and not some other malicious website. -Anti-CSRF tokens can be managed either in a stateful way: they're put in the -session and are unique for each user and for each kind of action, or in a -stateless way: they're generated on the client-side. +Anti-CSRF tokens can be managed in two ways: using a **stateful** approach, +where tokens are stored in the session and are unique per user and action; or a +**stateless** approach, where tokens are generated on the client side. Installation ------------ @@ -106,7 +106,7 @@ protected forms, among them: field value with it. The most effective way to cache pages that need CSRF protected forms is to use -stateless CSRF tokens, see below. +:ref:`stateless CSRF tokens `, as explained below. .. _csrf-protection-forms: @@ -310,6 +310,8 @@ targeted parts of the plaintext. To mitigate these attacks, and prevent an attacker from guessing the CSRF tokens, a random mask is prepended to the token and used to scramble it. +.. _csrf-stateless-tokens: + Stateless CSRF Tokens --------------------- @@ -363,28 +365,31 @@ option: ; }; -Stateless CSRF tokens use a CSRF protection that doesn't need the session. This -means that you can cache the entire page and still have CSRF protection. +Stateless CSRF tokens provide protection without relying on the session. This +allows you to fully cache pages while still protecting against CSRF attacks. -When a stateless CSRF token is checked for validity, Symfony verifies the -``Origin`` and the ``Referer`` headers of the incoming HTTP request. +When validating a stateless CSRF token, Symfony checks the ``Origin`` and +``Referer`` headers of the incoming HTTP request. If either header matches the +application's target origin (i.e. its domain), the token is considered valid. -If either of these headers match the target origin of the application (its domain -name), the CSRF token is considered valid. This relies on the app being able to -know its own target origin. Don't miss configuring your reverse proxy if you're -behind one. See :doc:`/deployment/proxies`. +This mechanism relies on the application being able to determine its own origin. +If you're behind a reverse proxy, make sure it's properly configured. See +:doc:`/deployment/proxies`. Using a Default Token ID ~~~~~~~~~~~~~~~~~~~~~~~~ -While stateful CSRF tokens are better seggregated per form or action, stateless -ones don't need many token identifiers. In the previous example, ``authenticate`` -and ``logout`` are listed because they're the default identifiers used by the -Symfony Security component. The ``submit`` identifier is then listed so that -form types defined by the application can use it by default. The following -configuration - which applies only to form types declared using autofiguration -(the default way to declare *your* services) - will make your form types use the -``submit`` token identifier by default: +Stateful CSRF tokens are typically scoped per form or action, while stateless +tokens don't require many identifiers. + +In the example above, the ``authenticate`` and ``logout`` identifiers are listed +because they are used by default in the Symfony Security component. The ``submit`` +identifier is included so that form types defined by the application can also use +CSRF protection by default. + +The following configuration applies only to form types registered via +:ref:`autoconfiguration ` (which is the default for your +own services), and it sets ``submit`` as their default token identifier: .. configuration-block:: @@ -433,41 +438,40 @@ option will use the stateless CSRF protection. Generating CSRF Token Using Javascript ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -In addition to the ``Origin`` and ``Referer`` headers, stateless CSRF protection -also checks a cookie and a header (named ``csrf-token`` by default, see the -:ref:`CSRF configuration reference `). - -These extra checks are part of defense-in-depth strategies provided by the -stateless CSRF protection. They are optional and they require -`some JavaScript`_ to be activated. This JavaScript is responsible for generating -a crypto-safe random token when a form is submitted, then putting the token in -the hidden CSRF field of the form and submitting it also as a cookie and header. -On the server-side, the CSRF token is validated by checking the cookie and header -values. This "double-submit" protection relies on the same-origin policy -implemented by browsers and is strengthened by regenerating the token at every -form submission - which prevents cookie fixation issues - and by using -``samesite=strict`` and ``__Host-`` cookies, which make them domain-bound and -HTTPS-only. - -Note that the default snippet of JavaScript provided by Symfony requires that -the hidden CSRF form field is either named ``_csrf_token``, or that it has the -``data-controller="csrf-protection"`` attribute. You can of course take -inspiration from this snippet to write your own, provided you follow the same -protocol. - -As a last measure, a behavioral check is added on the server-side to ensure that -the validation method cannot be downgraded: if and only if a session is already -available, successful "double-submit" is remembered and is then required for -subsequent requests. This prevents attackers from exploiting potentially reduced -validation checks once cookie and/or header validation has been confirmed as -effective (they're optional by default as explained above). +In addition to the ``Origin`` and ``Referer`` HTTP headers, stateless CSRF protection +can also validate tokens using a cookie and a header (named ``csrf-token`` by +default; see the :ref:`CSRF configuration reference `). + +These additional checks are part of the **defense-in-depth** strategy provided by +stateless CSRF protection. They are optional and require `some JavaScript`_ to +be enabled. This JavaScript generates a cryptographically secure random token +when a form is submitted. It then inserts the token into the form's hidden CSRF +field and sends it in both a cookie and a request header. + +On the server side, CSRF token validation compares the values in the cookie and +the header. This "double-submit" protection relies on the browser's same-origin +policy and is further hardened by: + +* generating a new token for each submission (to prevent cookie fixation); +* using ``samesite=strict`` and ``__Host-`` cookie attributes (to enforce HTTPS + and limit the cookie to the current domain). + +By default, the Symfony JavaScript snippet expects the hidden CSRF field to be +named ``_csrf_token`` or to include the ``data-controller="csrf-protection"`` +attribute. You can adapt this logic to your needs as long as the same protocol +is followed. + +To prevent validation from being downgraded, an extra behavioral check is performed: +if (and only if) a session already exists, successful "double-submit" is remembered +and becomes required for future requests. This ensures that once the optional cookie/header +validation has been proven effective, it remains enforced for that session. .. note:: - Enforcing successful "double-submit" for every requests is not recommended as - as it could lead to a broken user experience. The opportunistic approach - described above is preferred because it allows the application to gracefully - degrade to ``Origin`` / ``Referer`` checks when JavaScript is not available. + Enforcing "double-submit" validation on all requests is not recommended, + as it may lead to a broken user experience. The opportunistic approach + described above is preferred, allowing the application to gracefully + fall back to ``Origin`` / ``Referer`` checks when JavaScript is unavailable. .. _`Cross-site request forgery`: https://en.wikipedia.org/wiki/Cross-site_request_forgery .. _`BREACH`: https://en.wikipedia.org/wiki/BREACH From f97b8d59f092fcbc14ea54daa9b59adfb33093ce Mon Sep 17 00:00:00 2001 From: Santiago San Martin Date: Sun, 18 May 2025 11:52:46 -0300 Subject: [PATCH 047/120] [Security] iscsrftokenvalid-attribute-controller-usage --- security/csrf.rst | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/security/csrf.rst b/security/csrf.rst index b303af9511b..cc1ab42482d 100644 --- a/security/csrf.rst +++ b/security/csrf.rst @@ -281,6 +281,20 @@ Suppose you want a CSRF token per item, so in the template you have something li +In addition :class:`Symfony\\Component\\Security\\Http\\Attribute\\IsCsrfTokenValid` +attribute can be applied to a controller class. +This will cause the CSRF token validation to be executed for all routes defined within the controller:: + + use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; + use Symfony\Component\Security\Http\Attribute\IsCsrfTokenValid; + // ... + + #[IsCsrfTokenValid('controller')] + final class FooController extends AbstractController + { + // ... + } + The :class:`Symfony\\Component\\Security\\Http\\Attribute\\IsCsrfTokenValid` attribute also accepts an :class:`Symfony\\Component\\ExpressionLanguage\\Expression` object evaluated to the id:: From 83b30927029e9f60cc4c4a77edba2bbd6bda5f2f Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Thu, 22 May 2025 17:45:51 +0200 Subject: [PATCH 048/120] Minor tweaks --- security/csrf.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/security/csrf.rst b/security/csrf.rst index cc1ab42482d..b72c7cc2526 100644 --- a/security/csrf.rst +++ b/security/csrf.rst @@ -281,16 +281,16 @@ Suppose you want a CSRF token per item, so in the template you have something li -In addition :class:`Symfony\\Component\\Security\\Http\\Attribute\\IsCsrfTokenValid` -attribute can be applied to a controller class. -This will cause the CSRF token validation to be executed for all routes defined within the controller:: +This attribute can also be applied to a controller class. When used this way, +the CSRF token validation will be applied to **all actions** defined in that +controller:: use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\Security\Http\Attribute\IsCsrfTokenValid; // ... - #[IsCsrfTokenValid('controller')] - final class FooController extends AbstractController + #[IsCsrfTokenValid('the token ID')] + final class SomeController extends AbstractController { // ... } From 53e3b0dece9748ae7f45e1b59ef3b7fd37bc9975 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Fri, 23 May 2025 08:43:43 +0200 Subject: [PATCH 049/120] Mention that backward compatibility promise doesn't cover translations --- contributing/code/bc.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/contributing/code/bc.rst b/contributing/code/bc.rst index 497c70fb01d..ad394d2720c 100644 --- a/contributing/code/bc.rst +++ b/contributing/code/bc.rst @@ -176,6 +176,13 @@ covered by our backward compatibility promise: | Use a public, protected or private method | Yes | +-----------------------------------------------+-----------------------------+ +Using our Translations +~~~~~~~~~~~~~~~~~~~~~~ + +All translations provided by Symfony for security and validation errors are +intended for internal use only. They may be changed or removed at any time. +Symfony's Backward Compatibility Promise does not apply to internal translations. + Working on Symfony Code ----------------------- From 2c33c3623d631fc631bf75d3a5dcfbfb3d2c259f Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Fri, 23 May 2025 09:12:33 +0200 Subject: [PATCH 050/120] [FrameworkBundle] Enable controller service with #[Route] attribute --- controller/service.rst | 84 +++++++++++++++++++++++++++++------------- routing.rst | 2 + 2 files changed, 61 insertions(+), 25 deletions(-) diff --git a/controller/service.rst b/controller/service.rst index 88af093ff29..cf83e066a19 100644 --- a/controller/service.rst +++ b/controller/service.rst @@ -7,11 +7,65 @@ and your controllers extend the `AbstractController`_ class, they *are* automati registered as services. This means you can use dependency injection like any other normal service. -If your controllers don't extend the `AbstractController`_ class, you must -explicitly mark your controller services as ``public``. Alternatively, you can -apply the ``controller.service_arguments`` tag to your controller services. This -will make the tagged services ``public`` and will allow you to inject services -in method parameters: +If you prefer to not extend the ``AbstractController`` class, you can register +your controllers as services in several ways: + +#. Using the ``#[Route]`` attribute; +#. Using the ``#[AsController]`` attribute; +#. Using the ``controller.service_arguments`` service tag. + +Using the ``#[Route]`` Attribute +-------------------------------- + +When using :ref:`the #[Route] attribute ` to define +routes on any PHP class, Symfony treats that class as a controller. It registers +it as a public, non-lazy service and enables service argument injection in all +its methods. + +This is the simplest and recommended way to register controllers as services +when not extending the base controller class. + +.. versionadded:: 7.3 + + The feature to register controllers as services when using the ``#[Route]`` + attribute was introduced in Symfony 7.3. + +Using the ``#[AsController]`` Attribute +--------------------------------------- + +If you prefer, you can use the ``#[AsController]`` PHP attribute to automatically +apply the ``controller.service_arguments`` tag to your controller services:: + + // src/Controller/HelloController.php + namespace App\Controller; + + use Symfony\Component\HttpFoundation\Response; + use Symfony\Component\HttpKernel\Attribute\AsController; + use Symfony\Component\Routing\Attribute\Route; + + #[AsController] + class HelloController + { + #[Route('/hello', name: 'hello', methods: ['GET'])] + public function index(): Response + { + // ... + } + } + +.. tip:: + + When using the ``#[Route]`` attribute, Symfony already registers the controller + class as a service, so using the ``#[AsController]`` attribute is redundant. + +Using the ``controller.service_arguments`` Service Tag +------------------------------------------------------ + +If your controllers don't extend the `AbstractController`_ class and you don't +use the ``#[AsController]`` or ``#[Route]`` attributes, you must register the +controllers as public services manually and apply the ``controller.service_arguments`` +:doc:`service tag ` to enable service injection in +controller actions: .. configuration-block:: @@ -58,26 +112,6 @@ in method parameters: calls: - [setContainer, ['@abstract_controller.locator']] -If you prefer, you can use the ``#[AsController]`` PHP attribute to automatically -apply the ``controller.service_arguments`` tag to your controller services:: - - // src/Controller/HelloController.php - namespace App\Controller; - - use Symfony\Component\HttpFoundation\Response; - use Symfony\Component\HttpKernel\Attribute\AsController; - use Symfony\Component\Routing\Attribute\Route; - - #[AsController] - class HelloController - { - #[Route('/hello', name: 'hello', methods: ['GET'])] - public function index(): Response - { - // ... - } - } - Registering your controller as a service is the first step, but you also need to update your routing config to reference the service properly, so that Symfony knows to use it. diff --git a/routing.rst b/routing.rst index e634a410c37..b8b2437ee3e 100644 --- a/routing.rst +++ b/routing.rst @@ -18,6 +18,8 @@ your favorite. :ref:`Symfony recommends attributes ` because it's convenient to put the route and controller in the same place. +.. _routing-route-attributes: + Creating Routes as Attributes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From 310783b4c0a9f1b329c010b1c20dcdfed5643400 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Fri, 23 May 2025 09:55:29 +0200 Subject: [PATCH 051/120] [Translation] Don't mention the abandoned Doctrine Translatable behavior --- translation.rst | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/translation.rst b/translation.rst index b9fbdd7e0f6..f41d0cb6489 100644 --- a/translation.rst +++ b/translation.rst @@ -599,8 +599,7 @@ Translations of Doctrine Entities Unlike the contents of templates, it's not practical to translate the contents stored in Doctrine Entities using translation catalogs. Instead, use the -Doctrine `Translatable Extension`_ or the `Translatable Behavior`_. For more -information, read the documentation of those libraries. +Doctrine `Translatable Extension`_. Custom Translation Resources ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1599,7 +1598,6 @@ Learn more .. _`ISO 3166-1 alpha-2`: https://en.wikipedia.org/wiki/ISO_3166-1#Current_codes .. _`ISO 639-1`: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes .. _`Translatable Extension`: https://github.com/doctrine-extensions/DoctrineExtensions/blob/main/doc/translatable.md -.. _`Translatable Behavior`: https://github.com/KnpLabs/DoctrineBehaviors .. _`Custom Language Name setting`: https://docs.lokalise.com/en/articles/1400492-uploading-files#custom-language-codes .. _`ICU resource bundle`: https://github.com/unicode-org/icu-docs/blob/main/design/bnf_rb.txt .. _`Portable object format`: https://www.gnu.org/software/gettext/manual/html_node/PO-Files.html From 203c0f8e21071ad1396ef12686a344e39f47a1bc Mon Sep 17 00:00:00 2001 From: MrYamous Date: Tue, 25 Feb 2025 20:45:57 +0100 Subject: [PATCH 052/120] [Security] Add ability for voters to explain their vote --- security.rst | 8 +++++++- security/voters.rst | 30 +++++++++++++++++++++++------- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/security.rst b/security.rst index ca910765be0..5a6fc15ae2e 100644 --- a/security.rst +++ b/security.rst @@ -2704,13 +2704,14 @@ anonymous users access by checking if there is no user set on the token:: // ... use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authentication\User\UserInterface; + use Symfony\Component\Security\Core\Authorization\Voter\Vote; use Symfony\Component\Security\Core\Authorization\Voter\Voter; class PostVoter extends Voter { // ... - protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool + protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token, ?Vote $vote = null): bool { // ... @@ -2722,6 +2723,11 @@ anonymous users access by checking if there is no user set on the token:: } } +.. versionadded:: 7.3 + + The `$vote` parameter in the :method:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface::voteOnAttribute` method + was introduced in Symfony 7.3. + Setting Individual User Permissions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/security/voters.rst b/security/voters.rst index e7452fadf99..ba4c96fe6fd 100644 --- a/security/voters.rst +++ b/security/voters.rst @@ -40,14 +40,20 @@ or extend :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\Vote which makes creating a voter even easier:: use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; + use Symfony\Component\Security\Core\Authorization\Voter\Vote; use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; abstract class Voter implements VoterInterface { abstract protected function supports(string $attribute, mixed $subject): bool; - abstract protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool; + abstract protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token, ?Vote $vote = null): bool; } +.. versionadded:: 7.3 + + The `$vote` parameter in the :method:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface::voteOnAttribute` method + was introduced in Symfony 7.3. + .. _how-to-use-the-voter-in-a-controller: .. tip:: @@ -140,6 +146,7 @@ would look like this:: use App\Entity\Post; use App\Entity\User; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; + use Symfony\Component\Security\Core\Authorization\Voter\Vote; use Symfony\Component\Security\Core\Authorization\Voter\Voter; class PostVoter extends Voter @@ -163,12 +170,14 @@ would look like this:: return true; } - protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool + protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token, ?Vote $vote = null): bool { $user = $token->getUser(); + $vote ??= new Vote(); if (!$user instanceof User) { // the user must be logged in; if not, deny access + $vote->reasons[] = 'The user is not logged in.'; return false; } @@ -197,7 +206,13 @@ would look like this:: private function canEdit(Post $post, User $user): bool { // this assumes that the Post object has a `getOwner()` method - return $user === $post->getOwner(); + if ($user === $post->getOwner()) { + return true; + } + + $vote->reasons[] = 'You are not the owner of the Post.'; + + return false; } } @@ -215,11 +230,12 @@ To recap, here's what's expected from the two abstract methods: return ``true`` if the attribute is ``view`` or ``edit`` and if the object is a ``Post`` instance. -``voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token)`` +``voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token, ?Vote $vote = null)`` If you return ``true`` from ``supports()``, then this method is called. Your job is to return ``true`` to allow access and ``false`` to deny access. - The ``$token`` can be used to find the current user object (if any). In this - example, all of the complex business logic is included to determine access. + The ``$token`` can be used to find the current user object (if any). The ``$vote`` + argument can be used to add a reason to the vote. In this example, all of the + complex business logic is included to determine access. .. _declaring-the-voter-as-a-service: @@ -256,7 +272,7 @@ with ``ROLE_SUPER_ADMIN``:: ) { } - protected function voteOnAttribute($attribute, mixed $subject, TokenInterface $token): bool + protected function voteOnAttribute($attribute, mixed $subject, TokenInterface $token, ?Vote $vote = null): bool { // ... From ef9c945fe180c9a7a459b37e6a13fd0f63c9678d Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Fri, 23 May 2025 10:24:48 +0200 Subject: [PATCH 053/120] Minor tweaks --- security.rst | 4 ++-- security/voters.rst | 22 ++++++++++++---------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/security.rst b/security.rst index 8b1c8a96e69..9d2df6165d0 100644 --- a/security.rst +++ b/security.rst @@ -2715,8 +2715,8 @@ anonymous users access by checking if there is no user set on the token:: .. versionadded:: 7.3 - The `$vote` parameter in the :method:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface::voteOnAttribute` method - was introduced in Symfony 7.3. + The ``$vote`` argument of the ``voteOnAttribute()`` method was introduced + in Symfony 7.3. Setting Individual User Permissions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/security/voters.rst b/security/voters.rst index ba4c96fe6fd..2b4a5af54e2 100644 --- a/security/voters.rst +++ b/security/voters.rst @@ -51,8 +51,8 @@ which makes creating a voter even easier:: .. versionadded:: 7.3 - The `$vote` parameter in the :method:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface::voteOnAttribute` method - was introduced in Symfony 7.3. + The ``$vote`` argument of the ``voteOnAttribute()`` method was introduced + in Symfony 7.3. .. _how-to-use-the-voter-in-a-controller: @@ -173,11 +173,10 @@ would look like this:: protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token, ?Vote $vote = null): bool { $user = $token->getUser(); - $vote ??= new Vote(); if (!$user instanceof User) { // the user must be logged in; if not, deny access - $vote->reasons[] = 'The user is not logged in.'; + $vote?->addReason('The user is not logged in.'); return false; } @@ -205,12 +204,15 @@ would look like this:: private function canEdit(Post $post, User $user): bool { - // this assumes that the Post object has a `getOwner()` method - if ($user === $post->getOwner()) { + // this assumes that the Post object has a `getAuthor()` method + if ($user === $post->getAuthor()) { return true; } - $vote->reasons[] = 'You are not the owner of the Post.'; + $vote?->addReason(sprintf( + 'The logged in user (username: %s) is not the author of this post (id: %d).', + $user->getUsername(), $post->getId() + )); return false; } @@ -233,9 +235,9 @@ To recap, here's what's expected from the two abstract methods: ``voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token, ?Vote $vote = null)`` If you return ``true`` from ``supports()``, then this method is called. Your job is to return ``true`` to allow access and ``false`` to deny access. - The ``$token`` can be used to find the current user object (if any). The ``$vote`` - argument can be used to add a reason to the vote. In this example, all of the - complex business logic is included to determine access. + The ``$token`` can be used to find the current user object (if any). + The ``$vote`` argument can be used to provide an explanation for the vote. + This explanation is included in log messages and on exception pages. .. _declaring-the-voter-as-a-service: From a2c83bfb2fc02162dcc2e13a5d67d6f187c861a8 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Fri, 23 May 2025 10:50:10 +0200 Subject: [PATCH 054/120] Minor tweaks --- workflow.rst | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/workflow.rst b/workflow.rst index 544f775cce0..482fe8ed273 100644 --- a/workflow.rst +++ b/workflow.rst @@ -1306,11 +1306,11 @@ In Twig templates, metadata is available via the ``workflow_metadata()`` functio

-Adding Custom Definition Validators -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Validating Workflow Definitions +------------------------------- -Sometimes, you may want to add custom logics to validate your workflow definition. -To do this, you need to implement the +Symfony allows you to validate workflow definitions using your own custom logic. +To do so, create a class that implements the :class:`Symfony\\Component\\Workflow\\Validator\\DefinitionValidatorInterface`:: namespace App\Workflow\Validator; @@ -1326,11 +1326,12 @@ To do this, you need to implement the if (!$definition->getMetadataStore()->getMetadata('title')) { throw new InvalidDefinitionException(sprintf('The workflow metadata title is missing in Workflow "%s".', $name)); } + + // ... } } -Once your definition validator is implemented, you can configure your workflow to use -it: +After implementing your validator, configure your workflow to use it: .. configuration-block:: @@ -1340,7 +1341,7 @@ it: framework: workflows: blog_publishing: - # ... previous configuration + # ... definition_validators: - App\Workflow\Validator\BlogPublishingValidator @@ -1357,7 +1358,7 @@ it: > - + App\Workflow\Validator\BlogPublishingValidator @@ -1370,7 +1371,7 @@ it: return static function (FrameworkConfig $framework): void { $blogPublishing = $framework->workflows()->workflows('blog_publishing'); - // ... previous configuration + // ... $blogPublishing->definitionValidators([ App\Workflow\Validator\BlogPublishingValidator::class @@ -1379,11 +1380,12 @@ it: // ... }; -The ``BlogPublishingValidator`` definition validator will be executed during the container compilation. +The ``BlogPublishingValidator`` will be executed during container compilation +to validate the workflow definition. .. versionadded:: 7.3 - Support for defining custom workflow definition validators was introduced in Symfony 7.3. + Support for workflow definition validators was introduced in Symfony 7.3. Learn more ---------- From 6a908963b4574972ce644893138b76e5a52609c2 Mon Sep 17 00:00:00 2001 From: Romain Card <47689092+Synxgz@users.noreply.github.com> Date: Fri, 23 May 2025 10:57:44 +0200 Subject: [PATCH 055/120] [Security] Fix type in `upgradePassword` --- security/passwords.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/security/passwords.rst b/security/passwords.rst index fe20187b3a0..4bf481fa827 100644 --- a/security/passwords.rst +++ b/security/passwords.rst @@ -500,13 +500,14 @@ the user provider:: namespace App\Security; // ... + use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; use Symfony\Component\Security\Core\User\PasswordUpgraderInterface; class UserProvider implements UserProviderInterface, PasswordUpgraderInterface { // ... - public function upgradePassword(UserInterface $user, string $newHashedPassword): void + public function upgradePassword(PasswordAuthenticatedUserInterface $user, string $newHashedPassword): void { // set the new hashed password on the User object $user->setPassword($newHashedPassword); From faa30fc32ec1a0360baa201db0afd7edcf4861bb Mon Sep 17 00:00:00 2001 From: Thomas Landauer Date: Sun, 25 May 2025 11:52:44 +0200 Subject: [PATCH 056/120] [Security] Stateless CSRF is enabled by default in 7.2 Page: https://symfony.com/doc/current/security/csrf.html#stateless-csrf-tokens Info is taken from https://github.com/symfony/recipes/blob/main/symfony/form/7.2/config/packages/csrf.yaml --- security/csrf.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/security/csrf.rst b/security/csrf.rst index b72c7cc2526..07e0671f07b 100644 --- a/security/csrf.rst +++ b/security/csrf.rst @@ -331,9 +331,9 @@ Stateless CSRF Tokens .. versionadded:: 7.2 - Stateless anti-CSRF protection was introduced in Symfony 7.2. + Stateless anti-CSRF protection was introduced in Symfony 7.2, and set as default. -By default CSRF tokens are stateful, which means they're stored in the session. +Traditionally CSRF tokens are stateful, which means they're stored in the session. But some token ids can be declared as stateless using the ``stateless_token_ids`` option: From a73b542aa43ab744336cfbf8e341467f7746d13e Mon Sep 17 00:00:00 2001 From: Hamza Makraz <19323431+makraz@users.noreply.github.com> Date: Sat, 24 May 2025 22:25:52 +0100 Subject: [PATCH 057/120] Update AsTaggedItem parameters name in the file value_resolver.rst --- controller/value_resolver.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controller/value_resolver.rst b/controller/value_resolver.rst index 48d0ba17bdb..f0f0db9aff1 100644 --- a/controller/value_resolver.rst +++ b/controller/value_resolver.rst @@ -382,7 +382,7 @@ but you can set it yourself to change its ``priority`` or ``name`` attributes. use Symfony\Component\DependencyInjection\Attribute\AsTaggedItem; use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; - #[AsTaggedItem(name: 'booking_id', priority: 150)] + #[AsTaggedItem(index: 'booking_id', priority: 150)] class BookingIdValueResolver implements ValueResolverInterface { // ... From d7eae45b90ca74ab0fed17efc2708a543c11c046 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Andr=C3=A9?= Date: Sat, 24 May 2025 15:06:55 +0200 Subject: [PATCH 058/120] [Form] Remove link to abandoned collection package --- form/form_collections.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/form/form_collections.rst b/form/form_collections.rst index 2a0ba99657f..3c8a2050690 100644 --- a/form/form_collections.rst +++ b/form/form_collections.rst @@ -699,13 +699,12 @@ the relationship between the removed ``Tag`` and ``Task`` object. The Symfony community has created some JavaScript packages that provide the functionality needed to add, edit and delete elements of the collection. - Check out the `@a2lix/symfony-collection`_ package for modern browsers and - the `symfony-collection`_ package based on jQuery for the rest of browsers. + Check out the `@a2lix/symfony-collection`_ or search on GitHub for other + recent packages. .. _`Owning Side and Inverse Side`: https://www.doctrine-project.org/projects/doctrine-orm/en/current/reference/unitofwork-associations.html .. _`JSFiddle`: https://jsfiddle.net/ey8ozh6n/ .. _`@a2lix/symfony-collection`: https://github.com/a2lix/symfony-collection -.. _`symfony-collection`: https://github.com/ninsuo/symfony-collection .. _`ArrayCollection`: https://www.doctrine-project.org/projects/doctrine-collections/en/1.6/index.html .. _`Symfony UX Demo of Form Collections`: https://ux.symfony.com/live-component/demos/form-collection-type .. _`Stimulus`: https://symfony.com/doc/current/frontend/encore/simple-example.html#stimulus-symfony-ux From f8cdbc89d9aadb4b509a314d28d093fb65f02557 Mon Sep 17 00:00:00 2001 From: miqrogroove <1371835+miqrogroove@users.noreply.github.com> Date: Sun, 25 May 2025 11:15:12 -0400 Subject: [PATCH 059/120] Fixed typo in event_dispatcher.rst --- event_dispatcher.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/event_dispatcher.rst b/event_dispatcher.rst index d9b913ed49f..13ef1624370 100644 --- a/event_dispatcher.rst +++ b/event_dispatcher.rst @@ -149,7 +149,7 @@ Defining Event Listeners with PHP Attributes An alternative way to define an event listener is to use the :class:`Symfony\\Component\\EventDispatcher\\Attribute\\AsEventListener` -PHP attribute. This allows to configure the listener inside its class, without +PHP attribute. This allows you to configure the listener inside its class, without having to add any configuration in external files:: namespace App\EventListener; From ff3a594325e29c00b95519ef2f422c3749e2531f Mon Sep 17 00:00:00 2001 From: Ilya Bakhlin Lebedev Date: Sun, 25 May 2025 12:11:13 +0200 Subject: [PATCH 060/120] Updating the web_server_configuration.rst File In the documentation, it's being said that Symfony requires the `symfony/apache-pack`, which adds a `.htaccess` file to the `public/` directory. However, it's nowhere specified to change the `AllowOverride` setting from `None` to `All`. --- setup/web_server_configuration.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup/web_server_configuration.rst b/setup/web_server_configuration.rst index fdedfc81794..27bcc67aab0 100644 --- a/setup/web_server_configuration.rst +++ b/setup/web_server_configuration.rst @@ -195,7 +195,8 @@ directive to pass requests for PHP files to PHP FPM: 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 + the Symfony application. Don't forget to change the Apache's ``AllowOverride None`` setting to + ``AllowOverride All``. However, in production, it's recommended to move these rules to the main Apache configuration file (as shown above) to improve performance. Caddy From 712707d07a4a322f8e387f9209279c949b50ae1a Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Mon, 26 May 2025 12:59:37 +0200 Subject: [PATCH 061/120] Minor tweaks --- setup/web_server_configuration.rst | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/setup/web_server_configuration.rst b/setup/web_server_configuration.rst index 27bcc67aab0..43bd79c10dd 100644 --- a/setup/web_server_configuration.rst +++ b/setup/web_server_configuration.rst @@ -192,12 +192,14 @@ directive to pass requests for PHP files to PHP FPM: .. 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. Don't forget to change the Apache's ``AllowOverride None`` setting to - ``AllowOverride All``. However, in production, it's recommended to move these - rules to the main Apache configuration file (as shown above) to improve performance. + If you're running some quick tests with Apache, you can run + ``composer require symfony/apache-pack`` to create an ``.htaccess`` file in + the ``public/`` directory with the rewrite rules needed to serve the Symfony + application. Make sure Apache's ``AllowOverride`` setting is set to ``All`` + for that directory; otherwise, the ``.htaccess`` file will be ignored. + + In production, however, it's recommended to move these rules to the main + Apache configuration file (as shown above) to improve performance. Caddy ----- From ff3f1a1a6ba524581818259645d2ab99d46d7c07 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Mon, 26 May 2025 15:29:35 +0200 Subject: [PATCH 062/120] [HttpClient] Update the concurrent requests section --- http_client.rst | 75 +++++++++++++++++++++++++++---------------------- 1 file changed, 41 insertions(+), 34 deletions(-) diff --git a/http_client.rst b/http_client.rst index f4be8b2460b..e2b0b4f41bf 100644 --- a/http_client.rst +++ b/http_client.rst @@ -1314,65 +1314,72 @@ remaining to do. Concurrent Requests ------------------- -Thanks to responses being lazy, requests are always managed concurrently. -On a fast enough network, the following code makes 379 requests in less than -half a second when cURL is used:: +Symfony's HTTP client makes asynchronous HTTP requests by default. This means +you don't need to configure anything special to send multiple requests in parallel +and process them efficiently. +Here's a practical example that fetches metadata about several Symfony +components from the Packagist API in parallel:: + + $packages = ['console', 'http-kernel', '...', 'routing', 'yaml']; $responses = []; - for ($i = 0; $i < 379; ++$i) { - $uri = "https://http2.akamai.com/demo/tile-$i.png"; - $responses[] = $client->request('GET', $uri); + foreach ($packages as $package) { + $uri = sprintf('https://repo.packagist.org/p2/symfony/%s.json', $package); + // send all requests concurrently (they won't block until response content is read) + $responses[$package] = $client->request('GET', $uri); } - foreach ($responses as $response) { - $content = $response->getContent(); - // ... + $results = []; + // iterate through the responses and read their content + foreach ($responses as $package => $response) { + // process response data somehow ... + $results[$package] = $response->toArray(); } -As you can read in the first "for" loop, requests are issued but are not consumed -yet. That's the trick when concurrency is desired: requests should be sent -first and be read later on. This will allow the client to monitor all pending -requests while your code waits for a specific one, as done in each iteration of -the above "foreach" loop. +As you can see, the requests are sent in the first loop, but their responses +aren't consumed until the second one. This is the key to achieving parallel and +concurrent execution: dispatch all requests first, and read them later. +This allows the client to handle all pending responses efficiently while your +code waits only when necessary. .. note:: - The maximum number of concurrent requests that you can perform depends on - the resources of your machine (e.g. your operating system may limit the - number of simultaneous reads of the file that stores the certificates - file). Make your requests in batches to avoid these issues. + The maximum number of concurrent requests depends on your system's resources + (e.g. the operating system might limit the number of simultaneous connections + or access to certificate files). To avoid hitting these limits, consider + processing requests in batches. Multiplexing Responses ~~~~~~~~~~~~~~~~~~~~~~ -If you look again at the snippet above, responses are read in requests' order. -But maybe the 2nd response came back before the 1st? Fully asynchronous operations -require being able to deal with the responses in whatever order they come back. +In the previous example, responses are read in the same order as the requests +were sent. However, it's possible that, for instance, the second response arrives +before the first. To handle such cases efficiently, you need fully asynchronous +processing, which allows responses to be handled in whatever order they arrive. -In order to do so, the -:method:`Symfony\\Contracts\\HttpClient\\HttpClientInterface::stream` -accepts a list of responses to monitor. As mentioned +To achieve this, the +:method:`Symfony\\Contracts\\HttpClient\\HttpClientInterface::stream` method +can be used to monitor a list of responses. As mentioned :ref:`previously `, this method yields response -chunks as they arrive from the network. By replacing the "foreach" in the -snippet with this one, the code becomes fully async:: +chunks as soon as they arrive over the network. Replacing the standard ``foreach`` +loop with the following version enables true asynchronous behavior:: foreach ($client->stream($responses) as $response => $chunk) { if ($chunk->isFirst()) { - // headers of $response just arrived - // $response->getHeaders() is now a non-blocking call + // the $response headers just arrived + // $response->getHeaders() is now non-blocking } elseif ($chunk->isLast()) { - // the full content of $response just completed - // $response->getContent() is now a non-blocking call + // the full $response body has been received + // $response->getContent() is now non-blocking } else { - // $chunk->getContent() will return a piece - // of the response body that just arrived + // $chunk->getContent() returns a piece of the body that just arrived } } .. tip:: - Use the ``user_data`` option combined with ``$response->getInfo('user_data')`` - to track the identity of the responses in your foreach loops. + Use the ``user_data`` option along with ``$response->getInfo('user_data')`` + to identify each response during streaming. Dealing with Network Timeouts ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From 1ea48c70da87d7cebe252c7fb94cb0fce499834f Mon Sep 17 00:00:00 2001 From: Kevin Bond Date: Wed, 21 May 2025 18:15:40 -0400 Subject: [PATCH 063/120] [Security] remove `plaintext` password hasher usage --- security/passwords.rst | 90 +++++++++++++++++++----------------------- 1 file changed, 40 insertions(+), 50 deletions(-) diff --git a/security/passwords.rst b/security/passwords.rst index 4bf481fa827..9ebfa122ab2 100644 --- a/security/passwords.rst +++ b/security/passwords.rst @@ -124,25 +124,22 @@ Further in this article, you can find a .. code-block:: yaml - # config/packages/test/security.yaml - security: - # ... - - password_hashers: - # Use your user class name here - App\Entity\User: - algorithm: plaintext # disable hashing (only do this in tests!) - - # or use the lowest possible values - App\Entity\User: - algorithm: auto # This should be the same value as in config/packages/security.yaml - cost: 4 # Lowest possible value for bcrypt - time_cost: 3 # Lowest possible value for argon - memory_cost: 10 # Lowest possible value for argon + # config/packages/security.yaml + when@test: + security: + # ... + + password_hashers: + # Use your user class name here + App\Entity\User: + algorithm: auto + cost: 4 # Lowest possible value for bcrypt + time_cost: 3 # Lowest possible value for argon + memory_cost: 10 # Lowest possible value for argon .. code-block:: xml - + - - - - - - - - - - - - + + + + + + + + + .. code-block:: php - // config/packages/test/security.php + // config/packages/security.php use App\Entity\User; + use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; use Symfony\Config\SecurityConfig; - return static function (SecurityConfig $security): void { + return static function (SecurityConfig $security, ContainerConfigurator $container): void { // ... - // Use your user class name here - $security->passwordHasher(User::class) - ->algorithm('plaintext'); // disable hashing (only do this in tests!) - - // or use the lowest possible values - $security->passwordHasher(User::class) - ->algorithm('auto') // This should be the same value as in config/packages/security.yaml - ->cost(4) // Lowest possible value for bcrypt - ->timeCost(2) // Lowest possible value for argon - ->memoryCost(10) // Lowest possible value for argon - ; + if ('test' === $container->env()) { + // Use your user class name here + $security->passwordHasher(User::class) + ->algorithm('auto') // This should be the same value as in config/packages/security.yaml + ->cost(4) // Lowest possible value for bcrypt + ->timeCost(2) // Lowest possible value for argon + ->memoryCost(10) // Lowest possible value for argon + ; + } }; Hashing the Password From 1851eae15c33913d2b483098b132ffb5ef548c5f Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Tue, 27 May 2025 08:13:25 +0200 Subject: [PATCH 064/120] Remove the wrong XML config sample --- security/passwords.rst | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/security/passwords.rst b/security/passwords.rst index 9ebfa122ab2..7f05bc3acb9 100644 --- a/security/passwords.rst +++ b/security/passwords.rst @@ -137,33 +137,6 @@ Further in this article, you can find a time_cost: 3 # Lowest possible value for argon memory_cost: 10 # Lowest possible value for argon - .. code-block:: xml - - - - - - - - - - - - - - - - .. code-block:: php // config/packages/security.php From 7d13bac560ff6506c3eaaf734aa388d7f7d3bf19 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Tue, 27 May 2025 08:28:54 +0200 Subject: [PATCH 065/120] Minor tweak --- security/csrf.rst | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/security/csrf.rst b/security/csrf.rst index 07e0671f07b..29fe96fa689 100644 --- a/security/csrf.rst +++ b/security/csrf.rst @@ -331,11 +331,12 @@ Stateless CSRF Tokens .. versionadded:: 7.2 - Stateless anti-CSRF protection was introduced in Symfony 7.2, and set as default. + Stateless anti-CSRF protection was introduced in Symfony 7.2. -Traditionally CSRF tokens are stateful, which means they're stored in the session. -But some token ids can be declared as stateless using the ``stateless_token_ids`` -option: +Traditionally, CSRF tokens are stateful, meaning they're stored in the session. +However, some token IDs can be declared as stateless using the +``stateless_token_ids`` option. Stateless CSRF tokens are enabled by default +in applications using :ref:`Symfony Flex `. .. configuration-block:: From 881051cce0d0c678da689bcdd268f52e29c2a8de Mon Sep 17 00:00:00 2001 From: Dustin Meiner <168725484+curenect-meiner@users.noreply.github.com> Date: Sun, 25 May 2025 14:39:42 +0200 Subject: [PATCH 066/120] Update uid.rst with the string almost their is failure cause symfony dont find the class. This prevents it form it. --- components/uid.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/uid.rst b/components/uid.rst index b031348f85a..fe15f964f44 100644 --- a/components/uid.rst +++ b/components/uid.rst @@ -527,6 +527,7 @@ entity primary keys:: namespace App\Entity; use Doctrine\ORM\Mapping as ORM; + use Symfony\Bridge\Doctrine\IdGenerator\UlidGenerator; use Symfony\Bridge\Doctrine\Types\UlidType; use Symfony\Component\Uid\Ulid; @@ -535,7 +536,7 @@ entity primary keys:: #[ORM\Id] #[ORM\Column(type: UlidType::NAME, unique: true)] #[ORM\GeneratedValue(strategy: 'CUSTOM')] - #[ORM\CustomIdGenerator(class: 'doctrine.ulid_generator')] + #[ORM\CustomIdGenerator(class: UlidGenerator::class)] private ?Ulid $id; public function getId(): ?Ulid From f98e8dc1d1853dd88d0c40c28e441952b90a3538 Mon Sep 17 00:00:00 2001 From: chx Date: Tue, 22 Apr 2025 08:47:55 +0200 Subject: [PATCH 067/120] Clarify the code flow a little bit more --- components/runtime.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/runtime.rst b/components/runtime.rst index 4eb75de2a75..336a8573227 100644 --- a/components/runtime.rst +++ b/components/runtime.rst @@ -36,6 +36,8 @@ So how does this front-controller work? At first, the special the component. This file runs the following logic: #. It instantiates a :class:`Symfony\\Component\\Runtime\\RuntimeInterface`; +#. The runtime includes the front-controller script -- in this case + ``public/index.php`` -- making it run again. Make sure this doesn't cause problems. #. The callable (returned by ``public/index.php``) is passed to the Runtime, whose job is to resolve the arguments (in this example: ``array $context``); #. Then, this callable is called to get the application (``App\Kernel``); From 7ada520c4c6856701cd7e4a30df9314da859c086 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Tue, 27 May 2025 09:11:49 +0200 Subject: [PATCH 068/120] Minor tweak --- components/runtime.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/components/runtime.rst b/components/runtime.rst index 336a8573227..770ea102563 100644 --- a/components/runtime.rst +++ b/components/runtime.rst @@ -36,8 +36,9 @@ So how does this front-controller work? At first, the special the component. This file runs the following logic: #. It instantiates a :class:`Symfony\\Component\\Runtime\\RuntimeInterface`; -#. The runtime includes the front-controller script -- in this case - ``public/index.php`` -- making it run again. Make sure this doesn't cause problems. +#. The front-controller script (e.g. ``public/index.php``) is included by the + runtime, making it run again. Ensure this doesn't produce any side effects + in your code; #. The callable (returned by ``public/index.php``) is passed to the Runtime, whose job is to resolve the arguments (in this example: ``array $context``); #. Then, this callable is called to get the application (``App\Kernel``); From 4e67b7324679f9fbffa24aad0d0635c41be8285a Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Tue, 27 May 2025 09:21:16 +0200 Subject: [PATCH 069/120] Reword --- messenger.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/messenger.rst b/messenger.rst index e37f075518a..97b95565fa7 100644 --- a/messenger.rst +++ b/messenger.rst @@ -2468,7 +2468,8 @@ Possible options to configure with tags are: Name of the method that will process the message. ``priority`` - Priority of the handler when multiple handlers can process the same message; higher values will be processed first. + Defines the order in which the handler is executed when multiple handlers + can process the same message; those with higher priority run first. .. _handler-subscriber-options: From 9323d4717cbbbdf31108d122982e821330a75e89 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Tue, 27 May 2025 09:22:09 +0200 Subject: [PATCH 070/120] [Messenger] Backoport an improvement in the priority description --- messenger.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/messenger.rst b/messenger.rst index 18c7be913b5..cc0b13c3467 100644 --- a/messenger.rst +++ b/messenger.rst @@ -2416,7 +2416,8 @@ Possible options to configure with tags are: Name of the method that will process the message. ``priority`` - Priority of the handler when multiple handlers can process the same message. + Defines the order in which the handler is executed when multiple handlers + can process the same message; those with higher priority run first. .. _handler-subscriber-options: From 01f381305940492ddbbfc0ab54a30db383971ac4 Mon Sep 17 00:00:00 2001 From: ifiroth <97906657+ifiroth@users.noreply.github.com> Date: Tue, 27 May 2025 11:11:06 +0200 Subject: [PATCH 071/120] Update voters.rst I think you're missing an argument here --- security/voters.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/security/voters.rst b/security/voters.rst index 2b4a5af54e2..f20e1de62af 100644 --- a/security/voters.rst +++ b/security/voters.rst @@ -186,7 +186,7 @@ would look like this:: return match($attribute) { self::VIEW => $this->canView($post, $user), - self::EDIT => $this->canEdit($post, $user), + self::EDIT => $this->canEdit($post, $user, $vote), default => throw new \LogicException('This code should not be reached!') }; } @@ -202,7 +202,7 @@ would look like this:: return !$post->isPrivate(); } - private function canEdit(Post $post, User $user): bool + private function canEdit(Post $post, User $user, ?Vote $vote): bool { // this assumes that the Post object has a `getAuthor()` method if ($user === $post->getAuthor()) { From 91a952566fc2a93c08cb162eeddb1fc83edef0ab Mon Sep 17 00:00:00 2001 From: NanoSector Date: Mon, 24 Mar 2025 21:31:11 +0100 Subject: [PATCH 072/120] [Doctrine] entityvalueresolver target entities --- doctrine.rst | 22 +++++++++++++++++ doctrine/resolve_target_entity.rst | 39 +++++++++++++++++------------- 2 files changed, 44 insertions(+), 17 deletions(-) diff --git a/doctrine.rst b/doctrine.rst index 171f8a3348a..876aaa3065d 100644 --- a/doctrine.rst +++ b/doctrine.rst @@ -780,6 +780,28 @@ variable. Let's say you want the first or the last comment of a product dependin Comment $comment ): Response { } + +.. _doctrine-entity-value-resolver-resolve-target-entities: + +Fetch via Interfaces +~~~~~~~~~~~~~~~~~~~~ + +Suppose your ``Product`` object implements an interface called ``ProductInterface``. +If you want to decouple your controllers from your entity implementations, you can instead reference entities via an interface. +To do this, first you need to configure the :doc:`resolve_target_entities option `. + +Your controller can then reference the Product entity by its interface instead:: + + public function show( + #[MapEntity] + ProductInterface $product + ): Response { + // ... + } + +.. versionadded:: 7.3 + + Support for ``resolve_target_entites`` in the ``EntityValueResolver`` was introduced in Symfony 7.3. MapEntity Options ~~~~~~~~~~~~~~~~~ diff --git a/doctrine/resolve_target_entity.rst b/doctrine/resolve_target_entity.rst index 5ae6475a957..c55b27f6463 100644 --- a/doctrine/resolve_target_entity.rst +++ b/doctrine/resolve_target_entity.rst @@ -1,30 +1,35 @@ -How to Define Relationships with Abstract Classes and Interfaces -================================================================ +Referencing Entities with Abstract Classes and Interfaces +========================================================= -One of the goals of bundles is to create discrete bundles of functionality -that do not have many (if any) dependencies, allowing you to use that -functionality in other applications without including unnecessary items. +In applications where functionality is segregated with minimal concrete dependencies +between the various layers, such as monoliths which are split into multiple modules, +it might be hard to prevent hard dependencies on entities between modules. Doctrine 2.2 includes a new utility called the ``ResolveTargetEntityListener``, that functions by intercepting certain calls inside Doctrine and rewriting ``targetEntity`` parameters in your metadata mapping at runtime. It means that -in your bundle you are able to use an interface or abstract class in your -mappings and expect correct mapping to a concrete entity at runtime. +you are able to use an interface or abstract class in your mappings and expect +correct mapping to a concrete entity at runtime. This functionality allows you to define relationships between different entities without making them hard dependencies. +.. tip:: + + Starting with Symfony 7.3, this functionality also works with the ``EntityValueResolver``. + See :ref:`doctrine-entity-value-resolver-resolve-target-entities` for more details. + Background ---------- -Suppose you have an InvoiceBundle which provides invoicing functionality -and a CustomerBundle that contains customer management tools. You want -to keep these separated, because they can be used in other systems without -each other, but for your application you want to use them together. +Suppose you have an application which provides two modules; an Invoice module which +provides invoicing functionality, and a Customer module that contains customer management +tools. You want to keep dependencies between these modules separated, because they should +not be aware of the other module's implementation details. -In this case, you have an ``Invoice`` entity with a relationship to a -non-existent object, an ``InvoiceSubjectInterface``. The goal is to get -the ``ResolveTargetEntityListener`` to replace any mention of the interface +In this case, you have an ``Invoice`` entity with a relationship to the interface +``InvoiceSubjectInterface``. This is not recognized as a valid entity by Doctrine. +The goal is to get the ``ResolveTargetEntityListener`` to replace any mention of the interface with a real object that implements that interface. Set up @@ -89,7 +94,7 @@ An InvoiceSubjectInterface:: public function getName(): string; } -Next, you need to configure the listener, which tells the DoctrineBundle +Next, you need to configure the ``resolve_target_entities`` option, which tells the DoctrineBundle about the replacement: .. configuration-block:: @@ -141,6 +146,6 @@ Final Thoughts -------------- With the ``ResolveTargetEntityListener``, you are able to decouple your -bundles, keeping them usable by themselves, but still being able to +modules, keeping them usable by themselves, but still being able to define relationships between different objects. By using this method, -your bundles will end up being easier to maintain independently. +your modules will end up being easier to maintain independently. From e6ba3a96ec8c28f692dedb1bc22a423868aad574 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Tue, 27 May 2025 12:02:55 +0200 Subject: [PATCH 073/120] Rewords --- doctrine.rst | 28 +++++++------ doctrine/resolve_target_entity.rst | 67 ++++++++++++++---------------- 2 files changed, 48 insertions(+), 47 deletions(-) diff --git a/doctrine.rst b/doctrine.rst index e66488beefd..ecaf675df08 100644 --- a/doctrine.rst +++ b/doctrine.rst @@ -789,22 +789,26 @@ variable. Let's say you want the first or the last comment of a product dependin Fetch via Interfaces ~~~~~~~~~~~~~~~~~~~~ -Suppose your ``Product`` object implements an interface called ``ProductInterface``. -If you want to decouple your controllers from your entity implementations, you can instead reference entities via an interface. -To do this, first you need to configure the :doc:`resolve_target_entities option `. - -Your controller can then reference the Product entity by its interface instead:: +Suppose your ``Product`` class implements an interface called ``ProductInterface``. +If you want to decouple your controllers from the concrete entity implementation, +you can reference the entity by its interface instead. - public function show( - #[MapEntity] - ProductInterface $product - ): Response { - // ... - } +To enable this, first configure the +:doc:`resolve_target_entities option `. +Then, your controller can type-hint the interface, and the entity will be +resolved automatically:: + + public function show( + #[MapEntity] + ProductInterface $product + ): Response { + // ... + } .. versionadded:: 7.3 - Support for ``resolve_target_entites`` in the ``EntityValueResolver`` was introduced in Symfony 7.3. + Support for target entity resolution in the ``EntityValueResolver`` was + introduced Symfony 7.3 MapEntity Options ~~~~~~~~~~~~~~~~~ diff --git a/doctrine/resolve_target_entity.rst b/doctrine/resolve_target_entity.rst index c55b27f6463..1495f475628 100644 --- a/doctrine/resolve_target_entity.rst +++ b/doctrine/resolve_target_entity.rst @@ -1,44 +1,45 @@ Referencing Entities with Abstract Classes and Interfaces ========================================================= -In applications where functionality is segregated with minimal concrete dependencies -between the various layers, such as monoliths which are split into multiple modules, -it might be hard to prevent hard dependencies on entities between modules. +In applications where functionality is organized in layers or modules with +minimal concrete dependencies, such as monoliths split into multiple modules, +it can be challenging to avoid tight coupling between entities. -Doctrine 2.2 includes a new utility called the ``ResolveTargetEntityListener``, -that functions by intercepting certain calls inside Doctrine and rewriting -``targetEntity`` parameters in your metadata mapping at runtime. It means that -you are able to use an interface or abstract class in your mappings and expect -correct mapping to a concrete entity at runtime. +Doctrine provides a utility called the ``ResolveTargetEntityListener`` to solve +this issue. It works by intercepting certain calls within Doctrine and rewriting +``targetEntity`` parameters in your metadata mapping at runtime. This allows you +to reference an interface or abstract class in your mappings and have it resolved +to a concrete entity at runtime. -This functionality allows you to define relationships between different entities -without making them hard dependencies. +This makes it possible to define relationships between entities without +creating hard dependencies. This feature also works with the ``EntityValueResolver`` +:ref:`as explained in the main Doctrine article `. -.. tip:: +.. versionadded:: 7.3 - Starting with Symfony 7.3, this functionality also works with the ``EntityValueResolver``. - See :ref:`doctrine-entity-value-resolver-resolve-target-entities` for more details. + Support for target entity resolution in the ``EntityValueResolver`` was + introduced Symfony 7.3 Background ---------- -Suppose you have an application which provides two modules; an Invoice module which -provides invoicing functionality, and a Customer module that contains customer management -tools. You want to keep dependencies between these modules separated, because they should -not be aware of the other module's implementation details. +Suppose you have an application with two modules: an Invoice module that +provides invoicing functionality, and a Customer module that handles customer +management. You want to keep these modules decoupled, so that neither is aware +of the other's implementation details. -In this case, you have an ``Invoice`` entity with a relationship to the interface -``InvoiceSubjectInterface``. This is not recognized as a valid entity by Doctrine. -The goal is to get the ``ResolveTargetEntityListener`` to replace any mention of the interface -with a real object that implements that interface. +In this case, your ``Invoice`` entity has a relationship to the interface +``InvoiceSubjectInterface``. Since interfaces are not valid Doctrine entities, +the goal is to use the ``ResolveTargetEntityListener`` to replace all +references to this interface with a concrete class that implements it. Set up ------ -This article uses the following two basic entities (which are incomplete for -brevity) to explain how to set up and use the ``ResolveTargetEntityListener``. +This article uses two basic (incomplete) entities to demonstrate how to set up +and use the ``ResolveTargetEntityListener``. -A Customer entity:: +A ``Customer`` entity:: // src/Entity/Customer.php namespace App\Entity; @@ -55,7 +56,7 @@ A Customer entity:: // are already implemented in the BaseCustomer } -An Invoice entity:: +An ``Invoice`` entity:: // src/Entity/Invoice.php namespace App\Entity; @@ -63,9 +64,6 @@ An Invoice entity:: use App\Model\InvoiceSubjectInterface; use Doctrine\ORM\Mapping as ORM; - /** - * Represents an Invoice. - */ #[ORM\Entity] #[ORM\Table(name: 'invoice')] class Invoice @@ -74,7 +72,7 @@ An Invoice entity:: protected InvoiceSubjectInterface $subject; } -An InvoiceSubjectInterface:: +The interface representing the subject used in the invoice:: // src/Model/InvoiceSubjectInterface.php namespace App\Model; @@ -94,8 +92,8 @@ An InvoiceSubjectInterface:: public function getName(): string; } -Next, you need to configure the ``resolve_target_entities`` option, which tells the DoctrineBundle -about the replacement: +Now configure the ``resolve_target_entities`` option to tell Doctrine +how to replace the interface with the concrete class: .. configuration-block:: @@ -145,7 +143,6 @@ about the replacement: Final Thoughts -------------- -With the ``ResolveTargetEntityListener``, you are able to decouple your -modules, keeping them usable by themselves, but still being able to -define relationships between different objects. By using this method, -your modules will end up being easier to maintain independently. +Using ``ResolveTargetEntityListener`` allows you to decouple your modules +while still defining relationships between their entities. This makes your +codebase more modular and easier to maintain over time. From a94507c576e7bebbb126116b3fb9bfe6e6dbd55e Mon Sep 17 00:00:00 2001 From: Dustin Meiner <168725484+curenect-meiner@users.noreply.github.com> Date: Tue, 27 May 2025 11:36:02 +0200 Subject: [PATCH 074/120] Update uid.rst --- components/uid.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/uid.rst b/components/uid.rst index fe15f964f44..4b6938e98d2 100644 --- a/components/uid.rst +++ b/components/uid.rst @@ -331,6 +331,7 @@ entity primary keys:: namespace App\Entity; use Doctrine\ORM\Mapping as ORM; + use Symfony\Bridge\Doctrine\IdGenerator\UuidGenerator; use Symfony\Bridge\Doctrine\Types\UuidType; use Symfony\Component\Uid\Uuid; @@ -339,7 +340,7 @@ entity primary keys:: #[ORM\Id] #[ORM\Column(type: UuidType::NAME, unique: true)] #[ORM\GeneratedValue(strategy: 'CUSTOM')] - #[ORM\CustomIdGenerator(class: 'doctrine.uuid_generator')] + #[ORM\CustomIdGenerator(class: UuidGenerator::class)] private ?Uuid $id; public function getId(): ?Uuid From 15728d3dbe8e79d47d0cef98b5c784673ed72619 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Tue, 27 May 2025 13:05:49 +0200 Subject: [PATCH 075/120] Minor tweaks --- security/access_token.rst | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/security/access_token.rst b/security/access_token.rst index 0370949e584..4d358aec526 100644 --- a/security/access_token.rst +++ b/security/access_token.rst @@ -411,15 +411,15 @@ and retrieve the user info: ; }; -To enable the `OpenID Connect Discovery`_, the ``OidcUserInfoTokenHandler`` +To enable `OpenID Connect Discovery`_, the ``OidcUserInfoTokenHandler`` requires the ``symfony/cache`` package to store the OIDC configuration in -cache. If you haven't installed it yet, run this command: +the cache. If you haven't installed it yet, run the following command: .. code-block:: terminal $ composer require symfony/cache -Then, configure the ``base_uri`` and ``discovery`` keys: +Next, configure the ``base_uri`` and ``discovery`` options: .. configuration-block:: @@ -477,6 +477,10 @@ Then, configure the ``base_uri`` and ``discovery`` keys: ; }; +.. versionadded:: 7.3 + + Support for OpenID Connect Discovery was introduced in Symfony 7.3. + Following the `OpenID Connect Specification`_, the ``sub`` claim is used as user identifier by default. To use another claim, specify it on the configuration: @@ -691,16 +695,16 @@ it and retrieve the user info from it: The support of multiple algorithms to sign the JWS was introduced in Symfony 7.1. In previous versions, only the ``ES256`` algorithm was supported. -To enable the `OpenID Connect Discovery`_, the ``OidcTokenHandler`` -requires the ``symfony/cache`` package to store the OIDC configuration in -cache. If you haven't installed it yet, run this command: +To enable `OpenID Connect Discovery`_, the ``OidcTokenHandler`` requires the +``symfony/cache`` package to store the OIDC configuration in the cache. If you +haven't installed it yet, run the following command: .. code-block:: terminal $ composer require symfony/cache -Then, you can remove the ``keyset`` configuration key (it will be imported from -the OpenID Connect Discovery), and configure the ``discovery`` key: +Then, you can remove the ``keyset`` configuration option (it will be imported +from the OpenID Connect Discovery), and configure the ``discovery`` option: .. configuration-block:: From 8b403f7948d9ce2bcec4f63b2783d8a055436a90 Mon Sep 17 00:00:00 2001 From: Florent Morselli Date: Sun, 9 Feb 2025 10:44:02 +0100 Subject: [PATCH 076/120] Add support for encrypted access tokens (JWE) in OIDC This update introduces support for decrypting encrypted access tokens (JWE) in Symfony 7.3. It includes configuration options for enabling encryption, enforcing it, specifying decryption algorithms, and providing decryption keysets. The feature extends flexibility in handling secure tokens alongside existing signing mechanisms. --- security/access_token.rst | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/security/access_token.rst b/security/access_token.rst index 4d358aec526..70c9e21980e 100644 --- a/security/access_token.rst +++ b/security/access_token.rst @@ -615,8 +615,8 @@ If you haven't installed it yet, run this command: $ composer require web-token/jwt-library -Symfony provides a generic ``OidcTokenHandler`` to decode your token, validate -it and retrieve the user info from it: +Symfony provides a generic ``OidcTokenHandler`` that decodes the token, validates +it, and retrieves the user information from it. Optionally, the token can be encrypted (JWE): .. configuration-block:: @@ -637,6 +637,11 @@ it and retrieve the user info from it: audience: 'api-example' # Issuers (`iss` claim): required for validation purpose issuers: ['https://oidc.example.com'] + encryption: + enabled: true # Default to false + enforce: false # Default to false, requires an encrypted token when true + algorithms: ['ECDH-ES', 'A128GCM'] + keyset: '{"keys": [...]}' # Encryption private keyset .. code-block:: xml @@ -662,6 +667,10 @@ it and retrieve the user info from it: ES256 RS256 https://oidc.example.com + + ECDH-ES + A128GCM + @@ -681,12 +690,20 @@ it and retrieve the user info from it: ->oidc() // Algorithm used to sign the JWS ->algorithms(['ES256', 'RS256']) - // A JSON-encoded JWK + // A JSON-encoded JWKSet (public keys) ->keyset('{"keys":[{"kty":"...","k":"..."}]}') // Audience (`aud` claim): required for validation purpose ->audience('api-example') // Issuers (`iss` claim): required for validation purpose ->issuers(['https://oidc.example.com']) + ->encryption() + ->enabled(true) //Default to false + ->enforce(false) //Default to false, requires an encrypted token when true + // Algorithm used to decrypt the JWE + ->algorithms(['ECDH-ES', 'A128GCM']) + // A JSON-encoded JWKSet (private keys) + ->keyset('{"keys":[...]}') + ; }; @@ -695,6 +712,10 @@ it and retrieve the user info from it: The support of multiple algorithms to sign the JWS was introduced in Symfony 7.1. In previous versions, only the ``ES256`` algorithm was supported. +.. versionadded:: 7.3 + + Support for encryption algorithms to decrypt JWEs was introduced in Symfony 7.3. + To enable `OpenID Connect Discovery`_, the ``OidcTokenHandler`` requires the ``symfony/cache`` package to store the OIDC configuration in the cache. If you haven't installed it yet, run the following command: From 22cd58eb815c7c672ab3d4f7164ed8a51dcca7bd Mon Sep 17 00:00:00 2001 From: Mathieu Santostefano Date: Sun, 9 Feb 2025 17:56:57 +0100 Subject: [PATCH 077/120] [Routing] Add Attribute code examples for alias in `#[Route]` attribute --- routing.rst | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/routing.rst b/routing.rst index e634a410c37..2f18b121472 100644 --- a/routing.rst +++ b/routing.rst @@ -1340,6 +1340,23 @@ have been renamed. Let's say you have a route called ``product_show``: .. configuration-block:: + .. code-block:: php-attributes + + // src/Controller/ProductController.php + namespace App\Controller; + + use Symfony\Component\HttpFoundation\Response; + use Symfony\Component\Routing\Attribute\Route; + + class ProductController + { + #[Route('/product/{id}', name: 'product_show')] + public function show(): Response + { + // ... + } + } + .. code-block:: yaml # config/routes.yaml @@ -1376,6 +1393,25 @@ Instead of duplicating the original route, you can create an alias for it. .. configuration-block:: + .. code-block:: php-attributes + + // src/Controller/ProductController.php + namespace App\Controller; + + use Symfony\Component\HttpFoundation\Response; + use Symfony\Component\Routing\Attribute\Route; + + class ProductController + { + // "alias" named argument indicates the name of the alias you want to create. + // The alias will point to the actual route "product_show" + #[Route('/product/{id}', name: 'product_show', alias: ['product_details'])] + public function show(): Response + { + // ... + } + } + .. code-block:: yaml # config/routes.yaml @@ -1416,6 +1452,15 @@ Instead of duplicating the original route, you can create an alias for it. In this example, both ``product_show`` and ``product_details`` routes can be used in the application and will produce the same result. +.. note:: + + Using non-attributes formats (YAML, XML and PHP) is the only way + to define an alias pointing to a route that you don't own. + + So that you can use your own route name for URL generation, + while actually using a route defined by a third-party bundle as the target of that URL generation, + as the 2 definitions are not required to be in the same config file (or even in the same format). + .. _routing-alias-deprecation: Deprecating Route Aliases @@ -1436,6 +1481,42 @@ This way, the ``product_show`` alias could be deprecated. .. configuration-block:: + .. code-block:: php-attributes + + // src/Controller/ProductController.php + namespace App\Controller; + + use Symfony\Component\HttpFoundation\Response; + use Symfony\Component\Routing\Attribute\Route; + + class ProductController + { + // this outputs the following generic deprecation message: + // Since acme/package 1.2: The "product_show" route alias is deprecated. You should stop using it, as it will be removed in the future. + #[Route('/product/{id}', + name: 'product_details', + alias: new DeprecatedAlias( + aliasName: 'product_show', + package: 'acme/package', + version: '1.2', + ), + )] + // Or, you can also define a custom deprecation message (%alias_id% placeholder is available) + #[Route('/product/{id}', + name: 'product_details', + alias: new DeprecatedAlias( + aliasName: 'product_show', + package: 'acme/package', + version: '1.2', + message: 'The "%alias_id%" route alias is deprecated. Please use "product_details" instead.', + ), + )] + public function show(): Response + { + // ... + } + } + .. code-block:: yaml # Move the concrete route definition under ``product_details`` From 32e93b8b32003bd7bbe123f21137a19d87c5c185 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Tue, 27 May 2025 15:33:25 +0200 Subject: [PATCH 078/120] Tweaks and added the versionadded directive --- routing.rst | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/routing.rst b/routing.rst index ee913f07b4c..bf3946b343d 100644 --- a/routing.rst +++ b/routing.rst @@ -1405,8 +1405,8 @@ Instead of duplicating the original route, you can create an alias for it. class ProductController { - // "alias" named argument indicates the name of the alias you want to create. - // The alias will point to the actual route "product_show" + // the "alias" argument assigns an alternate name to this route; + // the alias will point to the actual route "product_show" #[Route('/product/{id}', name: 'product_show', alias: ['product_details'])] public function show(): Response { @@ -1451,17 +1451,21 @@ Instead of duplicating the original route, you can create an alias for it. $routes->alias('product_details', 'product_show'); }; +.. versionadded:: 7.3 + + Support for route aliases in PHP attributes was introduced in Symfony 7.3. + In this example, both ``product_show`` and ``product_details`` routes can be used in the application and will produce the same result. .. note:: - Using non-attributes formats (YAML, XML and PHP) is the only way - to define an alias pointing to a route that you don't own. + YAML, XML, and PHP configuration formats are the only ways to define an alias + for a route that you do not own. You can't do this when using PHP attributes. - So that you can use your own route name for URL generation, - while actually using a route defined by a third-party bundle as the target of that URL generation, - as the 2 definitions are not required to be in the same config file (or even in the same format). + This allows you for example to use your own route name for URL generation, + while still targeting a route defined by a third-party bundle. The alias and + the original route do not need to be declared in the same file or format. .. _routing-alias-deprecation: From c15467e0abef4c60c6e2265994fc5761127f7b2c Mon Sep 17 00:00:00 2001 From: Matthieu Lempereur Date: Mon, 31 Mar 2025 13:32:59 +0200 Subject: [PATCH 079/120] [Messenger] [Amqp] Add default exchange support --- messenger.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/messenger.rst b/messenger.rst index fc8f9491022..f4c3f683627 100644 --- a/messenger.rst +++ b/messenger.rst @@ -1645,11 +1645,15 @@ The transport has a number of options: Exchange flags ``exchange[name]`` - Name of the exchange + Name of the exchange, an empty string can be used to use the default exchange ``exchange[type]`` (default: ``fanout``) Type of exchange +.. versionadded:: 7.3 + + Empty string support for ``exchange[name]`` was introduced in Symfony 7.3. + You can also configure AMQP-specific settings on your message by adding :class:`Symfony\\Component\\Messenger\\Bridge\\Amqp\\Transport\\AmqpStamp` to your Envelope:: From b17f770615cfe7beb1291746bb186cc07c4a8f8e Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Tue, 27 May 2025 16:10:17 +0200 Subject: [PATCH 080/120] Minor tweak --- messenger.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messenger.rst b/messenger.rst index 08fdcf7f4dc..46ee188d68d 100644 --- a/messenger.rst +++ b/messenger.rst @@ -1645,7 +1645,7 @@ The transport has a number of options: Exchange flags ``exchange[name]`` - Name of the exchange, an empty string can be used to use the default exchange + Name of the exchange. Use an empty string to use the default exchange. ``exchange[type]`` (default: ``fanout``) Type of exchange From 326e7e52585777d299865e5b788b9c00519aabe4 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Tue, 27 May 2025 16:45:15 +0200 Subject: [PATCH 081/120] Tweaks and rewords --- security/custom_authenticator.rst | 73 +++++++++++++++---------------- 1 file changed, 35 insertions(+), 38 deletions(-) diff --git a/security/custom_authenticator.rst b/security/custom_authenticator.rst index 5877194ab4e..ed22ab96d19 100644 --- a/security/custom_authenticator.rst +++ b/security/custom_authenticator.rst @@ -219,10 +219,10 @@ identifier (e.g. the username or email):: User Identifier ~~~~~~~~~~~~~~~ -The user identifier is a unique string that identifies the user. It is used -to load the user using :ref:`the user provider `. -This identifier is often something like the user's email address or username, -but it could be any unique value associated with the user. +The user identifier is a unique string that identifies the user. It is often +something like their email address or username, but it can be any unique value +associated with the user. It allows loading the user through the configured +:ref:`user provider `. .. note:: @@ -262,21 +262,39 @@ but it could be any unique value associated with the user. } } -It is a good practice to normalize the user identifier before using it. -For example, this ensures that variations such as "john.doe", "John.Doe", -or "JOHN.DOE" refer to the same user. -Normalization can include converting the identifier to lowercase -and trimming unnecessary spaces. -You can optionally pass a user identifier normalizer as third argument to the -``UserBadge``. This callable receives the ``$userIdentifier`` -and must return a normalized user identifier as a string. +It's a good practice to normalize the user identifier before using it. This +ensures that variations like "john.doe", "John.Doe", or "JOHN.DOE" are treated +as the same user. + +Normalization typically involves converting the identifier to lowercase and +trimming extra spaces. For example, Google considers the following email +addresses equivalent: ``john.doe@gmail.com``, ``j.hon.d.oe@gmail.com``, and +``johndoe@gmail.com``. This is due to normalization rules that remove dots and +lowercase the address. + +In enterprise environments, users might authenticate using different identifier +formats, such as: + +* ``john.doe@acme.com`` +* ``acme.com\jdoe`` +* ``https://acme.com/+jdoe`` +* ``acct:jdoe@acme.com`` + +Applying normalization (e.g. lowercasing, trimming, or unifying formats) helps +ensure consistent identity resolution and prevents duplication caused by +format differences. + +In Symfony applications, you can optionally pass a user identifier normalizer as +the third argument to the ``UserBadge``. This callable receives the ``$userIdentifier`` +and must return a normalized string. .. versionadded:: 7.3 - The support of the user identifier normalizer was introduced in Symfony 7.3. + Support for user identifier normalizers was introduced in Symfony 7.3. -For instance, the example below uses a normalizer that converts usernames to a normalized, ASCII-only, lowercase format, -suitable for consistent comparison and storage. +For instance, the example below uses a normalizer that converts usernames to +a normalized, ASCII-only, lowercase format suitable for consistent comparison +and storage:: // src/Security/NormalizedUserBadge.php namespace App\Security; @@ -319,33 +337,12 @@ suitable for consistent comparison and storage. } } -.. note:: - - For example, Google treats the following email addresses as equivalent: - ``john.doe@gmail.com``, ``j.hon.d.oe@gmail.com``, and ``johndoe@gmail.com``. - This is because Google applies normalization rules that remove dots - and convert the address to lowercase (though behavior varies across services). - -.. note:: - - In enterprise environments, a user may authenticate using different formats - of their identifier, such as: - - - ``john.doe@acme.com`` - - ``acme.com\jdoe`` - - ``https://acme.com/+jdoe`` - - ``acct:jdoe@acme.com`` - - Applying normalization (e.g., trimming, lowercasing, or format unification) - helps ensure consistent identity recognition across systems and prevents - duplicates caused by format variations. - User Credential ~~~~~~~~~~~~~~~ -The user credential is used to authenticate the user i.e. to verify +The user credential is used to authenticate the user; that is, to verify the validity of the provided information (such as a password, an API token, -or other custom credentials). +or custom credentials). The following credential classes are supported by default: From 7dfeaba1252a9b2d50280f667c87cad7a7e390dc Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Wed, 28 May 2025 10:45:42 +0200 Subject: [PATCH 082/120] Fixed a minor RST syntax issue --- setup/_update_dep_errors.rst.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup/_update_dep_errors.rst.inc b/setup/_update_dep_errors.rst.inc index 49ae97067e4..5dc7e6745bc 100644 --- a/setup/_update_dep_errors.rst.inc +++ b/setup/_update_dep_errors.rst.inc @@ -24,7 +24,7 @@ versions of other libraries. Check your error message to debug. Another issue that may happen is that the project dependencies can be installed on your local computer but not on the remote server. This usually happens when the PHP versions are different on each machine. The solution is to add the -`platform`_ config option to your `composer.json` file to define the highest +`platform`_ config option to your ``composer.json`` file to define the highest PHP version allowed for the dependencies (set it to the server's PHP version). .. _`platform`: https://getcomposer.org/doc/06-config.md#platform From ece1723cd9f4a3bf908159742444cd8c12ae5971 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Wed, 28 May 2025 10:46:47 +0200 Subject: [PATCH 083/120] Fixed a minor RST syntax issue --- components/cache/adapters/redis_adapter.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/cache/adapters/redis_adapter.rst b/components/cache/adapters/redis_adapter.rst index 3362f4cc2db..23dd8d948a1 100644 --- a/components/cache/adapters/redis_adapter.rst +++ b/components/cache/adapters/redis_adapter.rst @@ -222,7 +222,7 @@ Available Options .. versionadded:: 7.1 - The option `sentinel_master` as an alias for `redis_sentinel` was introduced + The option ``sentinel_master`` as an alias for ``redis_sentinel`` was introduced in Symfony 7.1. .. note:: From fa9df566b994fe1ee54f851e087f2bdf0a4b30b0 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Thu, 29 May 2025 07:54:14 +0200 Subject: [PATCH 084/120] [Security] Fixed a code example --- security/custom_authenticator.rst | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/security/custom_authenticator.rst b/security/custom_authenticator.rst index ed22ab96d19..fd7fa0b139e 100644 --- a/security/custom_authenticator.rst +++ b/security/custom_authenticator.rst @@ -300,14 +300,13 @@ and storage:: namespace App\Security; use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; - use Symfony\Component\String\UnicodeString; use function Symfony\Component\String\u; final class NormalizedUserBadge extends UserBadge { public function __construct(string $identifier) { - $callback = static fn (string $identifier) => u($identifier)->normalize(UnicodeString::NFKC)->ascii()->lower()->toString(); + $callback = static fn (string $identifier): string => u($identifier)->normalize(UnicodeString::NFKC)->ascii()->lower()->toString(); parent::__construct($identifier, null, $callback); } @@ -318,7 +317,7 @@ and storage:: final class PasswordAuthenticator extends AbstractLoginFormAuthenticator { - // Simplified for brievety + // simplified for brevity public function authenticate(Request $request): Passport { $username = (string) $request->request->get('username', ''); @@ -331,7 +330,7 @@ and storage:: new NormalizedUserBadge($username), new PasswordCredentials($password), [ - //All other useful badges + // all other useful badges ] ); } From d923acb02ff743309fe3b2be2a69c95aee704928 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Wed, 28 May 2025 08:43:53 +0200 Subject: [PATCH 085/120] [Security] Document the new `expose_security_errors` option --- reference/configuration/security.rst | 31 +++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/reference/configuration/security.rst b/reference/configuration/security.rst index c8609c20c9c..8bae363d9cf 100644 --- a/reference/configuration/security.rst +++ b/reference/configuration/security.rst @@ -23,7 +23,8 @@ key in your application configuration. * `access_denied_url`_ * `erase_credentials`_ -* `hide_user_not_found`_ +* `expose_security_errors`_ +* `hide_user_not_found`_ (deprecated) * `session_fixation_strategy`_ **Advanced Options**: @@ -71,11 +72,39 @@ after authentication:: Since Symfony 7.3, ``eraseCredentials()`` methods are deprecated and are not called if they have the ``#[\Deprecated]`` attribute. +expose_security_errors +---------------------- + +**type**: ``string`` **default**: ``'none'`` + +.. deprecated:: 7.3 + + The ``expose_security_errors`` option was introduced in Symfony 7.3 + +User enumeration is a common security issue where attackers infer valid usernames +based on error messages. For example, a message like "This user does not exist" +shown by your login form reveals whether a username exists. + +This option lets you hide some or all errors related to user accounts +(e.g. blocked or expired accounts) to prevent this issue. Instead, these +errors will trigger a generic ``BadCredentialsException``. The value of this +option can be one of the following: + +* ``'none'``: hides all user-related security exceptions; +* ``'account_status'``: shows account-related exceptions (e.g. blocked or expired + accounts) but only for users who provided the correct password; +* ``'all'``: shows all security-related exceptions. + hide_user_not_found ------------------- **type**: ``boolean`` **default**: ``true`` +.. deprecated:: 7.3 + + The ``hide_user_not_found`` option was deprecated in favor of the + ``expose_security_errors`` option in Symfony 7.3. + If ``true``, when a user is not found a generic exception of type :class:`Symfony\\Component\\Security\\Core\\Exception\\BadCredentialsException` is thrown with the message "Bad credentials". From c1b44e71348da9b97d9a5aac119dfa9dc1507ac9 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Wed, 28 May 2025 10:16:02 +0200 Subject: [PATCH 086/120] [Cache][Lock] Add support for `Relay\Cluster` in docs --- components/cache/adapters/redis_adapter.rst | 32 +++++++++++++++++++-- components/lock.rst | 10 +++++-- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/components/cache/adapters/redis_adapter.rst b/components/cache/adapters/redis_adapter.rst index 9f29eae84e7..c0295b487a0 100644 --- a/components/cache/adapters/redis_adapter.rst +++ b/components/cache/adapters/redis_adapter.rst @@ -20,9 +20,9 @@ to utilize a cluster of servers to provide redundancy and/or fail-over is also a **Requirements:** At least one `Redis server`_ must be installed and running to use this adapter. Additionally, this adapter requires a compatible extension or library that implements - ``\Redis``, ``\RedisArray``, ``RedisCluster``, ``\Relay\Relay`` or ``\Predis``. + ``\Redis``, ``\RedisArray``, ``RedisCluster``, ``\Relay\Relay``, ``\Relay\Cluster`` or ``\Predis``. -This adapter expects a `Redis`_, `RedisArray`_, `RedisCluster`_, `Relay`_ or `Predis`_ instance to be +This adapter expects a `Redis`_, `RedisArray`_, `RedisCluster`_, `Relay`_, `RelayCluster`_ or `Predis`_ instance to be passed as the first parameter. A namespace and default cache lifetime can optionally be passed as the second and third parameters:: @@ -48,6 +48,10 @@ as the second and third parameters:: ?MarshallerInterface $marshaller = null ); +.. versionadded:: 7.3 + + Support for ``Relay\Cluster`` was introduced in Symfony 7.3. + Configure the Connection ------------------------ @@ -226,11 +230,34 @@ Available Options ``ssl`` (type: ``array``, default: ``null``) SSL context options. See `php.net/context.ssl`_ for more information. +``relay_cluster_context`` (type: ``array``, default: ``[]``) + Defines configuration options specific to ``\Relay\Cluster``. For example, to + user a self-signed certificate for testing in local environment:: + + $options = [ + // ... + 'relay_cluster_context' => [ + // ... + 'stream' => [ + 'verify_peer' => false, + 'verify_peer_name' => false, + 'allow_self_signed' => true, + 'local_cert' => '/valkey.crt', + 'local_pk' => '/valkey.key', + 'cafile' => '/valkey.crt', + ], + ], + ]; + .. versionadded:: 7.1 The option ``sentinel_master`` as an alias for ``redis_sentinel`` was introduced in Symfony 7.1. +.. versionadded:: 7.3 + + The ``relay_cluster_context`` option was introduced in Symfony 7.3. + .. note:: When using the `Predis`_ library some additional Predis-specific options are available. @@ -359,6 +386,7 @@ Supports key rotation, ensuring secure decryption with both old and new keys:: .. _`RedisArray`: https://github.com/phpredis/phpredis/blob/develop/arrays.md .. _`RedisCluster`: https://github.com/phpredis/phpredis/blob/develop/cluster.md .. _`Relay`: https://relay.so/ +.. _`RelayCluster`: https://relay.so/docs/1.x/connections#cluster .. _`Predis`: https://packagist.org/packages/predis/predis .. _`Predis Connection Parameters`: https://github.com/nrk/predis/wiki/Connection-Parameters#list-of-connection-parameters .. _`TCP-keepalive`: https://redis.io/topics/clients#tcp-keepalive diff --git a/components/lock.rst b/components/lock.rst index b8ba38c8fc7..2403763bd4a 100644 --- a/components/lock.rst +++ b/components/lock.rst @@ -612,9 +612,9 @@ RedisStore ~~~~~~~~~~ The RedisStore saves locks on a Redis server, it requires a Redis connection -implementing the ``\Redis``, ``\RedisArray``, ``\RedisCluster``, ``\Relay\Relay`` or -``\Predis`` classes. This store does not support blocking, and expects a TTL to -avoid stalled locks:: +implementing the ``\Redis``, ``\RedisArray``, ``\RedisCluster``, ``\Relay\Relay``, +``\Relay\Cluster`` or ``\Predis`` classes. This store does not support blocking, +and expects a TTL to avoid stalled locks:: use Symfony\Component\Lock\Store\RedisStore; @@ -623,6 +623,10 @@ avoid stalled locks:: $store = new RedisStore($redis); +.. versionadded:: 7.3 + + Support for ``Relay\Cluster`` was introduced in Symfony 7.3. + .. _lock-store-semaphore: SemaphoreStore From e37d364158f6f8e3793b39b866cc59dde4aa48fb Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Thu, 29 May 2025 08:11:20 +0200 Subject: [PATCH 087/120] Minor tweaks --- scheduler.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scheduler.rst b/scheduler.rst index 4c5955a9470..f88d9e4f328 100644 --- a/scheduler.rst +++ b/scheduler.rst @@ -895,12 +895,12 @@ Modifying the Schedule at Runtime .. versionadded:: 6.4 - Modifying the schedule at runtime and recalculating the heap was introduced in Symfony 6.4. + Support for modifying the schedule at runtime and recalculating the heap + was introduced in Symfony 6.4. When a recurring message is added to or removed from the schedule, the scheduler automatically restarts and recalculates the internal trigger heap. -This allows dynamic control over scheduled tasks during runtime. -code:: +This enables dynamic control of scheduled tasks at runtime:: // src/Scheduler/DynamicScheduleProvider.php namespace App\Scheduler; @@ -921,7 +921,7 @@ code:: public function clearAndAddMessages(): void { - // Clear the current schedule (if any) and add new recurring messages + // clear the current schedule and add new recurring messages $this->schedule?->clear(); $this->schedule?->add( RecurringMessage::cron('@hourly', new DoActionMessage()), From 9ef310b4f6201f1e43250ab7705a845c6353b5a6 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Thu, 29 May 2025 08:35:11 +0200 Subject: [PATCH 088/120] Removed an unneeded versionadded directive --- scheduler.rst | 5 ----- 1 file changed, 5 deletions(-) diff --git a/scheduler.rst b/scheduler.rst index cec0fe25ede..de2e448f5af 100644 --- a/scheduler.rst +++ b/scheduler.rst @@ -864,11 +864,6 @@ code:: Modifying the Schedule at Runtime --------------------------------- -.. versionadded:: 6.4 - - Support for modifying the schedule at runtime and recalculating the heap - was introduced in Symfony 6.4. - When a recurring message is added to or removed from the schedule, the scheduler automatically restarts and recalculates the internal trigger heap. This enables dynamic control of scheduled tasks at runtime:: From 47988f995bf4d180cbcb9cbe73d11692f49c4baa Mon Sep 17 00:00:00 2001 From: Antoine Lamirault Date: Thu, 29 May 2025 13:19:16 +0200 Subject: [PATCH 089/120] [Serializer] Fix YAML normalizationContext and denormalizationContext names --- serializer.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/serializer.rst b/serializer.rst index 440494d0be1..cc514684bab 100644 --- a/serializer.rst +++ b/serializer.rst @@ -520,8 +520,8 @@ You can also specify a context specific to normalization or denormalization: attributes: createdAt: contexts: - - normalizationContext: { datetime_format: 'Y-m-d' } - denormalizationContext: { datetime_format: !php/const \DateTime::RFC3339 } + - normalization_context: { datetime_format: 'Y-m-d' } + denormalization_context: { datetime_format: !php/const \DateTime::RFC3339 } .. code-block:: xml @@ -1371,7 +1371,7 @@ normalizers (in order of priority): $propertyInfo = new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]); $normalizers = [new ObjectNormalizer(new ClassMetadataFactory(new AttributeLoader()), null, null, $propertyInfo), new ArrayDenormalizer()]; - + $this->serializer = new Serializer($normalizers, [new JsonEncoder()]); :class:`Symfony\\Component\\Serializer\\Normalizer\\ObjectNormalizer` From b356a5b6d86cf9a04cc3f438be00150c3ec437c5 Mon Sep 17 00:00:00 2001 From: Antoine Lamirault Date: Thu, 29 May 2025 13:36:20 +0200 Subject: [PATCH 090/120] [EventDispatcher] Document event name is optionnal --- components/event_dispatcher.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/components/event_dispatcher.rst b/components/event_dispatcher.rst index 8cd676dd5fe..62a3707bb39 100644 --- a/components/event_dispatcher.rst +++ b/components/event_dispatcher.rst @@ -277,8 +277,9 @@ Dispatch the Event The :method:`Symfony\\Component\\EventDispatcher\\EventDispatcher::dispatch` method notifies all listeners of the given event. It takes two arguments: -the ``Event`` instance to pass to each listener of that event and the name -of the event to dispatch:: +the ``Event`` instance to pass to each listener of that event and optionally the +name of the event to dispatch. If it's not defined, the class of the ``Event`` +instance will be used:: use Acme\Store\Event\OrderPlacedEvent; use Acme\Store\Order; From 65599c4cec1f2bc3661fad1fe9e3594146bd65f7 Mon Sep 17 00:00:00 2001 From: Antoine Lamirault Date: Thu, 29 May 2025 14:07:18 +0200 Subject: [PATCH 091/120] Replace get_class() calls by ::class --- components/options_resolver.rst | 2 +- components/property_info.rst | 2 +- components/yaml.rst | 2 +- console/verbosity.rst | 2 +- doctrine/associations.rst | 2 +- security/user_providers.rst | 2 +- service_container/service_subscribers_locators.rst | 6 +++--- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/components/options_resolver.rst b/components/options_resolver.rst index 6b033a1b69c..35e443d3d94 100644 --- a/components/options_resolver.rst +++ b/components/options_resolver.rst @@ -922,7 +922,7 @@ can change your code to do the configuration only once per class:: public function __construct(array $options = []) { // What type of Mailer is this, a Mailer, a GoogleMailer, ... ? - $class = get_class($this); + $class = $this::class; // Was configureOptions() executed before for this class? if (!isset(self::$resolversByClass[$class])) { diff --git a/components/property_info.rst b/components/property_info.rst index 6d57c1bb274..238da6396ab 100644 --- a/components/property_info.rst +++ b/components/property_info.rst @@ -131,7 +131,7 @@ class exposes public methods to extract several types of information: $propertyInfo->getProperties($awesomeObject); // Good! - $propertyInfo->getProperties(get_class($awesomeObject)); + $propertyInfo->getProperties($awesomeObject::class); $propertyInfo->getProperties('Example\Namespace\YourAwesomeClass'); $propertyInfo->getProperties(YourAwesomeClass::class); diff --git a/components/yaml.rst b/components/yaml.rst index 268ad5c5452..700ebd22379 100644 --- a/components/yaml.rst +++ b/components/yaml.rst @@ -296,7 +296,7 @@ You can make it convert to a ``DateTime`` instance by using the ``PARSE_DATETIME flag:: $date = Yaml::parse('2016-05-27', Yaml::PARSE_DATETIME); - var_dump(get_class($date)); // DateTime + var_dump($date::class); // DateTime Dumping Multi-line Literal Blocks ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/console/verbosity.rst b/console/verbosity.rst index f7a1a1e5e59..b822e3d7534 100644 --- a/console/verbosity.rst +++ b/console/verbosity.rst @@ -60,7 +60,7 @@ level. For example:: // available methods: ->isQuiet(), ->isVerbose(), ->isVeryVerbose(), ->isDebug() if ($output->isVerbose()) { - $output->writeln('User class: '.get_class($user)); + $output->writeln('User class: '.$user::class); } // alternatively you can pass the verbosity level PHP constant to writeln() diff --git a/doctrine/associations.rst b/doctrine/associations.rst index 2279d02a851..bb670eeee52 100644 --- a/doctrine/associations.rst +++ b/doctrine/associations.rst @@ -447,7 +447,7 @@ by adding JOINs. $category = $product->getCategory(); // prints "Proxies\AppEntityCategoryProxy" - dump(get_class($category)); + dump($category::class); die(); This proxy object extends the true ``Category`` object, and looks and diff --git a/security/user_providers.rst b/security/user_providers.rst index 7e9de36eff1..73b723faaaf 100644 --- a/security/user_providers.rst +++ b/security/user_providers.rst @@ -425,7 +425,7 @@ command will generate a nice skeleton to get you started:: public function refreshUser(UserInterface $user): UserInterface { if (!$user instanceof User) { - throw new UnsupportedUserException(sprintf('Invalid user class "%s".', get_class($user))); + throw new UnsupportedUserException(sprintf('Invalid user class "%s".', $user::class)); } // Return a User object after making sure its data is "fresh". diff --git a/service_container/service_subscribers_locators.rst b/service_container/service_subscribers_locators.rst index 14cdb010152..d67384d50dd 100644 --- a/service_container/service_subscribers_locators.rst +++ b/service_container/service_subscribers_locators.rst @@ -36,7 +36,7 @@ to handle their respective command when it is asked for:: public function handle(Command $command): mixed { - $commandClass = get_class($command); + $commandClass = $command::class; if (!$handler = $this->handlerMap[$commandClass] ?? null) { return; @@ -94,7 +94,7 @@ in the service subscriber:: public function handle(Command $command): mixed { - $commandClass = get_class($command); + $commandClass = $command::class; if ($this->locator->has($commandClass)) { $handler = $this->locator->get($commandClass); @@ -328,7 +328,7 @@ attribute:: public function handle(Command $command): mixed { - $commandClass = get_class($command); + $commandClass = $command::class; if ($this->handlers->has($commandClass)) { $handler = $this->handlers->get($commandClass); From cb8e2ff4f85289bf6be15ab40ad4736397f7fa0d Mon Sep 17 00:00:00 2001 From: Thomas Landauer Date: Fri, 21 Mar 2025 11:13:12 +0100 Subject: [PATCH 092/120] =?UTF-8?q?[AssetMapper]=20Adding=20info=20that=20?= =?UTF-8?q?always=20*all*=20entrypoints=20are=20included=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/asset_mapper.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/asset_mapper.rst b/frontend/asset_mapper.rst index 061c4598bfa..bab55035271 100644 --- a/frontend/asset_mapper.rst +++ b/frontend/asset_mapper.rst @@ -1081,8 +1081,10 @@ loads the ``app.js`` file *and* the ``checkout.js`` file. It's important to *not* call ``parent()`` in the ``importmap`` block. Each page can only have *one* importmap, so ``importmap()`` must be called exactly once. -If, for some reason, you want to execute *only* ``checkout.js`` +If you want to execute *only* ``checkout.js`` and *not* ``app.js``, pass only ``checkout`` to ``importmap()``. +In this case, still **both** files are added to ````). Using a Content Security Policy (CSP) ------------------------------------- From 45c11e5a0f5690eb8a30cc87ba447e96939056ab Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Thu, 29 May 2025 16:57:15 +0200 Subject: [PATCH 093/120] Tweaks and rewords --- frontend/asset_mapper.rst | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/frontend/asset_mapper.rst b/frontend/asset_mapper.rst index be013514a8f..ca500a9686d 100644 --- a/frontend/asset_mapper.rst +++ b/frontend/asset_mapper.rst @@ -1095,16 +1095,19 @@ both ``app`` and ``checkout``: {{ importmap(['app', 'checkout']) }} {% endblock %} -By passing both ``app`` and ``checkout``, the ``importmap()`` function will -output the ``importmap`` and also add a ````). +The ``importmap()`` function always includes the full import map to ensure all +module definitions are available on the page. It also adds a ``