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
+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:
ES256RS256https://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 ``