From 6156dcfa45f40c5422427a706529d9a1723c0050 Mon Sep 17 00:00:00 2001 From: wkania Date: Sun, 1 Jun 2025 07:13:58 +0200 Subject: [PATCH 001/122] [Form] Add input=date_point to DateTimeType, DateType and TimeType --- reference/forms/types/datetime.rst | 1 + reference/forms/types/options/date_input.rst.inc | 1 + reference/forms/types/time.rst | 1 + 3 files changed, 3 insertions(+) diff --git a/reference/forms/types/datetime.rst b/reference/forms/types/datetime.rst index 5fda8e9a14f..786abbc3af9 100644 --- a/reference/forms/types/datetime.rst +++ b/reference/forms/types/datetime.rst @@ -110,6 +110,7 @@ on your underlying object. Valid values are: * ``string`` (e.g. ``2011-06-05 12:15:00``) * ``datetime`` (a ``DateTime`` object) * ``datetime_immutable`` (a ``DateTimeImmutable`` object) +* ``date_point`` (a ``DatePoint`` object) * ``array`` (e.g. ``[2011, 06, 05, 12, 15, 0]``) * ``timestamp`` (e.g. ``1307276100``) diff --git a/reference/forms/types/options/date_input.rst.inc b/reference/forms/types/options/date_input.rst.inc index dafd7c483ad..06b17228bc0 100644 --- a/reference/forms/types/options/date_input.rst.inc +++ b/reference/forms/types/options/date_input.rst.inc @@ -9,6 +9,7 @@ on your underlying object. Valid values are: * ``string`` (e.g. ``2011-06-05``) * ``datetime`` (a ``DateTime`` object) * ``datetime_immutable`` (a ``DateTimeImmutable`` object) +* ``date_point`` (a ``DatePoint`` object) * ``array`` (e.g. ``['year' => 2011, 'month' => 06, 'day' => 05]``) * ``timestamp`` (e.g. ``1307232000``) diff --git a/reference/forms/types/time.rst b/reference/forms/types/time.rst index a3378f948cd..11377ba594f 100644 --- a/reference/forms/types/time.rst +++ b/reference/forms/types/time.rst @@ -99,6 +99,7 @@ on your underlying object. Valid values are: * ``string`` (e.g. ``12:17:26``) * ``datetime`` (a ``DateTime`` object) * ``datetime_immutable`` (a ``DateTimeImmutable`` object) +* ``date_point`` (a ``DatePoint`` object) * ``array`` (e.g. ``['hour' => 12, 'minute' => 17, 'second' => 26]``) * ``timestamp`` (e.g. ``1307232000``) From 2276ec1ad77b668d1db559b030cb4f2f9aacbd52 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Mon, 2 Jun 2025 08:25:06 +0200 Subject: [PATCH 002/122] Minor tweaks --- reference/forms/types/datetime.rst | 6 +++++- reference/forms/types/options/date_input.rst.inc | 6 +++++- reference/forms/types/time.rst | 6 +++++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/reference/forms/types/datetime.rst b/reference/forms/types/datetime.rst index 786abbc3af9..035bdee7c7e 100644 --- a/reference/forms/types/datetime.rst +++ b/reference/forms/types/datetime.rst @@ -110,10 +110,14 @@ on your underlying object. Valid values are: * ``string`` (e.g. ``2011-06-05 12:15:00``) * ``datetime`` (a ``DateTime`` object) * ``datetime_immutable`` (a ``DateTimeImmutable`` object) -* ``date_point`` (a ``DatePoint`` object) +* ``date_point`` (a :ref:`DatePoint ` object) * ``array`` (e.g. ``[2011, 06, 05, 12, 15, 0]``) * ``timestamp`` (e.g. ``1307276100``) +.. versionadded:: 7.4 + + Support for ``date_point`` values was introduced in Symfony 7.4. + The value that comes back from the form will also be normalized back into this format. diff --git a/reference/forms/types/options/date_input.rst.inc b/reference/forms/types/options/date_input.rst.inc index 06b17228bc0..b4dc263cac1 100644 --- a/reference/forms/types/options/date_input.rst.inc +++ b/reference/forms/types/options/date_input.rst.inc @@ -9,10 +9,14 @@ on your underlying object. Valid values are: * ``string`` (e.g. ``2011-06-05``) * ``datetime`` (a ``DateTime`` object) * ``datetime_immutable`` (a ``DateTimeImmutable`` object) -* ``date_point`` (a ``DatePoint`` object) +* ``date_point`` (a :ref:`DatePoint ` object) * ``array`` (e.g. ``['year' => 2011, 'month' => 06, 'day' => 05]``) * ``timestamp`` (e.g. ``1307232000``) +.. versionadded:: 7.4 + + Support for ``date_point`` values was introduced in Symfony 7.4. + The value that comes back from the form will also be normalized back into this format. diff --git a/reference/forms/types/time.rst b/reference/forms/types/time.rst index 11377ba594f..968907efd5b 100644 --- a/reference/forms/types/time.rst +++ b/reference/forms/types/time.rst @@ -99,10 +99,14 @@ on your underlying object. Valid values are: * ``string`` (e.g. ``12:17:26``) * ``datetime`` (a ``DateTime`` object) * ``datetime_immutable`` (a ``DateTimeImmutable`` object) -* ``date_point`` (a ``DatePoint`` object) +* ``date_point`` (a :ref:`DatePoint ` object) * ``array`` (e.g. ``['hour' => 12, 'minute' => 17, 'second' => 26]``) * ``timestamp`` (e.g. ``1307232000``) +.. versionadded:: 7.4 + + Support for ``date_point`` values was introduced in Symfony 7.4. + The value that comes back from the form will also be normalized back into this format. From d7fd599cc6d3f1aa055249b94903ca98a4d8cb25 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Tue, 3 Jun 2025 13:02:05 +0200 Subject: [PATCH 003/122] [WebLink] Add class to parse Link headers from HTTP responses --- web_link.rst | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/web_link.rst b/web_link.rst index 79fd51b8d02..3fe2e349f50 100644 --- a/web_link.rst +++ b/web_link.rst @@ -195,6 +195,30 @@ You can also add links to the HTTP response directly from controllers and servic } } +Parsing Link Headers +-------------------- + +Some third-party APIs provide resources such as pagination URLs using the +``Link`` HTTP header. The WebLink component provides the +:class:`Symfony\\Component\\WebLink\\HttpHeaderParser` utility class to parse +those headers and transform them into :class:`Symfony\\Component\\WebLink\\Link` +instances:: + + use Symfony\Component\WebLink\HttpHeaderParser; + + $parser = new HttpHeaderParser(); + // get the value of the Link header from the Request + $linkHeader = '; rel="prerender",; rel="dns-prefetch"; pr="0.7",; rel="preload"; as="script"'; + + $links = $parser->parse($linkHeader)->getLinks(); + $links[0]->getRels(); // ['prerender'] + $links[1]->getAttributes(); // ['pr' => '0.7'] + $links[2]->getHref(); // '/baz.js' + +.. versionadded:: 7.4 + + The ``HttpHeaderParser`` class was introduced in Symfony 7.4. + .. _`WebLink`: https://github.com/symfony/web-link .. _`HTTP/2 Server Push`: https://tools.ietf.org/html/rfc7540#section-8.2 .. _`Resource Hints`: https://www.w3.org/TR/resource-hints/ From 412a94bbaabbb100e8c7501debaf31af7f59ec2e Mon Sep 17 00:00:00 2001 From: Antoine Lamirault Date: Sat, 7 Jun 2025 10:31:08 +0200 Subject: [PATCH 004/122] [PhpUnitBridge] Add strtotime() to ClockMock --- components/phpunit_bridge.rst | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/components/phpunit_bridge.rst b/components/phpunit_bridge.rst index 5ce4c003a11..999e9626b40 100644 --- a/components/phpunit_bridge.rst +++ b/components/phpunit_bridge.rst @@ -566,9 +566,13 @@ Clock Mocking The :class:`Symfony\\Bridge\\PhpUnit\\ClockMock` class provided by this bridge allows you to mock the PHP's built-in time functions ``time()``, ``microtime()``, -``sleep()``, ``usleep()``, ``gmdate()``, and ``hrtime()``. Additionally the -function ``date()`` is mocked so it uses the mocked time if no timestamp is -specified. +``sleep()``, ``usleep()``, ``gmdate()``, ``hrtime()``, and ``strtotime()``. +Additionally the function ``date()`` is mocked so it uses the mocked time if no +timestamp is specified. + +.. versionadded:: 7.4 + + Support for mocking the ``strtotime()`` function was introduced in Symfony 7.4. Other functions with an optional timestamp parameter that defaults to ``time()`` will still use the system time instead of the mocked time. This means that you From 6e4ba05869fa2a8da110ba327e3cb7d3007fefb8 Mon Sep 17 00:00:00 2001 From: Santiago San Martin Date: Sun, 8 Jun 2025 01:16:36 -0300 Subject: [PATCH 005/122] [Mailer] Add new assertEmailAddressNotContains method --- testing.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testing.rst b/testing.rst index 9356f2013a7..4af0cb19ec0 100644 --- a/testing.rst +++ b/testing.rst @@ -1082,8 +1082,8 @@ Mailer Assertions ``assertEmailHeaderSame(RawMessage $email, string $headerName, string $expectedValue, string $message = '')``/``assertEmailHeaderNotSame(RawMessage $email, string $headerName, string $expectedValue, string $message = '')`` Asserts that the given email does (not) have the expected header set to the expected value. -``assertEmailAddressContains(RawMessage $email, string $headerName, string $expectedValue, string $message = '')`` - Asserts that the given address header equals the expected e-mail +``assertEmailAddressContains(RawMessage $email, string $headerName, string $expectedValue, string $message = '')``/``assertEmailAddressNotContains(RawMessage $email, string $headerName, string $expectedValue, string $message = '')`` + Asserts that the given address header does (not) equal the expected e-mail address. This assertion normalizes addresses like ``Jane Smith `` into ``jane@example.com``. ``assertEmailSubjectContains(RawMessage $email, string $expectedValue, string $message = '')``/``assertEmailSubjectNotContains(RawMessage $email, string $expectedValue, string $message = '')`` From c259c28d33bcb6a4977259353b23bd2f6864d4c3 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Wed, 18 Jun 2025 17:25:16 +0200 Subject: [PATCH 006/122] Add the missing versionadded directive --- testing.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/testing.rst b/testing.rst index 4af0cb19ec0..fc2a22324ff 100644 --- a/testing.rst +++ b/testing.rst @@ -1090,6 +1090,10 @@ Mailer Assertions Asserts that the subject of the given email does (not) contain the expected subject. +.. versionadded:: 7.4 + + The ``assertEmailAddressNotContains()`` assertion was introduced in Symfony 7.4. + Notifier Assertions ................... From 8059765da0eaef018e2616e2ba0a6cce8fca2339 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Fri, 20 Jun 2025 16:14:38 +0200 Subject: [PATCH 007/122] ObjectMapper component is not experimental in 7.4 branch --- object_mapper.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/object_mapper.rst b/object_mapper.rst index 625466ffefc..74cbb5756e7 100644 --- a/object_mapper.rst +++ b/object_mapper.rst @@ -3,8 +3,7 @@ Object Mapper .. versionadded:: 7.3 - The ObjectMapper component was introduced in Symfony 7.3 as an - :doc:`experimental feature `. + The ObjectMapper component was introduced in Symfony 7.3. This component transforms one object into another, simplifying tasks such as converting DTOs (Data Transfer Objects) into entities or vice versa. It can also From b6b6d9952f65a9a49603b8412fe4ace479e870a1 Mon Sep 17 00:00:00 2001 From: Yopai <6121503+Yopai@users.noreply.github.com> Date: Sat, 21 Jun 2025 20:19:29 +0200 Subject: [PATCH 008/122] Update setup.rst : fix version number for 7.4 --- setup.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/setup.rst b/setup.rst index a1fe9669a6e..cff4858a40a 100644 --- a/setup.rst +++ b/setup.rst @@ -48,10 +48,10 @@ application: .. code-block:: terminal # run this if you are building a traditional web application - $ symfony new my_project_directory --version="7.3.x-dev" --webapp + $ symfony new my_project_directory --version="7.4.x-dev" --webapp # run this if you are building a microservice, console application or API - $ symfony new my_project_directory --version="7.3.x-dev" + $ symfony new my_project_directory --version="7.4.x-dev" The only difference between these two commands is the number of packages installed by default. The ``--webapp`` option installs extra packages to give @@ -63,12 +63,12 @@ Symfony application using Composer: .. code-block:: terminal # run this if you are building a traditional web application - $ composer create-project symfony/skeleton:"7.3.x-dev" my_project_directory + $ composer create-project symfony/skeleton:"7.4.x-dev" my_project_directory $ cd my_project_directory $ composer require webapp # run this if you are building a microservice, console application or API - $ composer create-project symfony/skeleton:"7.3.x-dev" my_project_directory + $ composer create-project symfony/skeleton:"7.4.x-dev" my_project_directory No matter which command you run to create the Symfony application. All of them will create a new ``my_project_directory/`` directory, download some dependencies From ad966a7b82c6ae4303ce3d4b2a1350d1f71faf0e Mon Sep 17 00:00:00 2001 From: Matthias Schmidt Date: Sun, 22 Jun 2025 12:38:08 +0200 Subject: [PATCH 009/122] [Routing] Document _query parameter for URL generation Fix #21055 --- routing.rst | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/routing.rst b/routing.rst index ffc7d168395..46215fe1881 100644 --- a/routing.rst +++ b/routing.rst @@ -1151,6 +1151,17 @@ special parameters created by Symfony: This is used for such things as setting the ``Content-Type`` of the response (e.g. a ``json`` format translates into a ``Content-Type`` of ``application/json``). +``_query`` + Used to add query parameters to the generated URL. + + .. versionadded:: 7.4 + + The ``_query`` parameter was introduced in Symfony 7.4. + + .. deprecated:: 7.4 + + Passing a value other than an array as the ``_query`` parameter was deprecated in Symfony 7.4. + ``_fragment`` Used to set the fragment identifier, which is the optional last part of a URL that starts with a ``#`` character and is used to identify a portion of a document. From 8a6a9f24bd125d24977b88f5637f7b6ee0186b1e Mon Sep 17 00:00:00 2001 From: Matthias Schmidt Date: Sun, 22 Jun 2025 13:07:27 +0200 Subject: [PATCH 010/122] [Scheduler] Document unique schedule name Fix #21024 --- scheduler.rst | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scheduler.rst b/scheduler.rst index 983ede09357..fc4656f811e 100644 --- a/scheduler.rst +++ b/scheduler.rst @@ -133,9 +133,14 @@ on a particular schedule:: .. tip:: - By default, the schedule name is ``default`` and the transport name follows + The schedule name must be unique and by default, it is ``default``. The transport name follows the syntax: ``scheduler_nameofyourschedule`` (e.g. ``scheduler_default``). +.. versionadded:: 7.4 + + Throwing an exception for duplicate schedule names instead of replacing the existing schedule + was introduced with Symfony 7.4. + .. tip:: `Memoizing`_ your schedule is a good practice to prevent unnecessary reconstruction From 88c022db18e6a95d8215ed8084825ba58f873089 Mon Sep 17 00:00:00 2001 From: Vincent Amstoutz Date: Fri, 20 Jun 2025 15:09:28 +0200 Subject: [PATCH 011/122] docs(FrameworkBundle): allow to unverbose all the methods in BrowserKitAssertionsTrait --- testing.rst | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/testing.rst b/testing.rst index fc2a22324ff..0fe90dad93d 100644 --- a/testing.rst +++ b/testing.rst @@ -963,11 +963,11 @@ However, Symfony provides useful shortcut methods for the most common cases: Response Assertions ................... -``assertResponseIsSuccessful(string $message = '', bool $verbose = true)`` +``assertResponseIsSuccessful(string $message = '', ?bool $verbose = null)`` Asserts that the response was successful (HTTP status is 2xx). -``assertResponseStatusCodeSame(int $expectedCode, string $message = '', bool $verbose = true)`` +``assertResponseStatusCodeSame(int $expectedCode, string $message = '', ?bool $verbose = null)`` Asserts a specific HTTP status code. -``assertResponseRedirects(?string $expectedLocation = null, ?int $expectedCode = null, string $message = '', bool $verbose = true)`` +``assertResponseRedirects(?string $expectedLocation = null, ?int $expectedCode = null, string $message = '', ?bool $verbose = null)`` Asserts the response is a redirect response (optionally, you can check the target location and status code). The excepted location can be either an absolute or a relative path. @@ -985,13 +985,22 @@ Response Assertions Asserts the response format returned by the :method:`Symfony\\Component\\HttpFoundation\\Response::getFormat` method is the same as the expected value. -``assertResponseIsUnprocessable(string $message = '', bool $verbose = true)`` +``assertResponseIsUnprocessable(string $message = '', bool ?$verbose = null)`` Asserts the response is unprocessable (HTTP status is 422) .. versionadded:: 7.1 The ``$verbose`` parameters were introduced in Symfony 7.1. +.. versionadded:: 7.4 + + The ``$defaultVerboseMode = true;`` attribute was introduced in + :class:`Symfony\\Bundle\\FrameworkBundle\\Test\\BrowserKitAssertionsTrait` + in Symfony 7.4. This attribute allows you to define the default verbosity + for all applicable assertions within the trait, overriding the initial `null` + value of the `$verbose` parameter. + + Request Assertions .................. From fdf8824289df7eb57874348ef7c1355bad98c0f4 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Wed, 25 Jun 2025 09:43:20 +0200 Subject: [PATCH 012/122] Reword and tweaks --- testing.rst | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/testing.rst b/testing.rst index 0fe90dad93d..e5e57522d69 100644 --- a/testing.rst +++ b/testing.rst @@ -988,18 +988,21 @@ Response Assertions ``assertResponseIsUnprocessable(string $message = '', bool ?$verbose = null)`` Asserts the response is unprocessable (HTTP status is 422) +By default, these assert methods provide detailed error messages when they fail. +You can control the verbosity level using the optional ``verbose`` argument in +each assert method. To set this verbosity level globally, use the +``setBrowserKitAssertionsAsVerbose()`` method from the +:class:`Symfony\\Bundle\\FrameworkBundle\\Test\\BrowserKitAssertionsTrait`:: + + BrowserKitAssertionsTrait::setBrowserKitAssertionsAsVerbose(false); + .. versionadded:: 7.1 The ``$verbose`` parameters were introduced in Symfony 7.1. .. versionadded:: 7.4 - The ``$defaultVerboseMode = true;`` attribute was introduced in - :class:`Symfony\\Bundle\\FrameworkBundle\\Test\\BrowserKitAssertionsTrait` - in Symfony 7.4. This attribute allows you to define the default verbosity - for all applicable assertions within the trait, overriding the initial `null` - value of the `$verbose` parameter. - + The ``setBrowserKitAssertionsAsVerbose()`` method ws introduced in Symfony 7.4. Request Assertions .................. From c7ce246b8f3e43e3e46c493e37f6e8bc1dcd30af Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Wed, 25 Jun 2025 11:19:17 +0200 Subject: [PATCH 013/122] fix typo --- testing.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing.rst b/testing.rst index e5e57522d69..1afc15befbb 100644 --- a/testing.rst +++ b/testing.rst @@ -1002,7 +1002,7 @@ each assert method. To set this verbosity level globally, use the .. versionadded:: 7.4 - The ``setBrowserKitAssertionsAsVerbose()`` method ws introduced in Symfony 7.4. + The ``setBrowserKitAssertionsAsVerbose()`` method was introduced in Symfony 7.4. Request Assertions .................. From 7f37b3c77c6c888401697be1aa306002582663fb Mon Sep 17 00:00:00 2001 From: Vincent Amstoutz Date: Thu, 26 Jun 2025 10:38:40 +0200 Subject: [PATCH 014/122] docs(SecurityBundle): register alias for argument for password hasher --- security.rst | 4 +-- security/passwords.rst | 55 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/security.rst b/security.rst index 9d2df6165d0..ede0bc39825 100644 --- a/security.rst +++ b/security.rst @@ -461,8 +461,8 @@ You can also manually hash a password by running: $ php bin/console security:hash-password -Read more about all available hashers and password migration in -:doc:`security/passwords`. +Read more about all available hashers (including specific hashers) and password +migration in :doc:`security/passwords`. .. _firewalls-authentication: .. _a-authentication-firewalls: diff --git a/security/passwords.rst b/security/passwords.rst index 7f05bc3acb9..bba983a94e3 100644 --- a/security/passwords.rst +++ b/security/passwords.rst @@ -226,6 +226,61 @@ After configuring the correct algorithm, you can use the throw new \Exception('Bad credentials, cannot delete this user.'); } +Injecting a Specific Password Hasher +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In some cases, you might define a password hasher in your configuration that is +not linked to a user entity but is instead identified by a unique key. +For example, you might have a separate hasher for things like password recovery +codes. + +With the following configuration: + +.. code-block:: yaml + + # config/packages/security.yaml + security: + password_hashers: + recovery_code: 'auto' + + firewalls: + main: + # ... + +It is possible to inject the recovery_code password hasher into any service. +To do this, you can't rely on standard autowiring, as Symfony wouldn't know +which specific hasher to provide. + +Instead, you can use the ``#[Target]`` attribute to request the hasher by its +configuration key:: + + // src/Controller/HomepageController.php + namespace App\Controller; + + use Symfony\Component\DependencyInjection\Attribute\Target; + use Symfony\Component\PasswordHasher\PasswordHasherInterface; + + class HomepageController extends AbstractController + { + public function __construct( + #[Target('recovery_code')] + private readonly PasswordHasherInterface $passwordHasher, + ) { + } + + #[Route('/')] + public function index(): Response + { + $plaintextToken = 'some-secret-token'; + + // Note: use hash(), not hashPassword(), as we are not using a UserInterface object + $hashedToken = $this->passwordHasher->hash($plaintextToken); + } + } + +When injecting a specific hasher by its name, you should type-hint the generic +:class:`Symfony\\Component\\PasswordHasher\\PasswordHasherInterface`. + Reset Password -------------- From ee6841b81adf0d9cf25b15b16a08cddad89bf685 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Fri, 27 Jun 2025 08:21:51 +0200 Subject: [PATCH 015/122] Minor tweaks --- security/passwords.rst | 77 ++++++++++++++++++++++-------------------- 1 file changed, 40 insertions(+), 37 deletions(-) diff --git a/security/passwords.rst b/security/passwords.rst index bba983a94e3..5de5d4b7b24 100644 --- a/security/passwords.rst +++ b/security/passwords.rst @@ -226,13 +226,42 @@ After configuring the correct algorithm, you can use the throw new \Exception('Bad credentials, cannot delete this user.'); } +Reset Password +-------------- + +Using `MakerBundle`_ and `SymfonyCastsResetPasswordBundle`_, you can create +a secure out of the box solution to handle forgotten passwords. First, +install the SymfonyCastsResetPasswordBundle: + +.. code-block:: terminal + + $ composer require symfonycasts/reset-password-bundle + +Then, use the ``make:reset-password`` command. This asks you a few +questions about your app and generates all the files you need! After, +you'll see a success message and a list of any other steps you need to do. + +.. code-block:: terminal + + $ php bin/console make:reset-password + +.. tip:: + + Starting in `MakerBundle`_: v1.57.0 - You can pass either ``--with-uuid`` or + ``--with-ulid`` to ``make:reset-password``. Leveraging Symfony's :doc:`Uid Component `, + the entities will be generated with the ``id`` type as :ref:`Uuid ` + or :ref:`Ulid ` instead of ``int``. + +You can customize the reset password bundle's behavior by updating the +``reset_password.yaml`` file. For more information on the configuration, +check out the `SymfonyCastsResetPasswordBundle`_ guide. + Injecting a Specific Password Hasher ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -In some cases, you might define a password hasher in your configuration that is -not linked to a user entity but is instead identified by a unique key. -For example, you might have a separate hasher for things like password recovery -codes. +In some cases, you may define a password hasher in your configuration that is +not tied to a user class. For example, you might use a separate hasher for +password recovery codes or API tokens. With the following configuration: @@ -247,12 +276,12 @@ With the following configuration: main: # ... -It is possible to inject the recovery_code password hasher into any service. -To do this, you can't rely on standard autowiring, as Symfony wouldn't know -which specific hasher to provide. +You can inject the ``recovery_code`` password hasher into any service. However, +you can't rely on standard autowiring, as Symfony doesn't know which specific +hasher to provide. -Instead, you can use the ``#[Target]`` attribute to request the hasher by its -configuration key:: +Instead, use the ``#[Target]`` attribute to explicitly request the hasher by +its configuration key:: // src/Controller/HomepageController.php namespace App\Controller; @@ -281,35 +310,9 @@ configuration key:: When injecting a specific hasher by its name, you should type-hint the generic :class:`Symfony\\Component\\PasswordHasher\\PasswordHasherInterface`. -Reset Password --------------- - -Using `MakerBundle`_ and `SymfonyCastsResetPasswordBundle`_, you can create -a secure out of the box solution to handle forgotten passwords. First, -install the SymfonyCastsResetPasswordBundle: - -.. code-block:: terminal - - $ composer require symfonycasts/reset-password-bundle - -Then, use the ``make:reset-password`` command. This asks you a few -questions about your app and generates all the files you need! After, -you'll see a success message and a list of any other steps you need to do. +.. versionadded:: 7.4 -.. code-block:: terminal - - $ php bin/console make:reset-password - -.. tip:: - - Starting in `MakerBundle`_: v1.57.0 - You can pass either ``--with-uuid`` or - ``--with-ulid`` to ``make:reset-password``. Leveraging Symfony's :doc:`Uid Component `, - the entities will be generated with the ``id`` type as :ref:`Uuid ` - or :ref:`Ulid ` instead of ``int``. - -You can customize the reset password bundle's behavior by updating the -``reset_password.yaml`` file. For more information on the configuration, -check out the `SymfonyCastsResetPasswordBundle`_ guide. + The feature to inject specific password hashers was introduced in Symfony 7.4. .. _security-password-migration: From 1cf79561eb134f698d54aa05f60bef132f8a7ed6 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Fri, 27 Jun 2025 08:38:05 +0200 Subject: [PATCH 016/122] Tweak and added some example --- routing.rst | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/routing.rst b/routing.rst index 46215fe1881..4eea1e23d29 100644 --- a/routing.rst +++ b/routing.rst @@ -1151,6 +1151,13 @@ special parameters created by Symfony: This is used for such things as setting the ``Content-Type`` of the response (e.g. a ``json`` format translates into a ``Content-Type`` of ``application/json``). +``_fragment`` + Used to set the fragment identifier, which is the optional last part of a URL that + starts with a ``#`` character and is used to identify a portion of a document. + +``_locale`` + Used to set the :ref:`locale ` on the request. + ``_query`` Used to add query parameters to the generated URL. @@ -1160,14 +1167,8 @@ special parameters created by Symfony: .. deprecated:: 7.4 - Passing a value other than an array as the ``_query`` parameter was deprecated in Symfony 7.4. - -``_fragment`` - Used to set the fragment identifier, which is the optional last part of a URL that - starts with a ``#`` character and is used to identify a portion of a document. - -``_locale`` - Used to set the :ref:`locale ` on the request. + Passing a value other than an array as the ``_query`` parameter was + deprecated in Symfony 7.4. You can include these attributes (except ``_fragment``) both in individual routes and in route imports. Symfony defines some special attributes with the same name @@ -1187,6 +1188,7 @@ and in route imports. Symfony defines some special attributes with the same name path: '/articles/{_locale}/search.{_format}', locale: 'en', format: 'html', + query: ['page' => 1], requirements: [ '_locale' => 'en|fr', '_format' => 'html|xml', @@ -1205,6 +1207,8 @@ and in route imports. Symfony defines some special attributes with the same name controller: App\Controller\ArticleController::search locale: en format: html + query: + page: 1 requirements: _locale: en|fr _format: html|xml @@ -1242,6 +1246,7 @@ and in route imports. Symfony defines some special attributes with the same name ->controller([ArticleController::class, 'search']) ->locale('en') ->format('html') + ->query(['page' => 1]) ->requirements([ '_locale' => 'en|fr', '_format' => 'html|xml', From 9d67f32feec97a62aa971de1a1cb7d630300946c Mon Sep 17 00:00:00 2001 From: Santiago San Martin Date: Thu, 26 Jun 2025 10:03:01 -0300 Subject: [PATCH 017/122] [BrowserKit] Add `isFirstPage()` and `isLastPage()` methods to History --- components/browser_kit.rst | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/components/browser_kit.rst b/components/browser_kit.rst index 8cf0772298c..7feb29096fb 100644 --- a/components/browser_kit.rst +++ b/components/browser_kit.rst @@ -329,6 +329,20 @@ history:: // go forward to documentation page $crawler = $client->forward(); + // check if the history position is on the first page + if (!$client->getHistory()->isFirstPage()) { + $crawler = $client->back(); + } + + // check if the history position is on the last page + if (!$client->getHistory()->isLastPage()) { + $crawler = $client->forward(); + } + +.. versionadded:: 7.4 + + The ``isFirstPage`` and ``isLastPage`` methods were introduced in Symfony 7.4. + You can delete the client's history with the ``restart()`` method. This will also delete all the cookies:: From bc2c9c33b8a07e9cd4b8870ed16f382e91a24ab1 Mon Sep 17 00:00:00 2001 From: Oskar Stark Date: Sun, 29 Jun 2025 22:14:52 +0200 Subject: [PATCH 018/122] minor --- components/browser_kit.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/browser_kit.rst b/components/browser_kit.rst index 7feb29096fb..ddbbd0f704d 100644 --- a/components/browser_kit.rst +++ b/components/browser_kit.rst @@ -333,7 +333,7 @@ history:: if (!$client->getHistory()->isFirstPage()) { $crawler = $client->back(); } - + // check if the history position is on the last page if (!$client->getHistory()->isLastPage()) { $crawler = $client->forward(); @@ -341,7 +341,7 @@ history:: .. versionadded:: 7.4 - The ``isFirstPage`` and ``isLastPage`` methods were introduced in Symfony 7.4. + The ``isFirstPage()`` and ``isLastPage()`` methods were introduced in Symfony 7.4. You can delete the client's history with the ``restart()`` method. This will also delete all the cookies:: From a3c42994c95c5b5a1cb764b4b6e91bb98a49c57b Mon Sep 17 00:00:00 2001 From: Antoine Lamirault Date: Sun, 29 Jun 2025 14:51:48 +0200 Subject: [PATCH 019/122] [Uid] Add microsecond precision to UUIDv7 and optimize on x64 --- components/uid.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/components/uid.rst b/components/uid.rst index b4083765436..46c710a0fd5 100644 --- a/components/uid.rst +++ b/components/uid.rst @@ -120,7 +120,7 @@ sortable (like :ref:`ULIDs `). It's more efficient for database indexing **UUID v7** (UNIX timestamp) Generates time-ordered UUIDs based on a high-resolution Unix Epoch timestamp -source (the number of milliseconds since midnight 1 Jan 1970 UTC, leap seconds excluded) +source (the number of microseconds since midnight 1 Jan 1970 UTC, leap seconds excluded) (`read the UUIDv7 spec `__). It's recommended to use this version over UUIDv1 and UUIDv6 because it provides better entropy (and a more strict chronological order of UUID generation):: @@ -130,6 +130,10 @@ better entropy (and a more strict chronological order of UUID generation):: $uuid = Uuid::v7(); // $uuid is an instance of Symfony\Component\Uid\UuidV7 +.. versionadded:: 7.4 + + In Symfony 7.4, the precision was increased from milliseconds to microseconds. + **UUID v8** (custom) Provides an RFC-compatible format intended for experimental or vendor-specific use cases From f5ca9c5c4d4ae035b855b593451f2817c05c8e40 Mon Sep 17 00:00:00 2001 From: Oskar Stark Date: Tue, 1 Jul 2025 10:34:19 +0200 Subject: [PATCH 020/122] Document the ability to set aliases and hidden status via command name - Add documentation for command aliases using pipe syntax in console.rst - Document how to make commands hidden using the `|` prefix in hide_commands.rst - Show examples for the #[AsCommand] attribute - Include version annotations for Symfony 7.4 - Ensure lines are within 80 character limit --- console.rst | 27 +++++++++++++++++++++++++++ console/hide_commands.rst | 24 ++++++++++++++++++++++-- 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/console.rst b/console.rst index a3d6701f47c..0ec6f28b02a 100644 --- a/console.rst +++ b/console.rst @@ -206,6 +206,33 @@ After configuring and registering the command, you can run it in the terminal: As you might expect, this command will do nothing as you didn't write any logic yet. Add your own logic inside the ``__invoke()`` method. +Command Aliases +~~~~~~~~~~~~~~~ + +You can define alternative names (aliases) for a command directly in its name +using a pipe (``|``) separator. The first name in the list becomes the actual +command name; the others are aliases that can also be used to run the command:: + + // src/Command/CreateUserCommand.php + namespace App\Command; + + use Symfony\Component\Console\Attribute\AsCommand; + use Symfony\Component\Console\Command\Command; + + #[AsCommand( + name: 'app:create-user|app:add-user|app:new-user', + description: 'Creates a new user.', + )] + class CreateUserCommand extends Command + { + // ... + } + +.. versionadded:: 7.4 + + The ability to define aliases through the command name was introduced in + Symfony 7.4. + Console Output -------------- diff --git a/console/hide_commands.rst b/console/hide_commands.rst index 4ab9d3a6dad..17bcf37a8f9 100644 --- a/console/hide_commands.rst +++ b/console/hide_commands.rst @@ -22,8 +22,28 @@ the ``hidden`` property of the ``AsCommand`` attribute:: // ... } -Hidden commands behave the same as normal commands but they are no longer displayed -in command listings, so end-users are not aware of their existence. +You can also define a command as hidden using the pipe (``|``) syntax in the +command name:: + + // src/Command/LegacyCommand.php + namespace App\Command; + + use Symfony\Component\Console\Attribute\AsCommand; + use Symfony\Component\Console\Command\Command; + + #[AsCommand(name: '|app:legacy')] + class LegacyCommand extends Command + { + // ... + } + +.. versionadded:: 7.4 + + The ability to define a command as hidden using the pipe syntax in the + command name was introduced in Symfony 7.4. + +Hidden commands behave the same as normal commands but they are no longer +displayed in command listings, so end-users are not aware of their existence. .. note:: From fbf50800fb5798d7bb4827a8d92a3577a8701c96 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Tue, 1 Jul 2025 13:12:49 +0200 Subject: [PATCH 021/122] [Console] Update the hidden command doc when using the pipe syntax --- console.rst | 2 ++ console/hide_commands.rst | 11 ++++------- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/console.rst b/console.rst index 0ec6f28b02a..0aeece2dc09 100644 --- a/console.rst +++ b/console.rst @@ -206,6 +206,8 @@ After configuring and registering the command, you can run it in the terminal: As you might expect, this command will do nothing as you didn't write any logic yet. Add your own logic inside the ``__invoke()`` method. +.. _command-aliases: + Command Aliases ~~~~~~~~~~~~~~~ diff --git a/console/hide_commands.rst b/console/hide_commands.rst index 17bcf37a8f9..aad4b6d45a4 100644 --- a/console/hide_commands.rst +++ b/console/hide_commands.rst @@ -22,8 +22,9 @@ the ``hidden`` property of the ``AsCommand`` attribute:: // ... } -You can also define a command as hidden using the pipe (``|``) syntax in the -command name:: +You can also define a command as hidden using the pipe (``|``) syntax of +:ref:`command aliases `. To do this, use the command name as one +of the aliases and leave the main command name (the part before the ``|``) empty:: // src/Command/LegacyCommand.php namespace App\Command; @@ -39,11 +40,7 @@ command name:: .. versionadded:: 7.4 - The ability to define a command as hidden using the pipe syntax in the - command name was introduced in Symfony 7.4. - -Hidden commands behave the same as normal commands but they are no longer -displayed in command listings, so end-users are not aware of their existence. + Support for hidding commands using the pipe syntax was introduced in Symfony 7.4. .. note:: From 97af218e6a6da8f7d4f291f5d1b8c96c3871a86a Mon Sep 17 00:00:00 2001 From: soyuka Date: Mon, 21 Jul 2025 11:37:07 +0200 Subject: [PATCH 022/122] [ObjectMapper] Add ObjectMapperAwareInterface to set the owning object --- object_mapper.rst | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/object_mapper.rst b/object_mapper.rst index 74cbb5756e7..18be632e050 100644 --- a/object_mapper.rst +++ b/object_mapper.rst @@ -589,6 +589,48 @@ Using it in practice:: // $employeeDto->manager->name === 'Alice' // $employeeDto->manager->manager === $employeeDto +Decorating the ObjectMapper +--------------------------- + +The ``object_mapper`` service can be decorated to add custom logic and state +management around the mapping process. + +One can use the +:class:`Symfony\\Component\\ObjectMapper\\ObjectMapperAwareInterface`. When a +decorator is applied, it can pass itself to the decorated service (if it implements +this interface). This allows the underlying services, like the ``ObjectMapper``, +to use the top-level decorator's ``map()`` method for recursive mapping, ensuring +that the decorator's state is consistently used. + +Here is an example of a decorator that preserves object identity across calls. +It uses the ``AsDecorator`` attribute to automatically configure itself as a +decorator for the ``object_mapper`` service:: + + // src/ObjectMapper/StatefulObjectMapper.php + namespace App\ObjectMapper; + + use Symfony\Component\DependencyInjection\Attribute\AsDecorator; + use Symfony\Component\ObjectMapper\ObjectMapperAwareInterface; + use Symfony\Component\ObjectMapper\ObjectMapperInterface; + + #[AsDecorator(decorates: ObjectMapperInterface::class)] + final class StatefulObjectMapper implements ObjectMapperInterface + { + public function __construct(private ObjectMapperInterface $decorated) + { + // Pass this decorator to the decorated service if it's aware + if ($this->decorated instanceof ObjectMapperAwareInterface) { + $this->decorated = $this->decorated->withObjectMapper($this); + } + } + + public function map(object $source, object|string|null $target = null): object + { + return $this->decorated->map($source, $target); + } + } + + .. _objectmapper-custom-mapping-logic: Custom Mapping Logic From e130ceb3e2da38c0b43703357d75ebf6d939a507 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Mon, 21 Jul 2025 16:28:11 +0200 Subject: [PATCH 023/122] Tweaks --- object_mapper.rst | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/object_mapper.rst b/object_mapper.rst index 18be632e050..e224226ae65 100644 --- a/object_mapper.rst +++ b/object_mapper.rst @@ -592,19 +592,19 @@ Using it in practice:: Decorating the ObjectMapper --------------------------- -The ``object_mapper`` service can be decorated to add custom logic and state -management around the mapping process. +The ``object_mapper`` service can be decorated to add custom logic or manage +state around the mapping process. -One can use the -:class:`Symfony\\Component\\ObjectMapper\\ObjectMapperAwareInterface`. When a -decorator is applied, it can pass itself to the decorated service (if it implements -this interface). This allows the underlying services, like the ``ObjectMapper``, -to use the top-level decorator's ``map()`` method for recursive mapping, ensuring -that the decorator's state is consistently used. +You can use the :class:`Symfony\\Component\\ObjectMapper\\ObjectMapperAwareInterface` +to enable the decorated service to access the outermost decorator. If the +decorated service implements this interface, the decorator can pass itself to +it. This allows underlying services, like the ``ObjectMapper``, to call the +decorator's ``map()`` method during recursive mapping, ensuring that the +decorator's state is used consistently throughout the process. -Here is an example of a decorator that preserves object identity across calls. +Here's an example of a decorator that preserves object identity across calls. It uses the ``AsDecorator`` attribute to automatically configure itself as a -decorator for the ``object_mapper`` service:: +decorator of the ``object_mapper`` service:: // src/ObjectMapper/StatefulObjectMapper.php namespace App\ObjectMapper; @@ -618,7 +618,7 @@ decorator for the ``object_mapper`` service:: { public function __construct(private ObjectMapperInterface $decorated) { - // Pass this decorator to the decorated service if it's aware + // pass this decorator to the decorated service if it's aware if ($this->decorated instanceof ObjectMapperAwareInterface) { $this->decorated = $this->decorated->withObjectMapper($this); } @@ -630,6 +630,9 @@ decorator for the ``object_mapper`` service:: } } +.. versionadded:: 7.4 + + The feature to decorate the ObjetMapper was introduced in Symfony 7.4. .. _objectmapper-custom-mapping-logic: From 65ca87649ec33f0efc03b4ef54692a260623f8e9 Mon Sep 17 00:00:00 2001 From: Santiago San Martin Date: Sat, 26 Jul 2025 14:26:39 -0300 Subject: [PATCH 024/122] [BrowserKit] Add browser history assertions to docs --- testing.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/testing.rst b/testing.rst index 539a8161157..a25cec26ae4 100644 --- a/testing.rst +++ b/testing.rst @@ -1047,6 +1047,10 @@ Browser Assertions ``assertBrowserCookieValueSame(string $name, string $expectedValue, string $path = '/', ?string $domain = null, string $message = '')`` Asserts the given cookie in the test Client is set to the expected value. +``assertBrowserHistoryIsOnFirstPage(string $message = '')``/``assertBrowserHistoryIsNotOnFirstPage(string $message = '')`` + Asserts that the browser history is (not) on the first page. +``assertBrowserHistoryIsOnLastPage(string $message = '')``/``assertBrowserHistoryIsNotOnLastPage(string $message = '')`` + Asserts that the browser history is (not) on the last page. ``assertThatForClient(Constraint $constraint, string $message = '')`` Asserts the given Constraint in the Client. Useful for using your custom asserts in the same way as built-in asserts (i.e. without passing the Client as argument):: @@ -1057,6 +1061,10 @@ Browser Assertions self::assertThatForClient(new SomeCustomConstraint()); } +.. versionadded:: 7.4 + + The ``assertBrowserHistoryIsOnFirstPage()`` and ``assertBrowserHistoryIsOnLastPage()`` assertions were introduced in Symfony 7.4. + Crawler Assertions .................. From d86745d2baeef67b89da475a736a168a77fcf9c0 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sat, 26 Jul 2025 14:36:56 +0200 Subject: [PATCH 025/122] [Validator] Add min and max in both error messages of `LengthValidator` --- reference/constraints/Length.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/reference/constraints/Length.rst b/reference/constraints/Length.rst index c1a8575070b..da25e5c1d40 100644 --- a/reference/constraints/Length.rst +++ b/reference/constraints/Length.rst @@ -201,10 +201,16 @@ You can use the following parameters in this message: Parameter Description ====================== ============================================================ ``{{ limit }}`` The expected maximum length +``{{ min }}`` The expected minimum length +``{{ max }}`` The expected maximum length ``{{ value }}`` The current (invalid) value ``{{ value_length }}`` The current value's length ====================== ============================================================ +.. versionadded:: 7.4 + + The `{{ min }}` and `{{ max }}` parameters were introduced in Symfony 7.4. + ``min`` ~~~~~~~ @@ -233,10 +239,16 @@ You can use the following parameters in this message: Parameter Description ====================== ============================================================ ``{{ limit }}`` The expected minimum length +``{{ min }}`` The expected minimum length +``{{ max }}`` The expected maximum length ``{{ value }}`` The current (invalid) value ``{{ value_length }}`` The current value's length ====================== ============================================================ +.. versionadded:: 7.4 + + The `{{ min }}` and `{{ max }}` parameters were introduced in Symfony 7.4. + .. include:: /reference/constraints/_normalizer-option.rst.inc .. include:: /reference/constraints/_payload-option.rst.inc From 48f8eee14d555eb3cfebcc2143119e48b6dd768a Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Fri, 25 Jul 2025 14:28:08 +0200 Subject: [PATCH 026/122] Add Static message in doc --- translation.rst | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/translation.rst b/translation.rst index ed656c62848..d3042356b8e 100644 --- a/translation.rst +++ b/translation.rst @@ -337,6 +337,19 @@ Templates are now much simpler because you can pass translatable objects to the There's also a :ref:`function called t() `, available both in Twig and PHP, as a shortcut to create translatable objects. +On the contrary, if you want your message to never be translated, you can +ensure this behavior with the +:class:`Symfony\\Component\\Translation\\StaticMessage` class:: + + use Symfony\Component\Translation\StaticMessage; + + $message = new StaticMessage('This message will never be translated.'); + +.. versionadded:: 7.4 + + The :class:`Symfony\\Component\\Translation\\StaticMessage` class was introduced in Symfony + 7.4. + .. _translation-in-templates: Translations in Templates From 31bb6877a9a8df7a1eb6353c5d23d922fe506841 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Mon, 28 Jul 2025 08:56:34 +0200 Subject: [PATCH 027/122] Tweaks and rewords --- translation.rst | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/translation.rst b/translation.rst index d3042356b8e..78589b2ec1c 100644 --- a/translation.rst +++ b/translation.rst @@ -337,18 +337,24 @@ Templates are now much simpler because you can pass translatable objects to the There's also a :ref:`function called t() `, available both in Twig and PHP, as a shortcut to create translatable objects. -On the contrary, if you want your message to never be translated, you can -ensure this behavior with the +Non-Translatable Messages +~~~~~~~~~~~~~~~~~~~~~~~~~ + +In some cases, you may want to explicitly prevent a message from being +translated. You can ensure this behavior by using the :class:`Symfony\\Component\\Translation\\StaticMessage` class:: use Symfony\Component\Translation\StaticMessage; $message = new StaticMessage('This message will never be translated.'); +This can be useful when rendering user-defined content or other strings +that must remain exactly as given. + .. versionadded:: 7.4 - The :class:`Symfony\\Component\\Translation\\StaticMessage` class was introduced in Symfony - 7.4. + The :class:`Symfony\\Component\\Translation\\StaticMessage` class was + introduced in Symfony 7.4. .. _translation-in-templates: From 6e5127b9c13e031460f08f22a7e33baf81e28fdd Mon Sep 17 00:00:00 2001 From: llupa Date: Mon, 28 Jul 2025 13:47:47 +0200 Subject: [PATCH 028/122] [Intl] Add `SYMFONY_INTL_WITH_USER_ASSIGNED` explanation --- components/intl.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/components/intl.rst b/components/intl.rst index ba3cbdcb959..4f70963fc07 100644 --- a/components/intl.rst +++ b/components/intl.rst @@ -202,6 +202,16 @@ numeric country codes:: $exists = Countries::numericCodeExists('250'); // => true +.. note:: + + When the ``SYMFONY_INTL_WITH_USER_ASSIGNED`` environment variable is set, + the Symfony Intl component will also recognize user-assigned codes: ``XK``, ``XKK``, and ``983``. + This allows applications to handle these codes, which is useful for supporting regions that need to use them. + +.. versionadded:: 7.4 + + Support for ``SYMFONY_INTL_WITH_USER_ASSIGNED`` was introduced in Symfony 7.4. + Locales ~~~~~~~ From b4d18711a472a314d2eb214a591d4c25fea5a123 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Wed, 6 Aug 2025 14:58:31 +0200 Subject: [PATCH 029/122] [Serializer] Add support for "can" prefixin attribute loader --- serializer.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/serializer.rst b/serializer.rst index eb06f1b34a1..927a798467d 100644 --- a/serializer.rst +++ b/serializer.rst @@ -1476,10 +1476,14 @@ normalizers (in order of priority): to read and write in the object. This allows it to access properties directly or using getters, setters, hassers, issers, canners, adders and removers. Names are generated by removing the ``get``, ``set``, - ``has``, ``is``, ``add`` or ``remove`` prefix from the method name and + ``has``, ``is``, ``can``, ``add`` or ``remove`` prefix from the method name and transforming the first letter to lowercase (e.g. ``getFirstName()`` -> ``firstName``). + .. versionadded:: 7.4 + + Support for the ``can`` prefix was introduced in Symfony 7.4. + During denormalization, it supports using the constructor as well as the discovered methods. From d649179159b8cab0e313f2410428fe07591d25b2 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Wed, 6 Aug 2025 15:22:16 +0200 Subject: [PATCH 030/122] [Serializer] Add CDATA_WRAPPING_NAME_PATTERN support to XmlEncoder --- serializer/encoders.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/serializer/encoders.rst b/serializer/encoders.rst index 8238d4d057d..c3fb2e26b50 100644 --- a/serializer/encoders.rst +++ b/serializer/encoders.rst @@ -205,6 +205,10 @@ These are the options available on the :ref:`serializer context &]/``) A regular expression pattern to determine if a value should be wrapped in a CDATA section. +``cdata_wrapping_name_pattern`` (default: ``false``) + A regular expression pattern that defines the names of fields whose values + should always be wrapped in a CDATA section, even if their contents don't + require it. Example: ``'/(firstname|lastname)/'`` ``ignore_empty_attributes`` (default: ``false``) If set to true, ignores all attributes with empty values in the generated XML @@ -216,6 +220,10 @@ These are the options available on the :ref:`serializer context Date: Wed, 6 Aug 2025 16:30:47 +0200 Subject: [PATCH 031/122] [JsonStreamer] Mention how to get the current object in value transformers --- serializer/streaming_json.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/serializer/streaming_json.rst b/serializer/streaming_json.rst index 3fd44824bc6..8aee4539a23 100644 --- a/serializer/streaming_json.rst +++ b/serializer/streaming_json.rst @@ -541,6 +541,16 @@ When callables are not enough, you can use a service implementing the The ``getStreamValueType()`` method must return the value's type as it will appear in the JSON stream. +.. tip:: + + The ``$options`` argument of the ``transform()`` method includes a special + option called ``_current_object`` which gives access to the object holding + the current property (or ``null`` if there's none). + + .. versionadded:: 7.4 + + The ``_current_object`` option was introduced in Symfony 7.4. + To use this transformer in a class, configure the ``#[ValueTransformer]`` attribute:: // src/Dto/Dog.php From c5e43d015970d84a6e100ece338a4fee73086175 Mon Sep 17 00:00:00 2001 From: Bob van de Vijver Date: Wed, 6 Aug 2025 20:24:26 +0200 Subject: [PATCH 032/122] Add Microsoft Graph Mailer bridge --- mailer.rst | 196 ++++++++++++++++++++++++++++------------------------- 1 file changed, 103 insertions(+), 93 deletions(-) diff --git a/mailer.rst b/mailer.rst index bdc50757cdd..04f09e3c169 100644 --- a/mailer.rst +++ b/mailer.rst @@ -97,28 +97,29 @@ Using a 3rd Party Transport Instead of using your own SMTP server or sendmail binary, you can send emails via a third-party provider: -===================== =============================================== =============== -Service Install with Webhook support -===================== =============================================== =============== -`AhaSend`_ ``composer require symfony/aha-send-mailer`` yes +===================== =================================================== =============== +Service Install with Webhook support +===================== =================================================== =============== +`AhaSend`_ ``composer require symfony/aha-send-mailer`` yes `Amazon SES`_ ``composer require symfony/amazon-mailer`` `Azure`_ ``composer require symfony/azure-mailer`` -`Brevo`_ ``composer require symfony/brevo-mailer`` yes +`Brevo`_ ``composer require symfony/brevo-mailer`` yes `Infobip`_ ``composer require symfony/infobip-mailer`` -`Mailgun`_ ``composer require symfony/mailgun-mailer`` yes -`Mailjet`_ ``composer require symfony/mailjet-mailer`` yes -`Mailomat`_ ``composer require symfony/mailomat-mailer`` yes +`Mailgun`_ ``composer require symfony/mailgun-mailer`` yes +`Mailjet`_ ``composer require symfony/mailjet-mailer`` yes +`Mailomat`_ ``composer require symfony/mailomat-mailer`` yes `MailPace`_ ``composer require symfony/mail-pace-mailer`` -`MailerSend`_ ``composer require symfony/mailer-send-mailer`` yes -`Mailtrap`_ ``composer require symfony/mailtrap-mailer`` yes -`Mandrill`_ ``composer require symfony/mailchimp-mailer`` yes +`MailerSend`_ ``composer require symfony/mailer-send-mailer`` yes +`Mailtrap`_ ``composer require symfony/mailtrap-mailer`` yes +`Mandrill`_ ``composer require symfony/mailchimp-mailer`` yes +`Microsoft Graph`_ ``composer require symfony/microsoft-graph-mailer`` `Postal`_ ``composer require symfony/postal-mailer`` -`Postmark`_ ``composer require symfony/postmark-mailer`` yes -`Resend`_ ``composer require symfony/resend-mailer`` yes +`Postmark`_ ``composer require symfony/postmark-mailer`` yes +`Resend`_ ``composer require symfony/resend-mailer`` yes `Scaleway`_ ``composer require symfony/scaleway-mailer`` -`SendGrid`_ ``composer require symfony/sendgrid-mailer`` yes -`Sweego`_ ``composer require symfony/sweego-mailer`` yes -===================== =============================================== =============== +`SendGrid`_ ``composer require symfony/sendgrid-mailer`` yes +`Sweego`_ ``composer require symfony/sweego-mailer`` yes +===================== =================================================== =============== .. versionadded:: 7.1 @@ -132,6 +133,10 @@ Service Install with Webhook su The AhaSend integration was introduced in Symfony 7.3. +.. versionadded:: 7.4 + + The Microsoft Graph integration was introduced in Symfony 7.4. + .. note:: As a convenience, Symfony also provides support for Gmail (``composer @@ -177,83 +182,87 @@ transport, but you can force to use one: This table shows the full list of available DSN formats for each third party provider: -+------------------------+---------------------------------------------------------+ -| Provider | Formats | -+========================+=========================================================+ -| `AhaSend`_ | - API ``ahasend+api://KEY@default`` | -| | - HTTP n/a | -| | - SMTP ``ahasend+smtp://USERNAME:PASSWORD@default`` | -+------------------------+---------------------------------------------------------+ -| `Amazon SES`_ | - SMTP ``ses+smtp://USERNAME:PASSWORD@default`` | -| | - HTTP ``ses+https://ACCESS_KEY:SECRET_KEY@default`` | -| | - API ``ses+api://ACCESS_KEY:SECRET_KEY@default`` | -+------------------------+---------------------------------------------------------+ -| `Azure`_ | - API ``azure+api://ACS_RESOURCE_NAME:KEY@default`` | -+------------------------+---------------------------------------------------------+ -| `Brevo`_ | - SMTP ``brevo+smtp://USERNAME:PASSWORD@default`` | -| | - HTTP n/a | -| | - API ``brevo+api://KEY@default`` | -+------------------------+---------------------------------------------------------+ -| `Google Gmail`_ | - SMTP ``gmail+smtp://USERNAME:APP-PASSWORD@default`` | -| | - HTTP n/a | -| | - API n/a | -+------------------------+---------------------------------------------------------+ -| `Infobip`_ | - SMTP ``infobip+smtp://KEY@default`` | -| | - HTTP n/a | -| | - API ``infobip+api://KEY@BASE_URL`` | -+------------------------+---------------------------------------------------------+ -| `Mandrill`_ | - SMTP ``mandrill+smtp://USERNAME:PASSWORD@default`` | -| | - HTTP ``mandrill+https://KEY@default`` | -| | - API ``mandrill+api://KEY@default`` | -+------------------------+---------------------------------------------------------+ -| `MailerSend`_ | - SMTP ``mailersend+smtp://KEY@default`` | -| | - HTTP n/a | -| | - API ``mailersend+api://KEY@BASE_URL`` | -+------------------------+---------------------------------------------------------+ -| `Mailgun`_ | - SMTP ``mailgun+smtp://USERNAME:PASSWORD@default`` | -| | - HTTP ``mailgun+https://KEY:DOMAIN@default`` | -| | - API ``mailgun+api://KEY:DOMAIN@default`` | -+------------------------+---------------------------------------------------------+ -| `Mailjet`_ | - SMTP ``mailjet+smtp://ACCESS_KEY:SECRET_KEY@default`` | -| | - HTTP n/a | -| | - API ``mailjet+api://ACCESS_KEY:SECRET_KEY@default`` | -+------------------------+---------------------------------------------------------+ -| `Mailomat`_ | - SMTP ``mailomat+smtp://USERNAME:PASSWORD@default`` | -| | - HTTP n/a | -| | - API ``mailomat+api://KEY@default`` | -+------------------------+---------------------------------------------------------+ -| `MailPace`_ | - SMTP ``mailpace+api://API_TOKEN@default`` | -| | - HTTP n/a | -| | - API ``mailpace+api://API_TOKEN@default`` | -+------------------------+---------------------------------------------------------+ -| `Mailtrap`_ | - SMTP ``mailtrap+smtp://PASSWORD@default`` | -| | - HTTP n/a | -| | - API ``mailtrap+api://API_TOKEN@default`` | -+------------------------+---------------------------------------------------------+ -| `Postal`_ | - SMTP n/a | -| | - HTTP n/a | -| | - API ``postal+api://API_KEY@BASE_URL`` | -+------------------------+---------------------------------------------------------+ -| `Postmark`_ | - SMTP ``postmark+smtp://ID@default`` | -| | - HTTP n/a | -| | - API ``postmark+api://KEY@default`` | -+------------------------+---------------------------------------------------------+ -| `Resend`_ | - SMTP ``resend+smtp://resend:API_KEY@default`` | -| | - HTTP n/a | -| | - API ``resend+api://API_KEY@default`` | -+------------------------+---------------------------------------------------------+ -| `Scaleway`_ | - SMTP ``scaleway+smtp://PROJECT_ID:API_KEY@default`` | -| | - HTTP n/a | -| | - API ``scaleway+api://PROJECT_ID:API_KEY@default`` | -+------------------------+---------------------------------------------------------+ -| `Sendgrid`_ | - SMTP ``sendgrid+smtp://KEY@default`` | -| | - HTTP n/a | -| | - API ``sendgrid+api://KEY@default`` | -+------------------------+---------------------------------------------------------+ -| `Sweego`_ | - SMTP ``sweego+smtp://LOGIN:PASSWORD@HOST:PORT`` | -| | - HTTP n/a | -| | - API ``sweego+api://API_KEY@default`` | -+------------------------+---------------------------------------------------------+ ++------------------------+-------------------------------------------------------------------------------------------+ +| Provider | Formats | ++========================+===========================================================================================+ +| `AhaSend`_ | - API ``ahasend+api://KEY@default`` | +| | - HTTP n/a | +| | - SMTP ``ahasend+smtp://USERNAME:PASSWORD@default`` | ++------------------------+-------------------------------------------------------------------------------------------+ +| `Amazon SES`_ | - SMTP ``ses+smtp://USERNAME:PASSWORD@default`` | +| | - HTTP ``ses+https://ACCESS_KEY:SECRET_KEY@default`` | +| | - API ``ses+api://ACCESS_KEY:SECRET_KEY@default`` | ++------------------------+-------------------------------------------------------------------------------------------+ +| `Azure`_ | - API ``azure+api://ACS_RESOURCE_NAME:KEY@default`` | ++------------------------+-------------------------------------------------------------------------------------------+ +| `Brevo`_ | - SMTP ``brevo+smtp://USERNAME:PASSWORD@default`` | +| | - HTTP n/a | +| | - API ``brevo+api://KEY@default`` | ++------------------------+-------------------------------------------------------------------------------------------+ +| `Google Gmail`_ | - SMTP ``gmail+smtp://USERNAME:APP-PASSWORD@default`` | +| | - HTTP n/a | +| | - API n/a | ++------------------------+-------------------------------------------------------------------------------------------+ +| `Infobip`_ | - SMTP ``infobip+smtp://KEY@default`` | +| | - HTTP n/a | +| | - API ``infobip+api://KEY@BASE_URL`` | ++------------------------+-------------------------------------------------------------------------------------------+ +| `Mandrill`_ | - SMTP ``mandrill+smtp://USERNAME:PASSWORD@default`` | +| | - HTTP ``mandrill+https://KEY@default`` | +| | - API ``mandrill+api://KEY@default`` | ++------------------------+-------------------------------------------------------------------------------------------+ +| `MailerSend`_ | - SMTP ``mailersend+smtp://KEY@default`` | +| | - HTTP n/a | +| | - API ``mailersend+api://KEY@BASE_URL`` | ++------------------------+-------------------------------------------------------------------------------------------+ +| `Mailgun`_ | - SMTP ``mailgun+smtp://USERNAME:PASSWORD@default`` | +| | - HTTP ``mailgun+https://KEY:DOMAIN@default`` | +| | - API ``mailgun+api://KEY:DOMAIN@default`` | ++------------------------+-------------------------------------------------------------------------------------------+ +| `Mailjet`_ | - SMTP ``mailjet+smtp://ACCESS_KEY:SECRET_KEY@default`` | +| | - HTTP n/a | +| | - API ``mailjet+api://ACCESS_KEY:SECRET_KEY@default`` | ++------------------------+-------------------------------------------------------------------------------------------+ +| `Mailomat`_ | - SMTP ``mailomat+smtp://USERNAME:PASSWORD@default`` | +| | - HTTP n/a | +| | - API ``mailomat+api://KEY@default`` | ++------------------------+-------------------------------------------------------------------------------------------+ +| `MailPace`_ | - SMTP ``mailpace+api://API_TOKEN@default`` | +| | - HTTP n/a | +| | - API ``mailpace+api://API_TOKEN@default`` | ++------------------------+-------------------------------------------------------------------------------------------+ +| `Mailtrap`_ | - SMTP ``mailtrap+smtp://PASSWORD@default`` | +| | - HTTP n/a | +| | - API ``mailtrap+api://API_TOKEN@default`` | ++------------------------+-------------------------------------------------------------------------------------------+ +| `Microsoft Graph`_ | - SMTP n/a | +| | - HTTP n/a | +| | - API ``microsoftgraph+api://CLIENT_APP_ID:CLIENT_APP_SECRET@default?tenantId=TENANT_ID`` | ++------------------------+-------------------------------------------------------------------------------------------+ +| `Postal`_ | - SMTP n/a | +| | - HTTP n/a | +| | - API ``postal+api://API_KEY@BASE_URL`` | ++------------------------+-------------------------------------------------------------------------------------------+ +| `Postmark`_ | - SMTP ``postmark+smtp://ID@default`` | +| | - HTTP n/a | +| | - API ``postmark+api://KEY@default`` | ++------------------------+-------------------------------------------------------------------------------------------+ +| `Resend`_ | - SMTP ``resend+smtp://resend:API_KEY@default`` | +| | - HTTP n/a | +| | - API ``resend+api://API_KEY@default`` | ++------------------------+-------------------------------------------------------------------------------------------+ +| `Scaleway`_ | - SMTP ``scaleway+smtp://PROJECT_ID:API_KEY@default`` | +| | - HTTP n/a | +| | - API ``scaleway+api://PROJECT_ID:API_KEY@default`` | ++------------------------+-------------------------------------------------------------------------------------------+ +| `Sendgrid`_ | - SMTP ``sendgrid+smtp://KEY@default`` | +| | - HTTP n/a | +| | - API ``sendgrid+api://KEY@default`` | ++------------------------+-------------------------------------------------------------------------------------------+ +| `Sweego`_ | - SMTP ``sweego+smtp://LOGIN:PASSWORD@HOST:PORT`` | +| | - HTTP n/a | +| | - API ``sweego+api://API_KEY@default`` | ++------------------------+-------------------------------------------------------------------------------------------+ .. warning:: @@ -2289,6 +2298,7 @@ the :class:`Symfony\\Bundle\\FrameworkBundle\\Test\\MailerAssertionsTrait`:: .. _`Markdown syntax`: https://commonmark.org/ .. _`Mailomat`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Mailer/Bridge/Mailomat/README.md .. _`MailPace`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Mailer/Bridge/MailPace/README.md +.. _`Microsoft Graph`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Mailer/Bridge/MicrosoftGraph/README.md .. _`OpenSSL PHP extension`: https://www.php.net/manual/en/book.openssl.php .. _`PEM encoded`: https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail .. _`Postal`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Mailer/Bridge/Postal/README.md From 3f8b86e7150bd7146df09998b473135e9d1e4c29 Mon Sep 17 00:00:00 2001 From: Santiago San Martin Date: Wed, 20 Aug 2025 19:24:36 -0300 Subject: [PATCH 033/122] [Routing] allow passing multiple environments to Route `env` argument --- routing.rst | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/routing.rst b/routing.rst index 9e83964bd2b..8948ab4ae4d 100644 --- a/routing.rst +++ b/routing.rst @@ -274,6 +274,13 @@ given value: { // ... } + + // You can also pass an array of environments + #[Route('/tools', name: 'tools', env: ['dev', 'test'])] + public function developerTools(): Response + { + // ... + } } .. code-block:: yaml @@ -312,6 +319,10 @@ given value: } }; +.. versionadded:: 7.4 + + The ability to pass an array of environments to the ``env`` argument was introduced in Symfony 7.4. + .. _routing-matching-expressions: Matching Expressions From 0e4da9e2da2537ebc82b4dc5b005477f4d756d8e Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Mon, 25 Aug 2025 08:53:11 +0200 Subject: [PATCH 034/122] stop mentioning that JsonPath and JsonStreamer are experimental --- components/json_path.rst | 3 +-- serializer/streaming_json.rst | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/components/json_path.rst b/components/json_path.rst index 9db8e48885e..6fa1e475eef 100644 --- a/components/json_path.rst +++ b/components/json_path.rst @@ -3,8 +3,7 @@ The JsonPath Component .. versionadded:: 7.3 - The JsonPath component was introduced in Symfony 7.3 as an - :doc:`experimental feature `. + The JsonPath component was introduced in Symfony 7.3. The JsonPath component lets you query and extract data from JSON structures. It implements the `RFC 9535 – JSONPath`_ standard, allowing you to navigate diff --git a/serializer/streaming_json.rst b/serializer/streaming_json.rst index 8aee4539a23..60870f8a138 100644 --- a/serializer/streaming_json.rst +++ b/serializer/streaming_json.rst @@ -3,8 +3,7 @@ Streaming JSON .. versionadded:: 7.3 - The JsonStreamer component was introduced in Symfony 7.3 as an - :doc:`experimental feature `. + The JsonStreamer component was introduced in Symfony 7.3. Symfony can encode PHP data structures to JSON streams and decode JSON streams back into PHP data structures. From 2d2269f1bd4d3950c5ea0300f3f91e9877105692 Mon Sep 17 00:00:00 2001 From: mamazu <14860264+mamazu@users.noreply.github.com> Date: Sat, 23 Aug 2025 11:32:45 +0200 Subject: [PATCH 035/122] Documenting changes in the debug:router output --- page_creation.rst | 10 +++++----- routing.rst | 41 +++++++++++++++++++++++++++++++---------- 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/page_creation.rst b/page_creation.rst index 0e2fd78e180..d7833b84bee 100644 --- a/page_creation.rst +++ b/page_creation.rst @@ -125,11 +125,11 @@ You should see your ``app_lucky_number`` route in the list: .. code-block:: terminal - ---------------- ------- ------- ----- -------------- - Name Method Scheme Host Path - ---------------- ------- ------- ----- -------------- - app_lucky_number ANY ANY ANY /lucky/number - ---------------- ------- ------- ----- -------------- + ---------------- ------- -------------- + Name Method Path + ---------------- ------- -------------- + app_lucky_number ANY /lucky/number + ---------------- ------- -------------- You will also see debugging routes besides ``app_lucky_number`` -- more on the debugging routes in the next section. diff --git a/routing.rst b/routing.rst index 9e83964bd2b..1d1147fa6b1 100644 --- a/routing.rst +++ b/routing.rst @@ -487,20 +487,23 @@ evaluates them: $ php bin/console debug:router - ---------------- ------- ------- ----- -------------------------------------------- - Name Method Scheme Host Path - ---------------- ------- ------- ----- -------------------------------------------- - homepage ANY ANY ANY / - contact GET ANY ANY /contact - contact_process POST ANY ANY /contact - article_show ANY ANY ANY /articles/{_locale}/{year}/{title}.{_format} - blog ANY ANY ANY /blog/{page} - blog_show ANY ANY ANY /blog/{slug} - ---------------- ------- ------- ----- -------------------------------------------- + ---------------- ------- -------------------------------------------- + Name Method Path + ---------------- ------- -------------------------------------------- + homepage ANY / + contact GET /contact + contact_process POST /contact + article_show ANY /articles/{_locale}/{year}/{title}.{_format} + blog ANY /blog/{page} + blog_show ANY /blog/{slug} + ---------------- ------- -------------------------------------------- # pass this option to also display all the defined route aliases $ php bin/console debug:router --show-aliases + # pass this option to also display the associated controllers with the routes + $ php bin/console debug:router --show-controllers + # pass this option to only display routes that match the given HTTP method # (you can use the special value ANY to see routes that match any method) $ php bin/console debug:router --method=GET @@ -510,6 +513,24 @@ evaluates them: The ``--method`` option was introduced in Symfony 7.3. +.. versionadded:: 7.4 + + For sites that don't have routes with schemes or hosts defined those columns are + hidden. They will be displayed if some routes configure the schema or host properties + of the route + + .. code-block:: terminal + + $ php bin/console debug:router + + ------------ ------- ------- --------- --------- + Name Method Scheme Host Path + ------------ ------- ------- --------- --------- + homepage ANY http ANY /homapage + contact GET https ANY /contact + contact_post POST ANY localhost /contact + ------------ ------- ------- --------- --------- + Pass the name (or part of the name) of some route to this argument to print the route details: From 26fea4cc56a3578725151e681d9c38a13184dcb6 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Wed, 27 Aug 2025 22:14:34 +0200 Subject: [PATCH 036/122] Add support for JSON encoded APP_RUNTIME_OPTIONS value --- components/runtime.rst | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/components/runtime.rst b/components/runtime.rst index 770ea102563..48bb7530caf 100644 --- a/components/runtime.rst +++ b/components/runtime.rst @@ -302,16 +302,23 @@ Using Options ~~~~~~~~~~~~~ Some behavior of the Runtimes can be modified through runtime options. They -can be set using the ``APP_RUNTIME_OPTIONS`` environment variable:: +can be set using the ``APP_RUNTIME_OPTIONS`` environment variable as an array +or a JSON encoded value:: $_SERVER['APP_RUNTIME_OPTIONS'] = [ 'project_dir' => '/var/task', ]; + // Which is the same than + // $_SERVER['APP_RUNTIME_OPTIONS'] = '{"project_dir":"\/var\/task"}'; require_once dirname(__DIR__).'/vendor/autoload_runtime.php'; // ... +.. versionadded:: 7.4 + + The support for JSON encoded APP_RUNTIME_OPTIONS value was introduced in Symfony 7.4. + You can also configure ``extra.runtime`` in ``composer.json``: .. code-block:: json From eccf50a78a0767795c6ace823099c2ee17d87512 Mon Sep 17 00:00:00 2001 From: tcoch Date: Fri, 29 Aug 2025 22:40:30 +0200 Subject: [PATCH 037/122] Add documentation for extending `IsGranted` attribute --- security.rst | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/security.rst b/security.rst index 8218b4ec355..6187c8c3bbe 100644 --- a/security.rst +++ b/security.rst @@ -2523,6 +2523,26 @@ that is thrown with the ``exceptionCode`` argument:: // ... } +You can also extend the ``IsGranted`` attribute to create meaningful shortcuts:: + + // src/Security/Attribute/IsAdmin.php + // ... + + use Symfony\Component\Security\Http\Attribute\IsGranted; + + class IsAdmin extends IsGranted + { + public function __construct() + { + return parent::__construct('ROLE_ADMIN'); + } + } + +.. versionadded:: 7.4 + + The :class:`Symfony\\Component\\Security\\Http\\Attribute\\IsGranted` + attribute is extendable since Symfony 7.4. + .. _security-template: Access Control in Templates From 8b5ef68328d8e87892fcae4d4b14aa6e1b9d49b5 Mon Sep 17 00:00:00 2001 From: Kieran Cross Date: Tue, 5 Aug 2025 20:18:15 +0100 Subject: [PATCH 038/122] [Mailer] Document Mailtrap's sandbox compatibility --- mailer.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mailer.rst b/mailer.rst index 656ccf4eece..b1afd58b58a 100644 --- a/mailer.rst +++ b/mailer.rst @@ -135,7 +135,7 @@ Service Install with Webhoo .. versionadded:: 7.4 - The Microsoft Graph integration was introduced in Symfony 7.4. + The Microsoft Graph integration and support for Mailtrap's sandbox environment were introduced in Symfony 7.4. .. note:: @@ -235,7 +235,8 @@ party provider: +------------------------+-------------------------------------------------------------------------------------------+ | `Mailtrap`_ | - SMTP ``mailtrap+smtp://PASSWORD@default`` | | | - HTTP n/a | -| | - API ``mailtrap+api://API_TOKEN@default`` | +| | - API (Live) ``mailtrap+api://API_TOKEN@default`` | +| | - API (Sandbox) ``mailtrap+sandbox://API_TOKEN@default/?inboxId=INBOX_ID`` | +------------------------+-------------------------------------------------------------------------------------------+ | `Microsoft Graph`_ | - SMTP n/a | | | - HTTP n/a | From f54811fef29241284cf1f8ac1cc33918aba49e5f Mon Sep 17 00:00:00 2001 From: Santiago San Martin Date: Wed, 20 Aug 2025 19:40:40 -0300 Subject: [PATCH 039/122] [Security] add `methods` argument to #[IsGranted] to restrict access by HTTP method --- security.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/security.rst b/security.rst index 6187c8c3bbe..50e33402188 100644 --- a/security.rst +++ b/security.rst @@ -2543,6 +2543,29 @@ You can also extend the ``IsGranted`` attribute to create meaningful shortcuts:: The :class:`Symfony\\Component\\Security\\Http\\Attribute\\IsGranted` attribute is extendable since Symfony 7.4. +You can restrict access validation to specific HTTP methods +by using the ``methods`` argument:: + + // src/Controller/AdminController.php + // ... + + use Symfony\Component\Security\Http\Attribute\IsGranted; + + #[IsGranted('ROLE_ADMIN', methods: 'POST')] + class AdminController extends AbstractController + { + // You can also specify an array of methods + #[IsGranted('ROLE_SUPER_ADMIN', methods: ['GET', 'PUT'])] + public function adminDashboard(): Response + { + // ... + } + } + +.. versionadded:: 7.4 + + The ``methods`` argument was introduced in Symfony 7.4. + .. _security-template: Access Control in Templates From 0582836134265f038dc1854647d0d51cdf2cb141 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Mon, 1 Sep 2025 16:35:27 +0200 Subject: [PATCH 040/122] Minor tweaks --- components/runtime.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/runtime.rst b/components/runtime.rst index 48bb7530caf..b401127eab9 100644 --- a/components/runtime.rst +++ b/components/runtime.rst @@ -303,12 +303,12 @@ Using Options Some behavior of the Runtimes can be modified through runtime options. They can be set using the ``APP_RUNTIME_OPTIONS`` environment variable as an array -or a JSON encoded value:: +or a JSON encoded string:: $_SERVER['APP_RUNTIME_OPTIONS'] = [ 'project_dir' => '/var/task', ]; - // Which is the same than + // same configuration using JSON: // $_SERVER['APP_RUNTIME_OPTIONS'] = '{"project_dir":"\/var\/task"}'; require_once dirname(__DIR__).'/vendor/autoload_runtime.php'; @@ -317,7 +317,7 @@ or a JSON encoded value:: .. versionadded:: 7.4 - The support for JSON encoded APP_RUNTIME_OPTIONS value was introduced in Symfony 7.4. + The support for JSON contents in ``APP_RUNTIME_OPTIONS`` was introduced in Symfony 7.4. You can also configure ``extra.runtime`` in ``composer.json``: From ba921fb376416799d0112eb6e949cba1a6046011 Mon Sep 17 00:00:00 2001 From: soyuka Date: Sun, 24 Aug 2025 09:25:07 +0200 Subject: [PATCH 041/122] [ObjectMapper] embed collection transformer --- object_mapper.rst | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/object_mapper.rst b/object_mapper.rst index e224226ae65..adf4a624377 100644 --- a/object_mapper.rst +++ b/object_mapper.rst @@ -428,6 +428,32 @@ And the related target object must define the ``createFromLegacy()`` method:: } } +Mapping Collections +------------------- + +By default, ObjectMapper does not map arrays or traversable collections. +To map each item in a collection (such as an array of DTOs to an array of entities), you **must** use the `MapCollection` transformer explicitly: + +Example:: + + use Symfony\Component\ObjectMapper\Attribute\Map; + use Symfony\Component\ObjectMapper\Transform\MapCollection; + + class ProductListInput + { + #[Map(transform: new MapCollection())] + /** @var ProductInput[] */ + public array $products; + } + +This configuration tells ObjectMapper to map each item in the `products` array using the usual mapping rules. + +If you do not add `transform: new MapCollection()`, the array will be mapped as-is. + +.. versionadded:: 7.4 + + The MapCollection component was introduced in Symfony 7.4. + Mapping Multiple Targets ------------------------ From 20bd5cb7779187d82ac774dd840ad3af77aba8d7 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Mon, 1 Sep 2025 16:41:50 +0200 Subject: [PATCH 042/122] Minor tweaks --- object_mapper.rst | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/object_mapper.rst b/object_mapper.rst index adf4a624377..19b5032e2d4 100644 --- a/object_mapper.rst +++ b/object_mapper.rst @@ -431,8 +431,9 @@ And the related target object must define the ``createFromLegacy()`` method:: Mapping Collections ------------------- -By default, ObjectMapper does not map arrays or traversable collections. -To map each item in a collection (such as an array of DTOs to an array of entities), you **must** use the `MapCollection` transformer explicitly: +By default, ObjectMapper does not map arrays or traversable collections. To map +each item in a collection (for example, mapping an array of DTOs to an array of +entities), you **must** explicitly use the ``MapCollection`` transformer: Example:: @@ -446,13 +447,13 @@ Example:: public array $products; } -This configuration tells ObjectMapper to map each item in the `products` array using the usual mapping rules. - -If you do not add `transform: new MapCollection()`, the array will be mapped as-is. +With this configuration, ObjectMapper maps each item in the ``products`` array +according to the usual mapping rules. Without ``transform: new MapCollection()``, +the array is left unchanged. .. versionadded:: 7.4 - The MapCollection component was introduced in Symfony 7.4. + The ``MapCollection`` transformer was introduced in Symfony 7.4. Mapping Multiple Targets ------------------------ From 6829e310d682694c20a492aa0b0a65b2b585293a Mon Sep 17 00:00:00 2001 From: Moshe Weitzman Date: Fri, 22 Aug 2025 09:49:31 -0400 Subject: [PATCH 043/122] Document that usages may be supplied by AsCommand Attribute --- console.rst | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/console.rst b/console.rst index a9457c1620f..7f581ff7c79 100644 --- a/console.rst +++ b/console.rst @@ -147,12 +147,13 @@ If you can't use PHP attributes, register the command as a service and :ref:`default services.yaml configuration `, this is already done for you, thanks to :ref:`autoconfiguration `. -You can also use ``#[AsCommand]`` to add a description and longer help text for the command:: +You can also use ``#[AsCommand]`` to add a description, usages, and longer help text for the command:: #[AsCommand( name: 'app:create-user', description: 'Creates a new user.', // the command description shown when running "php bin/console list" help: 'This command allows you to create a user...', // the command help shown when running the command with the "--help" option + usages: ['app:create-user alice'], )] class CreateUserCommand { @@ -162,6 +163,10 @@ You can also use ``#[AsCommand]`` to add a description and longer help text for } } +.. versionadded:: 7.4 + + The ability to add usages via a ``$usages`` property was introduced in Symfony 7.4. + Additionally, you can extend the :class:`Symfony\\Component\\Console\\Command\\Command` class to leverage advanced features like lifecycle hooks (e.g. :method:`Symfony\\Component\\Console\\Command\\Command::initialize` and and :method:`Symfony\\Component\\Console\\Command\\Command::interact`):: From f9e1b8912c80c659f3f6b31c56aa40fd298db02d Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Tue, 2 Sep 2025 17:28:26 +0200 Subject: [PATCH 044/122] Tweaks --- console.rst | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/console.rst b/console.rst index 7f581ff7c79..5ee09b3a2bb 100644 --- a/console.rst +++ b/console.rst @@ -147,13 +147,17 @@ If you can't use PHP attributes, register the command as a service and :ref:`default services.yaml configuration `, this is already done for you, thanks to :ref:`autoconfiguration `. -You can also use ``#[AsCommand]`` to add a description, usages, and longer help text for the command:: +You can also use ``#[AsCommand]`` to add a description, usage exampless, and +longer help text for the command:: #[AsCommand( name: 'app:create-user', - description: 'Creates a new user.', // the command description shown when running "php bin/console list" - help: 'This command allows you to create a user...', // the command help shown when running the command with the "--help" option - usages: ['app:create-user alice'], + // this short description is shown when running "php bin/console list" + description: 'Creates a new user.', + // this is shown when running the command with the "--help" option + help: 'This command allows you to create a user...', + // this allows you to show one or more usage examples (no need to add the command name) + usages: ['bob', 'alice --as-admin'], )] class CreateUserCommand { @@ -165,7 +169,8 @@ You can also use ``#[AsCommand]`` to add a description, usages, and longer help .. versionadded:: 7.4 - The ability to add usages via a ``$usages`` property was introduced in Symfony 7.4. + The feature to define usage examples in the ``#[AsCommand]`` attribute was + introduced in Symfony 7.4. Additionally, you can extend the :class:`Symfony\\Component\\Console\\Command\\Command` class to leverage advanced features like lifecycle hooks (e.g. :method:`Symfony\\Component\\Console\\Command\\Command::initialize` and From d252713c7edd3ee610cf41c952ca155fcd6f5b64 Mon Sep 17 00:00:00 2001 From: Oskar Stark Date: Tue, 1 Jul 2025 10:04:59 +0200 Subject: [PATCH 045/122] [Messenger] Document the `--exclude-receivers` option for `messenger:consume` command --- messenger.rst | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/messenger.rst b/messenger.rst index 18fc5e03cec..53b97311020 100644 --- a/messenger.rst +++ b/messenger.rst @@ -543,6 +543,23 @@ command with the ``--all`` option: The ``--all`` option was introduced in Symfony 7.1. +When using ``--all``, you can exclude specific receivers using the ``--exclude-receivers`` +option (shortcut ``-eq``). This is useful when you want to consume from all receivers +except certain ones (e.g., the failed transport): + +.. code-block:: terminal + + $ php bin/console messenger:consume --all --exclude-receivers=async_priority_low --exclude-receivers=failed + +.. versionadded:: 7.4 + + The ``--exclude-receivers`` option was introduced in Symfony 7.4. + +.. note:: + + The ``--exclude-receivers`` option can only be used together with ``--all``. + Also, you cannot exclude all receivers. + Messages that take a long time to process may be redelivered prematurely because some transports assume that an unacknowledged message is lost. To prevent this issue, use the ``--keepalive`` command option to specify an interval (in seconds; From fe02c3710d70a037a6b1215079e472069d0df7c7 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Tue, 2 Sep 2025 17:43:11 +0200 Subject: [PATCH 046/122] Remove some unnecessary and redundant content --- messenger.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/messenger.rst b/messenger.rst index dfeb9c94e38..24dbcd60adb 100644 --- a/messenger.rst +++ b/messenger.rst @@ -544,8 +544,7 @@ command with the ``--all`` option: The ``--all`` option was introduced in Symfony 7.1. When using ``--all``, you can exclude specific receivers using the ``--exclude-receivers`` -option (shortcut ``-eq``). This is useful when you want to consume from all receivers -except certain ones (e.g., the failed transport): +option (shortcut ``-eq``): .. code-block:: terminal From c11bfa5bdb6ceca685e941aa5a6423d227743a67 Mon Sep 17 00:00:00 2001 From: "Thibault G." Date: Mon, 8 Sep 2025 11:17:24 +0200 Subject: [PATCH 047/122] [Intl] Document `isValidInCountry()`, `isValidInAnyCountry()`, `forCountry()` --- components/intl.rst | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/components/intl.rst b/components/intl.rst index 4f70963fc07..217316f96ef 100644 --- a/components/intl.rst +++ b/components/intl.rst @@ -311,6 +311,43 @@ to catching the exception, you can also check if a given currency code is valid: $isValidCurrency = Currencies::exists($currencyCode); +If the currencies need to be filtered, there are also helpers to query and +validate currencies in relation to countries. These methods use ICU metadata +(``tender``, ``from`` and ``to`` dates) to determine whether a currency is +`legal tender`_ and/or active at a given point in time:: + + use Symfony\Component\Intl\Currencies; + + // Get the list of legal and active currencies for a country (defaults to "today") + $codes = Currencies::forCountry('FR'); + // ['EUR'] + + // Include non-legal currencies too, and check them at a given date + $codesAll = Currencies::forCountry( + 'ES', + legalTender: null, + active: true, + date: new \DateTimeImmutable('1982-01-01') + ); + // ['ESP', 'ESB'] + + // Validate a currency for a given country + $isOk = Currencies::isValidInCountry('CH', 'CHF'); + // true + + // Check if a currency is valid in *any* country on a specific date + $isGlobal = Currencies::isValidInAnyCountry( + 'USD', + legalTender: true, + active: true, + date: new \DateTimeImmutable('2005-01-01') + ); + // true + +Note that some currencies (especially non-legal-tender ones) do not have validity ranges defined. +In such cases, a ``RuntimeException`` will be thrown. +In addition, an ``InvalidArgumentException`` will be thrown if the specified currency is invalid. + .. _component-intl-timezones: Timezones @@ -429,6 +466,7 @@ Learn more .. _`ISO 3166-1 alpha-2`: https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 .. _`ISO 3166-1 alpha-3`: https://en.wikipedia.org/wiki/ISO_3166-1_alpha-3 .. _`ISO 3166-1 numeric`: https://en.wikipedia.org/wiki/ISO_3166-1_numeric +.. _`legal tender`: https://en.wikipedia.org/wiki/Legal_tender .. _`UTC/GMT time offsets`: https://en.wikipedia.org/wiki/List_of_UTC_time_offsets .. _`daylight saving time (DST)`: https://en.wikipedia.org/wiki/Daylight_saving_time .. _`ISO 639-1 alpha-2`: https://en.wikipedia.org/wiki/ISO_639-1 From e217d39bfba79b9e68d0852a02c3e023f07d35ac Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Tue, 9 Sep 2025 12:02:24 +0200 Subject: [PATCH 048/122] Minor tweaks --- components/intl.rst | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/components/intl.rst b/components/intl.rst index 217316f96ef..a0a1a245fa7 100644 --- a/components/intl.rst +++ b/components/intl.rst @@ -311,18 +311,22 @@ to catching the exception, you can also check if a given currency code is valid: $isValidCurrency = Currencies::exists($currencyCode); -If the currencies need to be filtered, there are also helpers to query and -validate currencies in relation to countries. These methods use ICU metadata -(``tender``, ``from`` and ``to`` dates) to determine whether a currency is -`legal tender`_ and/or active at a given point in time:: +By default, the previous currency methods return all currencies, including +those that are no longer in use. Symfony provides several methods to filter +currencies so you can work only with those that are actually valid and in use +for a given country. + +These methods use ICU metadata (``tender``, ``from`` and ``to`` dates) to +determine whether a currency is `legal tender`_ and/or active at a specific +point in time:: use Symfony\Component\Intl\Currencies; - // Get the list of legal and active currencies for a country (defaults to "today") + // get the list of today's legal and active currencies for a country $codes = Currencies::forCountry('FR'); // ['EUR'] - // Include non-legal currencies too, and check them at a given date + // include non-legal currencies too, and check them at a given date $codesAll = Currencies::forCountry( 'ES', legalTender: null, @@ -331,11 +335,11 @@ validate currencies in relation to countries. These methods use ICU metadata ); // ['ESP', 'ESB'] - // Validate a currency for a given country + // check if a currency is valid today for a country $isOk = Currencies::isValidInCountry('CH', 'CHF'); // true - // Check if a currency is valid in *any* country on a specific date + // check if a currency is valid in any country on a specific date $isGlobal = Currencies::isValidInAnyCountry( 'USD', legalTender: true, @@ -344,9 +348,14 @@ validate currencies in relation to countries. These methods use ICU metadata ); // true -Note that some currencies (especially non-legal-tender ones) do not have validity ranges defined. -In such cases, a ``RuntimeException`` will be thrown. -In addition, an ``InvalidArgumentException`` will be thrown if the specified currency is invalid. +Note that some currencies (especially non-legal-tender ones) do not have validity +ranges defined. In such cases, a ``RuntimeException`` will be thrown. In addition, +an ``InvalidArgumentException`` will be thrown if the specified currency is invalid. + +.. versionadded:: 7.4 + + The ``forCountry()``, ``isValidInCountry()`` and ``isValidInAnyCountry()`` + methods were introduced in Symfony 7.4. .. _component-intl-timezones: From f2a80ed16d3f3184be5d294d22fe99cd6bc06fe3 Mon Sep 17 00:00:00 2001 From: David ALLIX <517753+webda2l@users.noreply.github.com> Date: Wed, 10 Sep 2025 12:24:18 +0200 Subject: [PATCH 049/122] [Security] Add `tokenSource` parameter for CSRF token validation sources --- security/csrf.rst | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/security/csrf.rst b/security/csrf.rst index 8797b4e7553..c7a1b9a771b 100644 --- a/security/csrf.rst +++ b/security/csrf.rst @@ -321,6 +321,27 @@ array, the attribute is ignored for that request, and no CSRF validation occurs: // ... delete the object } +You can also choose where the CSRF token is read from using the ``tokenSource`` parameter +This is a bitfield allowing you to combine these sources: + +* ``IsCsrfTokenValid::SOURCE_PAYLOAD`` (default): request payload (POST body / json) +* ``IsCsrfTokenValid::SOURCE_QUERY``: query string +* ``IsCsrfTokenValid::SOURCE_HEADER``: request headers + +Example:: + + #[IsCsrfTokenValid( + 'delete-item', + tokenKey: 'token', + tokenSource: IsCsrfTokenValid::SOURCE_PAYLOAD | IsCsrfTokenValid::SOURCE_QUERY + )] + public function delete(Post $post): Response + { + // ... delete the object + } + +The token will be checked in each selected source, and validation fails if none match. + .. versionadded:: 7.1 The :class:`Symfony\\Component\\Security\\Http\\Attribute\\IsCsrfTokenValid` @@ -330,6 +351,10 @@ array, the attribute is ignored for that request, and no CSRF validation occurs: The ``methods`` parameter was introduced in Symfony 7.3. +.. versionadded:: 7.4 + + The ``tokenSource`` parameter was introduced in Symfony 7.4. + CSRF Tokens and Compression Side-Channel Attacks ------------------------------------------------ From e6b27d16252572cb6d65567a47e98f5dd0d157f6 Mon Sep 17 00:00:00 2001 From: nathanpage Date: Wed, 7 May 2025 11:46:44 +1000 Subject: [PATCH 050/122] [Lock] Add DynamoDbStore --- components/lock.rst | 28 ++++++++++++++++++++++++++++ lock.rst | 4 ++++ 2 files changed, 32 insertions(+) diff --git a/components/lock.rst b/components/lock.rst index e9fe61ecd1a..818750487f1 100644 --- a/components/lock.rst +++ b/components/lock.rst @@ -399,6 +399,7 @@ Store Scope Blocking Ex :ref:`RedisStore ` remote no yes yes yes :ref:`SemaphoreStore ` local yes no no no :ref:`ZookeeperStore ` remote no no no no +:ref:`DynamoDbStore ` remote no yes no yes ========================================================== ====== ======== ======== ======= ============= .. tip:: @@ -703,6 +704,32 @@ PHP process is terminated:: Zookeeper does not require a TTL as the nodes used for locking are ephemeral and die when the PHP process is terminated. +.. _lock-store-dynamodb: + +DynamoDbStore +~~~~~~~~~~~~~ + +The DynamoDbStore saves locks on a Amazon DynamoDB table. Install it by running: + +.. code-block:: terminal + + $ composer require symfony/amazon-dynamodb-lock + +It requires a `DynamoDbClient`_ instance or a `Data Source Name (DSN)`_. +This store does not support blocking, and expects a TTL to avoid stalled locks:: + + use Symfony\Component\Lock\Bridge\DynamoDb\Store\DynamoDbStore; + + // a DynamoDbClient instance or DSN + $dynamoDbClientOrDSN = 'dynamodb://default/lock'; + $store = new DynamoDbStore($dynamoDbClientOrDSN); + +The table where values are stored is created automatically on the first call to +the :method:`Symfony\\Component\\Lock\\Bridge\\DynamoDb\\DynamoDbStore::save` method. +You can also create this table explicitly by calling the +:method:`Symfony\\Component\\Lock\\Bridge\\DynamoDb\\DynamoDbStore::createTable` method in +your code. + Reliability ----------- @@ -1054,3 +1081,4 @@ are still running. .. _`readers-writer lock`: https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock .. _`priority policy`: https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock#Priority_policies .. _`PCNTL`: https://www.php.net/manual/book.pcntl.php +.. _`DynamoDbClient`: https://async-aws.com/clients/dynamodb.html diff --git a/lock.rst b/lock.rst index 3b9f1692df4..b4a5c83b700 100644 --- a/lock.rst +++ b/lock.rst @@ -61,6 +61,7 @@ this behavior by using the ``lock`` key like: lock: 'sqlsrv:server=127.0.0.1;Database=app' lock: 'oci:host=127.0.0.1;dbname=app' lock: 'mongodb://127.0.0.1/app?collection=lock' + lock: 'dynamodb://127.0.0.1/lock' lock: '%env(LOCK_DSN)%' # using an existing service lock: 'snc_redis.default' @@ -119,6 +120,8 @@ this behavior by using the ``lock`` key like: mongodb://127.0.0.1/app?collection=lock + dynamodb://127.0.0.1/lock + %env(LOCK_DSN)% @@ -157,6 +160,7 @@ this behavior by using the ``lock`` key like: ->resource('default', ['sqlsrv:server=127.0.0.1;Database=app']) ->resource('default', ['oci:host=127.0.0.1;dbname=app']) ->resource('default', ['mongodb://127.0.0.1/app?collection=lock']) + ->resource('default', ['dynamodb://127.0.0.1/lock']) ->resource('default', [env('LOCK_DSN')]) // using an existing service ->resource('default', ['snc_redis.default']) From 268f6938a21fbdd347f94abfe0a5ee7753db0982 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Mon, 15 Sep 2025 08:05:23 +0200 Subject: [PATCH 051/122] Add the versionadded directive --- components/lock.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/components/lock.rst b/components/lock.rst index 818750487f1..656a2472749 100644 --- a/components/lock.rst +++ b/components/lock.rst @@ -413,6 +413,10 @@ Store Scope Blocking Ex The :class:`Symfony\\Component\\Lock\\Store\\NullStore` was introduced in Symfony 7.2. +.. versionadded:: 7.4 + + The ``DynamoDbStore`` was introduced in Symfony 7.4. + .. _lock-store-flock: FlockStore From 34635568d9212e5e5f8a976f5ff4ae3153a9e2e3 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Mon, 15 Sep 2025 08:40:48 +0200 Subject: [PATCH 052/122] fix package name --- components/lock.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/lock.rst b/components/lock.rst index 656a2472749..51a065cdab1 100644 --- a/components/lock.rst +++ b/components/lock.rst @@ -717,7 +717,7 @@ The DynamoDbStore saves locks on a Amazon DynamoDB table. Install it by running: .. code-block:: terminal - $ composer require symfony/amazon-dynamodb-lock + $ composer require symfony/amazon-dynamo-db-lock It requires a `DynamoDbClient`_ instance or a `Data Source Name (DSN)`_. This store does not support blocking, and expects a TTL to avoid stalled locks:: From 004620ed7bbcc194c615fc6594b477b727735105 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Mon, 15 Sep 2025 11:45:53 +0200 Subject: [PATCH 053/122] Minor tweaks --- security/csrf.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/security/csrf.rst b/security/csrf.rst index c7a1b9a771b..8c7f62112fc 100644 --- a/security/csrf.rst +++ b/security/csrf.rst @@ -321,12 +321,12 @@ array, the attribute is ignored for that request, and no CSRF validation occurs: // ... delete the object } -You can also choose where the CSRF token is read from using the ``tokenSource`` parameter -This is a bitfield allowing you to combine these sources: +You can also choose where the CSRF token is read from using the ``tokenSource`` +parameter. This is a bitfield that allows you to combine different sources: * ``IsCsrfTokenValid::SOURCE_PAYLOAD`` (default): request payload (POST body / json) * ``IsCsrfTokenValid::SOURCE_QUERY``: query string -* ``IsCsrfTokenValid::SOURCE_HEADER``: request headers +* ``IsCsrfTokenValid::SOURCE_HEADER``: request header Example:: @@ -340,7 +340,7 @@ Example:: // ... delete the object } -The token will be checked in each selected source, and validation fails if none match. +The token is checked against each selected source, and validation fails if none match. .. versionadded:: 7.1 From 79ab1e92543b76a2bc896e7c2f457554640a5141 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Mon, 15 Sep 2025 12:09:32 +0200 Subject: [PATCH 054/122] [Config] Replace fixXmlConfig() by a new argument of arrayNode() --- components/config/definition.rst | 41 ++++++++++++-------------------- 1 file changed, 15 insertions(+), 26 deletions(-) diff --git a/components/config/definition.rst b/components/config/definition.rst index 2b1841bc24a..35ae35c468f 100644 --- a/components/config/definition.rst +++ b/components/config/definition.rst @@ -281,9 +281,9 @@ Before defining the children of an array node, you can provide options like: A basic prototyped array configuration can be defined as follows:: $node - ->fixXmlConfig('driver') ->children() - ->arrayNode('drivers') + // the arguments are the plural and singular variants of the option name + ->arrayNode('drivers', 'driver') ->scalarPrototype()->end() ->end() ->end() @@ -312,9 +312,8 @@ The processed configuration is:: A more complex example would be to define a prototyped array with children:: $node - ->fixXmlConfig('connection') ->children() - ->arrayNode('connections') + ->arrayNode('connections', 'connection') ->arrayPrototype() ->children() ->scalarNode('table')->end() @@ -385,9 +384,8 @@ the Symfony Config component treats arrays as lists by default. In order to maintain the array keys use the ``useAttributeAsKey()`` method:: $node - ->fixXmlConfig('connection') ->children() - ->arrayNode('connections') + ->arrayNode('connections', 'connection') ->useAttributeAsKey('name') ->arrayPrototype() ->children() @@ -734,14 +732,14 @@ normalization would make both of these ``auto_connect``. ``foo-bar_moo`` or if it already exists. Another difference between YAML and XML is in the way arrays of values may -be represented. In YAML you may have: +be represented. In YAML you may have an option called ``extensions`` (in plural): .. code-block:: yaml twig: extensions: ['twig.extension.foo', 'twig.extension.bar'] -and in XML: +and in XML you have a list of options called ``extension`` (in singular): .. code-block:: xml @@ -750,33 +748,24 @@ and in XML: twig.extension.bar -This difference can be removed in normalization by pluralizing the key used -in XML. You can specify that you want a key to be pluralized in this way -with ``fixXmlConfig()``:: +This difference can be removed in normalization by defining the singular variant +of the option name using the second argument of the ``arrayNode()`` method:: $rootNode - ->fixXmlConfig('extension') ->children() - ->arrayNode('extensions') + ->arrayNode('extensions', 'extension') ->scalarPrototype()->end() ->end() ->end() ; -If it is an irregular pluralization you can specify the plural to use as -a second argument:: +.. versionadded:: 7.4 - $rootNode - ->fixXmlConfig('child', 'children') - ->children() - ->arrayNode('children') - // ... - ->end() - ->end() - ; + The second argument of ``arrayNode()`` was introduced in Symfony 7.4. In prior + Symfony versions, you had to define the singular variant using the ``fixXmlConfig()`` + method on the root node (``$rootNode->fixXmlConfig('extension')``). -As well as fixing this, ``fixXmlConfig()`` ensures that single XML elements -are still turned into an array. So you may have: +This ensures that single XML elements are still turned into an array. So you may have: .. code-block:: xml @@ -791,7 +780,7 @@ and sometimes only: By default, ``connection`` would be an array in the first case and a string in the second, making it difficult to validate. You can ensure it is always -an array with ``fixXmlConfig()``. +an array with the second argument of ``arrayNode()``. You can further control the normalization process if you need to. For example, you may want to allow a string to be set and used as a particular key or From 49d73c02937a6a5a40d57bd6c8e53e9dfdc4c6fe Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Mon, 15 Sep 2025 13:21:39 +0200 Subject: [PATCH 055/122] [DependencyInjection] Allow multiple #[AsDecorator] attributes --- service_container/service_decoration.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/service_container/service_decoration.rst b/service_container/service_decoration.rst index e2cadbb0a4b..b04475f2982 100644 --- a/service_container/service_decoration.rst +++ b/service_container/service_decoration.rst @@ -123,6 +123,16 @@ but keeps a reference of the old one as ``.inner``: ->decorate(Mailer::class); }; +.. tip:: + + You can apply multiple ``#[AsDecorator]`` attributes to the same class to + decorate multiple services with it. + + .. versionadded:: 7.4 + + The feature to allow multiple ``#[AsDecorator]`` attributes was introduced + in Symfony 7.4. + The ``decorates`` option tells the container that the ``App\DecoratingMailer`` service replaces the ``App\Mailer`` service. If you're using the :ref:`default services.yaml configuration `, From b26d1472307f2a2b6d2268d6abfe2f40f986340e Mon Sep 17 00:00:00 2001 From: Oskar Stark Date: Mon, 15 Sep 2025 14:04:55 +0200 Subject: [PATCH 056/122] minor --- service_container/service_decoration.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/service_container/service_decoration.rst b/service_container/service_decoration.rst index b04475f2982..478691f4b3c 100644 --- a/service_container/service_decoration.rst +++ b/service_container/service_decoration.rst @@ -130,8 +130,8 @@ but keeps a reference of the old one as ``.inner``: .. versionadded:: 7.4 - The feature to allow multiple ``#[AsDecorator]`` attributes was introduced - in Symfony 7.4. + The possibility to allow multiple ``#[AsDecorator]`` attributes was + introduced in Symfony 7.4. The ``decorates`` option tells the container that the ``App\DecoratingMailer`` service replaces the ``App\Mailer`` service. If you're using the From 570d633b11726bb0c95ed46f32e1de1abe950e80 Mon Sep 17 00:00:00 2001 From: Tugdual Saunier Date: Tue, 16 Sep 2025 14:45:59 +0200 Subject: [PATCH 057/122] [Workflow] Document backed enum support --- workflow.rst | 148 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) diff --git a/workflow.rst b/workflow.rst index 54a1b06313e..c0c05875ee1 100644 --- a/workflow.rst +++ b/workflow.rst @@ -294,6 +294,154 @@ what actions are allowed on a blog post:: // See a specific available transition for the post in the current state $transition = $workflow->getEnabledTransition($post, 'publish'); +.. tip:: + + In some specific cases, using PHP enums as places in your workflows might + make sense and one can use them seamlessly with the Workflow component if + they uses backed enumerations. + + .. versionadded:: 7.4 + + The support for PHP Backed enumerations as Workflow places was + introduced with Symfony 7.4. + + First, define your enum with backed values:: + + // src/Enumeration/BlogPostStatus.php + namespace App\Enumeration; + + enum BlogPostStatus: string + { + case Draft = 'draft'; + case Reviewed = 'reviewed'; + case Published = 'published'; + case Rejected = 'rejected'; + } + + Then configure the workflow using the enum cases as places, initial + marking, and transitions: + + .. configuration-block:: + + .. code-block:: yaml + + # config/packages/workflow.yaml + framework: + workflows: + blog_publishing: + type: 'workflow' + marking_store: + type: 'method' + property: 'status' + supports: + - App\Entity\BlogPost + initial_marking: !php/enum App\Enumeration\BlogPostStatus::Draft + places: !php/enum App\Enumeration\BlogPostStatus + transitions: + to_review: + from: !php/enum App\Enumeration\BlogPostStatus::Draft + to: !php/enum App\Enumeration\BlogPostStatus::Reviewed + publish: + from: !php/enum App\Enumeration\BlogPostStatus::Reviewed + to: !php/enum App\Enumeration\BlogPostStatus::Published + reject: + from: !php/enum App\Enumeration\BlogPostStatus::Reviewed + to: !php/enum App\Enumeration\BlogPostStatus::Rejected + + .. code-block:: xml + + + + + + + + + + status + + App\Entity\BlogPost + draft + + + draft + reviewed + + + reviewed + published + + + reviewed + rejected + + + + + + .. code-block:: php + + // config/packages/workflow.php + use App\Entity\BlogPost; + use App\Enumeration\BlogPostStatus; + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework): void { + $blogPublishing = $framework->workflows()->workflows('blog_publishing'); + $blogPublishing + ->type('workflow') + ->supports([BlogPost::class]) + ->initialMarking([BlogPostStatus::Draft]); + + $blogPublishing->markingStore() + ->type('method') + ->property('status'); + + $blogPublishing->places(BlogPostStatus::cases()); + + $blogPublishing->transition() + ->name('to_review') + ->from(BlogPostStatus::Draft) + ->to([BlogPostStatus::Reviewed]); + + $blogPublishing->transition() + ->name('publish') + ->from([BlogPostStatus::Reviewed]) + ->to([BlogPostStatus::Published]); + + $blogPublishing->transition() + ->name('reject') + ->from([BlogPostStatus::Reviewed]) + ->to([BlogPostStatus::Rejected]); + }; + + The component will now transparently cast the enum to its backing value + when needed and vice-versa when working with your objects:: + + // src/Entity/BlogPost.php + namespace App\Entity; + + class BlogPost + { + private BlogPostStatus $status; + + public function getStatus(): BlogPostStatus + { + return $this->status; + } + + public function setStatus(BlogPostStatus $status): void + { + $this->status = $status; + } + } + Using a multiple state marking store ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From c8d14ef268f921699232ff83a76977af52957a3c Mon Sep 17 00:00:00 2001 From: Santiago San Martin Date: Tue, 3 Jun 2025 23:04:40 -0300 Subject: [PATCH 058/122] [HttpFoundation] Add documentation for #[IsSignatureValid] attribute with usage examples and options --- routing.rst | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/routing.rst b/routing.rst index f1040eda7a1..bf016faad02 100644 --- a/routing.rst +++ b/routing.rst @@ -3151,6 +3151,71 @@ If you need to know the reason why a signed URI is invalid, you can use the Support for :doc:`Symfony Clock ` in ``UriSigner`` was introduced in Symfony 7.3. +Another way to validate incoming requests is to use the ``#[IsSignatureValid]`` attribute. + +In the following example, all incoming requests to this controller action will be verified for +a valid signature. If the signature is missing or invalid, +a ``SignedUriException`` will be thrown:: + + // src/Controller/SomeController.php + // ... + + use App\Security\Attribute\IsSignatureValid; + + #[IsSignatureValid] + public function someAction(): Response + { + // ... + } + +To restrict signature validation to specific HTTP methods, +use the ``methods`` argument. This can be a string or an array of methods:: + + // Only validate POST requests + #[IsSignatureValid(methods: 'POST')] + public function createItem(): Response + { + // ... + } + + // Validate both POST and PUT requests + #[IsSignatureValid(methods: ['POST', 'PUT'])] + public function updateItem(): Response + { + // ... + } + +You can also apply ``#[IsSignatureValid]`` at the controller class level. +This way, all actions within the controller will automatically +be protected by signature validation:: + + // src/Controller/SecureController.php + // ... + + use App\Security\Attribute\IsSignatureValid; + + #[IsSignatureValid] + class SecureController extends AbstractController + { + public function index(): Response + { + // ... + } + + public function submit(): Response + { + // ... + } + } + + +This attribute provides a declarative way to enforce request signature validation directly +at the controller level, helping to keep your security logic consistent and maintainable. + +.. versionadded:: 7.4 + + The ``#[IsSignatureValid]`` attribute was introduced in Symfony 7.4. + Troubleshooting --------------- From cea79c8bad453fdacb67e91c14bfed7253c6fd93 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Wed, 17 Sep 2025 12:56:05 +0200 Subject: [PATCH 059/122] [HtmlSanitizer] Use the native HTML5 parser when using PHP 8.4+ --- html_sanitizer.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/html_sanitizer.rst b/html_sanitizer.rst index 38d7664ccf7..173c1937ebd 100644 --- a/html_sanitizer.rst +++ b/html_sanitizer.rst @@ -29,6 +29,13 @@ You can install the HTML Sanitizer component with: $ composer require symfony/html-sanitizer +.. versionadded:: 7.4 + + Starting in Symfony 7.4, applications running on PHP 8.4 or higher will use + the native HTML5 parser provided by PHP. All other applications will continue + to use the third-party ``masterminds/html5`` parser, which is installed + automatically when installing the HTML Sanitizer package. + Basic Usage ----------- From cb29685c6955773ca4ea43b6db1e0986e052eeb9 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Fri, 19 Sep 2025 16:19:24 +0200 Subject: [PATCH 060/122] [FrameworkBundle] Simplify usage of the Target attribute --- lock.rst | 9 +++++++-- rate_limiter.rst | 9 +++++++-- reference/configuration/framework.rst | 9 +++++++-- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/lock.rst b/lock.rst index 6cdeaecc673..e84925b27a3 100644 --- a/lock.rst +++ b/lock.rst @@ -319,7 +319,7 @@ For example, to inject the ``invoice`` package defined earlier:: When :ref:`dealing with multiple implementations of the same type ` the ``#[Target]`` attribute helps you select which one to inject. Symfony creates -a target called "asset package name" + ``.lock.factory`` suffix. +a target with the same name as the lock. For example, to select the ``invoice`` lock defined earlier:: @@ -329,8 +329,13 @@ For example, to select the ``invoice`` lock defined earlier:: class SomeService { public function __construct( - #[Target('invoice.lock.factory')] private LockFactory $lockFactory + #[Target('invoice')] private LockFactory $lockFactory ): void { // ... } } + +.. versionadded:: 7.4 + + Before Symfony 7.4, the target name had to include the ``.lock.factory`` + suffix (e.g. ``#[Target('invoice.lock.factory')]``). diff --git a/rate_limiter.rst b/rate_limiter.rst index cddab4a1c2a..4e8de44be0f 100644 --- a/rate_limiter.rst +++ b/rate_limiter.rst @@ -262,7 +262,7 @@ argument named ``$anonymousApiLimiter``:: When :ref:`dealing with multiple implementations of the same type ` the ``#[Target]`` attribute helps you select which one to inject. Symfony creates -a target called "rate limiter name" + ``.limiter`` suffix. +a target with the same name as the rate limiter. For example, to select the ``anonymous_api`` limiter defined earlier, use ``anonymous_api.limiter`` as the target:: @@ -273,7 +273,7 @@ For example, to select the ``anonymous_api`` limiter defined earlier, use class ApiController extends AbstractController { public function index( - #[Target('anonymous_api.limiter')] RateLimiterFactoryInterface $rateLimiter + #[Target('anonymous_api')] RateLimiterFactoryInterface $rateLimiter ): Response { // ... @@ -286,6 +286,11 @@ For example, to select the ``anonymous_api`` limiter defined earlier, use added and should now be used for autowiring instead of :class:`Symfony\\Component\\RateLimiter\\RateLimiterFactory`. +.. versionadded:: 7.4 + + Before Symfony 7.4, the target name had to include the ``.limiter`` + suffix (e.g. ``#[Target('anonymous_api.limiter')]``). + Using the Rate Limiter Service ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/reference/configuration/framework.rst b/reference/configuration/framework.rst index f700dc88f76..855b2bd7b07 100644 --- a/reference/configuration/framework.rst +++ b/reference/configuration/framework.rst @@ -301,7 +301,7 @@ to inject the ``foo_package`` package defined earlier:: When :ref:`dealing with multiple implementations of the same type ` the ``#[Target]`` attribute helps you select which one to inject. Symfony creates -a target called "asset package name" + ``.package`` suffix. +a target with the same name as the asset package. For example, to select the ``foo_package`` package defined earlier:: @@ -311,12 +311,17 @@ For example, to select the ``foo_package`` package defined earlier:: class SomeService { public function __construct( - #[Target('foo_package.package')] private PackageInterface $package + #[Target('foo_package')] private PackageInterface $package ): void { // ... } } +.. versionadded:: 7.4 + + Before Symfony 7.4, the target name had to include the ``.package`` + suffix (e.g. ``#[Target('foo_package.package')]``). + .. _reference-framework-assets-packages: packages From d13e9f1f439f7fbf5eef7a123dbbe9575cc275bc Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Mon, 15 Sep 2025 12:43:18 +0200 Subject: [PATCH 061/122] [Security] Add `access_decision()` and `access_decision_for_user()` --- security.rst | 64 +++++++++++++++++++++++++++++++++++++++++++++ security/voters.rst | 2 ++ 2 files changed, 66 insertions(+) diff --git a/security.rst b/security.rst index 50e33402188..0c3df87de10 100644 --- a/security.rst +++ b/security.rst @@ -2591,6 +2591,34 @@ the built-in ``is_granted_for_user()`` helper function: Delete {% endif %} +Symfony also provides the ``access_decision()`` and ``access_decision_for_user()`` +Twig functions to check authorization and to retrieve the reasons for denying +permission in :ref:`your custom security voters `: + +.. code-block:: html+twig + + {% set voter_decision = access_decision('post_edit', post) %} + {% if voter_decision.isGranted() %} + {# ... #} + {% else %} + {# before showing voter messages to end users, make sure it's safe to do so #} +

{{ voter_decision.message }}

+ {% endif %} + + {% set voter_decision = access_decision('post_edit', post, anotherUser) %} + {% if voter_decision.isGranted() %} + {# ... #} + {% else %} +

The {{ anotherUser.name }} user doesn't have sufficient permission:

+ {# before showing voter messages to end users, make sure it's safe to do so #} +

{{ voter_decision.message }}

+ {% endif %} + +.. versionadded:: 7.4 + + The ``access_decision()`` and ``access_decision_for_user()`` Twig functions + were introduced in Symfony 7.4. + .. _security-isgrantedforuser: Securing other Services @@ -2642,6 +2670,42 @@ want to include extra details only for users that have a ``ROLE_SALES_ADMIN`` ro The :method:`Symfony\\Bundle\\SecurityBundle\\Security::isGrantedForUser` method was introduced in Symfony 7.3. +You can also use the ``getAccessDecision()`` and ``getAccessDecisionForUser()`` +methods to check authorization and get to retrieve the reasons for denying +permission in :ref:`your custom security voters `:: + + // src/SalesReport/SalesReportManager.php + + // ... + use Symfony\Bundle\SecurityBundle\Security; + + class SalesReportManager + { + public function __construct( + private Security $security, + ) { + } + + public function generateReport(): void + { + $voterDecision = $this->security->getAccessDecision('ROLE_SALES_ADMIN'); + if ($voterDecision->isGranted('ROLE_SALES_ADMIN')) { + // ... + } else { + // do something with $voterDecision->getMessage() + } + + // ... + } + + // ... + } + +.. versionadded:: 7.4 + + The ``getAccessDecision()`` and ``getAccessDecisionForUser()`` methods + were introduced in Symfony 7.4. + If you're using the :ref:`default services.yaml configuration `, Symfony will automatically pass the ``security.helper`` to your service thanks to autowiring and the ``Security`` type-hint. diff --git a/security/voters.rst b/security/voters.rst index e621263abb4..803715ea4dc 100644 --- a/security/voters.rst +++ b/security/voters.rst @@ -124,6 +124,8 @@ calls out to the "voter" system. Right now, no voters will vote on whether or no the user can "view" or "edit" a ``Post``. But you can create your *own* voter that decides this using whatever logic you want. +.. _creating-the-custom-voter: + Creating the custom Voter ------------------------- From c6d70aa5f584c7af3b1aea912f46c7772d88afb1 Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Fri, 12 Sep 2025 12:10:06 +0200 Subject: [PATCH 062/122] [Console] Document timeout functionality for `QuestionHelper` --- components/console/helpers/questionhelper.rst | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/components/console/helpers/questionhelper.rst b/components/console/helpers/questionhelper.rst index a0126ee5a71..e576258d722 100644 --- a/components/console/helpers/questionhelper.rst +++ b/components/console/helpers/questionhelper.rst @@ -324,6 +324,63 @@ the response to a question should allow multiline answers by passing ``true`` to Multiline questions stop reading user input after receiving an end-of-transmission control character (``Ctrl-D`` on Unix systems or ``Ctrl-Z`` on Windows). +Setting a Timeout for User Input +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 7.4 + + The timeout functionality for questions was introduced in Symfony 7.4. + +You can set a maximum time limit for user input by using the +:method:`Symfony\\Component\\Console\\Question\\Question::setTimeout` method. +If the user doesn't respond within the specified timeout, a +:class:`Symfony\\Component\\Console\\Exception\\MissingInputException` will be thrown:: + + use Symfony\Component\Console\Exception\MissingInputException; + use Symfony\Component\Console\Question\Question; + + // ... + public function __invoke(InputInterface $input, OutputInterface $output): int + { + // ... + $helper = new QuestionHelper(); + + $question = new Question('Please enter your answer'); + $question->setTimeout(30); // 30 seconds timeout + + try { + $answer = $helper->ask($input, $output, $question); + // ... do something with the answer + } catch (MissingInputException $exception) { + $output->writeln('No input received within timeout period.'); + // ... handle timeout + } + + return Command::SUCCESS; + } + +This is particularly useful when you have interactive questions inside database +transactions or other time-sensitive operations where hanging indefinitely +could cause problems. + +.. note:: + + The timeout only applies to interactive input streams. For non-interactive + streams (like pipes or files), the timeout setting is ignored and the + question behaves normally. + +You can also use timeouts with other question types such as +:class:`Symfony\\Component\\Console\\Question\\ConfirmationQuestion` and +:class:`Symfony\\Component\\Console\\Question\\ChoiceQuestion`:: + + use Symfony\Component\Console\Question\ConfirmationQuestion; + + // ... + $question = new ConfirmationQuestion('Do you want to continue?', false); + $question->setTimeout(10); // 10 seconds timeout + + $continue = $helper->ask($input, $output, $question); + Hiding the User's Response ~~~~~~~~~~~~~~~~~~~~~~~~~~ From f7101711a8bef877ca85a4f121df3c182892b24d Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Tue, 23 Sep 2025 10:33:52 +0200 Subject: [PATCH 063/122] Tweaks --- components/console/helpers/questionhelper.rst | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/components/console/helpers/questionhelper.rst b/components/console/helpers/questionhelper.rst index e576258d722..64413b092b3 100644 --- a/components/console/helpers/questionhelper.rst +++ b/components/console/helpers/questionhelper.rst @@ -327,11 +327,11 @@ control character (``Ctrl-D`` on Unix systems or ``Ctrl-Z`` on Windows). Setting a Timeout for User Input ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. versionadded:: 7.4 - - The timeout functionality for questions was introduced in Symfony 7.4. +Sometimes, commands can hang if a user takes too long to respond. For example, +if interactive questions are used inside an open database transaction, a delayed +response could leave the transaction open for too long. -You can set a maximum time limit for user input by using the +You can prevent this by setting a maximum time limit for input using the :method:`Symfony\\Component\\Console\\Question\\Question::setTimeout` method. If the user doesn't respond within the specified timeout, a :class:`Symfony\\Component\\Console\\Exception\\MissingInputException` will be thrown:: @@ -346,7 +346,7 @@ If the user doesn't respond within the specified timeout, a $helper = new QuestionHelper(); $question = new Question('Please enter your answer'); - $question->setTimeout(30); // 30 seconds timeout + $question->setTimeout(30); // 30 seconds try { $answer = $helper->ask($input, $output, $question); @@ -359,15 +359,11 @@ If the user doesn't respond within the specified timeout, a return Command::SUCCESS; } -This is particularly useful when you have interactive questions inside database -transactions or other time-sensitive operations where hanging indefinitely -could cause problems. - .. note:: The timeout only applies to interactive input streams. For non-interactive - streams (like pipes or files), the timeout setting is ignored and the - question behaves normally. + streams (such as pipes or files), the timeout is ignored and the question + behaves normally. You can also use timeouts with other question types such as :class:`Symfony\\Component\\Console\\Question\\ConfirmationQuestion` and @@ -377,10 +373,14 @@ You can also use timeouts with other question types such as // ... $question = new ConfirmationQuestion('Do you want to continue?', false); - $question->setTimeout(10); // 10 seconds timeout + $question->setTimeout(10); // 10 seconds $continue = $helper->ask($input, $output, $question); +.. versionadded:: 7.4 + + The timeout functionality for questions was introduced in Symfony 7.4. + Hiding the User's Response ~~~~~~~~~~~~~~~~~~~~~~~~~~ From ef045c68ce0332f5cf4c780db7591585b3608871 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Tue, 23 Sep 2025 13:16:06 +0200 Subject: [PATCH 064/122] Tweaks --- workflow.rst | 260 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 161 insertions(+), 99 deletions(-) diff --git a/workflow.rst b/workflow.rst index c0c05875ee1..31883b93970 100644 --- a/workflow.rst +++ b/workflow.rst @@ -294,32 +294,155 @@ what actions are allowed on a blog post:: // See a specific available transition for the post in the current state $transition = $workflow->getEnabledTransition($post, 'publish'); -.. tip:: +Using Enums as Workflow Places +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When using a state machine, you can use PHP backend enums as places in your workflows: + +.. versionadded:: 7.4 + + The support for PHP backed enums as workflow places was introduced with Symfony 7.4. + +First, define your enum with backed values:: + + // src/Enumeration/BlogPostStatus.php + namespace App\Enumeration; + + enum BlogPostStatus: string + { + case Draft = 'draft'; + case Reviewed = 'reviewed'; + case Published = 'published'; + case Rejected = 'rejected'; + } - In some specific cases, using PHP enums as places in your workflows might - make sense and one can use them seamlessly with the Workflow component if - they uses backed enumerations. +Then configure the workflow using the enum cases as places, initial marking, +and transitions: + +.. configuration-block:: - .. versionadded:: 7.4 + .. code-block:: yaml + + # config/packages/workflow.yaml + framework: + workflows: + blog_publishing: + type: 'workflow' + marking_store: + type: 'method' + property: 'status' + supports: + - App\Entity\BlogPost + initial_marking: !php/enum App\Enumeration\BlogPostStatus::Draft + places: !php/enum App\Enumeration\BlogPostStatus + transitions: + to_review: + from: !php/enum App\Enumeration\BlogPostStatus::Draft + to: !php/enum App\Enumeration\BlogPostStatus::Reviewed + publish: + from: !php/enum App\Enumeration\BlogPostStatus::Reviewed + to: !php/enum App\Enumeration\BlogPostStatus::Published + reject: + from: !php/enum App\Enumeration\BlogPostStatus::Reviewed + to: !php/enum App\Enumeration\BlogPostStatus::Rejected - The support for PHP Backed enumerations as Workflow places was - introduced with Symfony 7.4. + .. code-block:: xml - First, define your enum with backed values:: + + + - // src/Enumeration/BlogPostStatus.php - namespace App\Enumeration; + + + + + status + + App\Entity\BlogPost + draft - enum BlogPostStatus: string + + draft + reviewed + + + reviewed + published + + + reviewed + rejected + + + + + + .. code-block:: php + + // config/packages/workflow.php + use App\Entity\BlogPost; + use App\Enumeration\BlogPostStatus; + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework): void { + $blogPublishing = $framework->workflows()->workflows('blog_publishing'); + $blogPublishing + ->type('workflow') + ->supports([BlogPost::class]) + ->initialMarking([BlogPostStatus::Draft]); + + $blogPublishing->markingStore() + ->type('method') + ->property('status'); + + $blogPublishing->places(BlogPostStatus::cases()); + + $blogPublishing->transition() + ->name('to_review') + ->from(BlogPostStatus::Draft) + ->to([BlogPostStatus::Reviewed]); + + $blogPublishing->transition() + ->name('publish') + ->from([BlogPostStatus::Reviewed]) + ->to([BlogPostStatus::Published]); + + $blogPublishing->transition() + ->name('reject') + ->from([BlogPostStatus::Reviewed]) + ->to([BlogPostStatus::Rejected]); + }; + +The component will now transparently cast the enum to its backing value +when needed and vice-versa when working with your objects:: + + // src/Entity/BlogPost.php + namespace App\Entity; + + class BlogPost + { + private BlogPostStatus $status; + + public function getStatus(): BlogPostStatus + { + return $this->status; + } + + public function setStatus(BlogPostStatus $status): void { - case Draft = 'draft'; - case Reviewed = 'reviewed'; - case Published = 'published'; - case Rejected = 'rejected'; + $this->status = $status; } + } + +.. tip:: - Then configure the workflow using the enum cases as places, initial - marking, and transitions: + You can also use `glob patterns`_ of PHP constants and enums to list the places: .. configuration-block:: @@ -328,25 +451,14 @@ what actions are allowed on a blog post:: # config/packages/workflow.yaml framework: workflows: - blog_publishing: - type: 'workflow' - marking_store: - type: 'method' - property: 'status' - supports: - - App\Entity\BlogPost - initial_marking: !php/enum App\Enumeration\BlogPostStatus::Draft - places: !php/enum App\Enumeration\BlogPostStatus - transitions: - to_review: - from: !php/enum App\Enumeration\BlogPostStatus::Draft - to: !php/enum App\Enumeration\BlogPostStatus::Reviewed - publish: - from: !php/enum App\Enumeration\BlogPostStatus::Reviewed - to: !php/enum App\Enumeration\BlogPostStatus::Published - reject: - from: !php/enum App\Enumeration\BlogPostStatus::Reviewed - to: !php/enum App\Enumeration\BlogPostStatus::Rejected + my_workflow_name: + # with constants: + places: 'App\Workflow\MyWorkflow::PLACE_*' + + # with enums: + places: !php/enum App\Workflow\Places + + # ... .. code-block:: xml @@ -361,26 +473,12 @@ what actions are allowed on a blog post:: https://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> - - - - status - - App\Entity\BlogPost - draft - - - draft - reviewed - - - reviewed - published - - - reviewed - rejected - + + places="App\Workflow\MyWorkflow::PLACE_*" + + places="App\Enumeration\BlogPostStatus::*"> + @@ -393,55 +491,17 @@ what actions are allowed on a blog post:: use Symfony\Config\FrameworkConfig; return static function (FrameworkConfig $framework): void { - $blogPublishing = $framework->workflows()->workflows('blog_publishing'); - $blogPublishing - ->type('workflow') - ->supports([BlogPost::class]) - ->initialMarking([BlogPostStatus::Draft]); + $blogPublishing = $framework->workflows()->workflows('my_workflow_name'); - $blogPublishing->markingStore() - ->type('method') - ->property('status'); + // with constants: + $blogPublishing->places('App\Workflow\MyWorkflow::PLACE_*'); + // with enums: $blogPublishing->places(BlogPostStatus::cases()); - $blogPublishing->transition() - ->name('to_review') - ->from(BlogPostStatus::Draft) - ->to([BlogPostStatus::Reviewed]); - - $blogPublishing->transition() - ->name('publish') - ->from([BlogPostStatus::Reviewed]) - ->to([BlogPostStatus::Published]); - - $blogPublishing->transition() - ->name('reject') - ->from([BlogPostStatus::Reviewed]) - ->to([BlogPostStatus::Rejected]); + // ... }; - The component will now transparently cast the enum to its backing value - when needed and vice-versa when working with your objects:: - - // src/Entity/BlogPost.php - namespace App\Entity; - - class BlogPost - { - private BlogPostStatus $status; - - public function getStatus(): BlogPostStatus - { - return $this->status; - } - - public function setStatus(BlogPostStatus $status): void - { - $this->status = $status; - } - } - Using a multiple state marking store ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1543,3 +1603,5 @@ Learn more /workflow/workflow-and-state-machine /workflow/dumping-workflows + +.. _`glob patterns`: https://php.net/glob From 49cb38bd83cad6d3097f3baae4c1489b520e9093 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Mon, 22 Sep 2025 17:51:11 +0200 Subject: [PATCH 065/122] [Validator] Add option to allow ANY protocol in Assert\Url constraint --- reference/constraints/Url.rst | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/reference/constraints/Url.rst b/reference/constraints/Url.rst index c3fac520f96..5a9b4cd0da8 100644 --- a/reference/constraints/Url.rst +++ b/reference/constraints/Url.rst @@ -237,6 +237,21 @@ the ``ftp://`` type URLs to be valid, redefine the ``protocols`` array, listing } } +The value of this option can also be an asterisk (``*``) to allow all protocols +or a regular expression:: + + // allows all protocols whose names are RFC 3986 compliant + // (e.g. 'https://', 'git+ssh://', 'file://', 'custom://') + protocols: ['*'] + + // regular expressions are also valid + protocols: ['https?', 'custom.*', 'my-app-.*'] + +.. versionadded:: 7.4 + + Support for ``*`` and regular expressions in the ``protocols`` option was + introduced in Symfony 7.4. + ``relativeProtocol`` ~~~~~~~~~~~~~~~~~~~~ From 83a9025f94381d1ca5a8749a6000f5c2ff33307c Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Wed, 24 Sep 2025 12:12:41 +0200 Subject: [PATCH 066/122] rework the usage of '*' for the protocols option --- reference/constraints/Url.rst | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/reference/constraints/Url.rst b/reference/constraints/Url.rst index 5a9b4cd0da8..eccee53e05a 100644 --- a/reference/constraints/Url.rst +++ b/reference/constraints/Url.rst @@ -165,7 +165,7 @@ Parameter Description ``protocols`` ~~~~~~~~~~~~~ -**type**: ``array`` **default**: ``['http', 'https']`` +**type**: ``array|string`` **default**: ``['http', 'https']`` The protocols considered to be valid for the URL. For example, if you also consider the ``ftp://`` type URLs to be valid, redefine the ``protocols`` array, listing @@ -237,20 +237,15 @@ the ``ftp://`` type URLs to be valid, redefine the ``protocols`` array, listing } } -The value of this option can also be an asterisk (``*``) to allow all protocols -or a regular expression:: +The value of this option can also be an asterisk (``*``) to allow all protocols:: // allows all protocols whose names are RFC 3986 compliant // (e.g. 'https://', 'git+ssh://', 'file://', 'custom://') - protocols: ['*'] - - // regular expressions are also valid - protocols: ['https?', 'custom.*', 'my-app-.*'] + protocols: '*' .. versionadded:: 7.4 - Support for ``*`` and regular expressions in the ``protocols`` option was - introduced in Symfony 7.4. + Support for ``*`` in the ``protocols`` option was introduced in Symfony 7.4. ``relativeProtocol`` ~~~~~~~~~~~~~~~~~~~~ From 96b92d358a7e06273bb5a2ceccd9a69d236793af Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Tue, 16 Sep 2025 09:22:36 +0200 Subject: [PATCH 067/122] [Validator] Add Video constraint for validating video files --- reference/constraints/File.rst | 2 + reference/constraints/Video.rst | 320 ++++++++++++++++++++++++++++++ reference/constraints/map.rst.inc | 1 + 3 files changed, 323 insertions(+) create mode 100644 reference/constraints/Video.rst diff --git a/reference/constraints/File.rst b/reference/constraints/File.rst index 62efa6cc08e..c773237af41 100644 --- a/reference/constraints/File.rst +++ b/reference/constraints/File.rst @@ -237,6 +237,8 @@ Parameter Description ``{{ suffix }}`` Suffix for the used file size unit (see above) ================ ============================================================= +.. _reference-constraints-file-mime-types: + ``mimeTypes`` ~~~~~~~~~~~~~ diff --git a/reference/constraints/Video.rst b/reference/constraints/Video.rst new file mode 100644 index 00000000000..644060d4c6c --- /dev/null +++ b/reference/constraints/Video.rst @@ -0,0 +1,320 @@ +Video +===== + +Validates that a file is a valid video that meets certain constraints (width, height, +aspect ratio, etc.). It extends the :doc:`File ` constraint +and adds video-specific validation options. + +.. versionadded:: 7.4 + + The ``Video`` constraint was introduced in Symfony 7.4. + +========== =================================================================== +Applies to :ref:`property or method ` +Class :class:`Symfony\\Component\\Validator\\Constraints\\Video` +Validator :class:`Symfony\\Component\\Validator\\Constraints\\VideoValidator` +========== =================================================================== + +Basic Usage +----------- + +This constraint is most commonly used on a property that stores a video file as a +:class:`Symfony\\Component\\HttpFoundation\\File\\File` object. For example, suppose +you're creating a video platform and want to validate uploaded video files:: + + // src/Entity/VideoContent.php + namespace App\Entity; + + use Symfony\Component\HttpFoundation\File\File; + use Symfony\Component\Validator\Constraints as Assert; + + class VideoContent + { + #[Assert\Video( + maxWidth: 1920, + maxHeight: 1080, + maxSize: '100M', + mimeTypes: ['video/mp4', 'video/webm'], + )] + private File $videoFile; + } + +.. warning:: + + This constraint requires the ``ffprobe`` binary to be installed and accessible + on your system. The constraint uses ``ffprobe`` to extract video metadata. + You can install it as part of the `FFmpeg package`_. + +Options +------- + +``allowedCodecs`` +~~~~~~~~~~~~~~~~~ + +**type**: ``array`` **default**: ``['h264', 'hevc', 'h265', 'vp9', 'av1', 'mpeg4', 'mpeg2video']`` + +Defines which `video codecs`_ are allowed. If the video uses a codec not in this +list, validation will fail:: + + // src/Entity/VideoContent.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class VideoContent + { + #[Assert\Video( + allowedCodecs: ['h264', 'hevc'], + )] + private $videoFile; + } + +``allowedContainers`` +~~~~~~~~~~~~~~~~~~~~~ + +**type**: ``array`` **default**: ``['mp4', 'mov', 'mkv', 'webm', 'avi']`` + +Defines which `video container formats`_ are allowed:: + + // src/Entity/VideoContent.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class VideoContent + { + #[Assert\Video( + allowedContainers: ['mp4', 'webm'], + )] + private $videoFile; + } + +``allowLandscape`` +~~~~~~~~~~~~~~~~~~ + +**type**: ``boolean`` **default**: ``true`` + +If set to ``false``, the video cannot be landscape oriented (i.e., the width +cannot be greater than the height). + +``allowPortrait`` +~~~~~~~~~~~~~~~~~ + +**type**: ``boolean`` **default**: ``true`` + +If set to ``false``, the video cannot be portrait oriented (i.e., the width +cannot be less than the height). + +``allowSquare`` +~~~~~~~~~~~~~~~ + +**type**: ``boolean`` **default**: ``true`` + +If set to ``false``, the video cannot be a square (i.e., the width and height +cannot be equal). + +``maxHeight`` +~~~~~~~~~~~~~ + +**type**: ``integer`` + +If set, the height of the video file must be less than or equal to this value +in pixels. + +``maxRatio`` +~~~~~~~~~~~~ + +**type**: ``float`` + +If set, the aspect ratio (``width / height``) of the video file must be less +than or equal to this value. For example, a square video has a ratio of 1, +a 16:9 video has a ratio of 1.78, and a 4:3 video has a ratio of 1.33. + +``maxWidth`` +~~~~~~~~~~~~ + +**type**: ``integer`` + +If set, the width of the video file must be less than or equal to this value +in pixels. + +``minHeight`` +~~~~~~~~~~~~~ + +**type**: ``integer`` + +If set, the height of the video file must be greater than or equal to this +value in pixels. + +``minPixels`` +~~~~~~~~~~~~~ + +**type**: ``float`` + +If set, the total number of pixels (``width * height``) of the video file must be greater +than or equal to this value. + +``maxPixels`` +~~~~~~~~~~~~~ + +**type**: ``float`` + +If set, the total number of pixels (``width * height``) of the video file must be less +than or equal to this value. + +``minRatio`` +~~~~~~~~~~~~ + +**type**: ``float`` + +If set, the aspect ratio (``width / height``) of the video file must be greater +than or equal to this value. + +``minWidth`` +~~~~~~~~~~~~ + +**type**: ``integer`` + +If set, the width of the video file must be greater than or equal to this value +in pixels. + +``mimeTypes`` +~~~~~~~~~~~~~ + +**type**: ``array`` or ``string`` **default**: ``video/*`` + +See the :ref:`File mimeTypes option ` for a +description of this option and a list of common video MIME types. + +.. include:: /reference/constraints/_groups-option.rst.inc + +.. include:: /reference/constraints/_payload-option.rst.inc + +Messages +-------- + +``allowLandscapeMessage`` +~~~~~~~~~~~~~~~~~~~~~~~~~ + +**type**: ``string`` **default**: ``The video is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented videos are not allowed.`` + +The message displayed if the video is landscape oriented and landscape videos +are not allowed. + +``allowPortraitMessage`` +~~~~~~~~~~~~~~~~~~~~~~~~ + +**type**: ``string`` **default**: ``The video is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented videos are not allowed.`` + +The message displayed if the video is portrait oriented and portrait videos are not allowed. + +``allowSquareMessage`` +~~~~~~~~~~~~~~~~~~~~~~ + +**type**: ``string`` **default**: ``The video is square ({{ width }}x{{ height }}px). Square videos are not allowed.`` + +The message displayed if the video is square and square videos are not allowed. + +``corruptedMessage`` +~~~~~~~~~~~~~~~~~~~~ + +**type**: ``string`` **default**: ``The video file is corrupted.`` + +The message displayed if the video file is corrupted and cannot be read by ``ffprobe``. + +``maxHeightMessage`` +~~~~~~~~~~~~~~~~~~~~ + +**type**: ``string`` **default**: ``The video height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px.`` + +The message displayed if the height of the video exceeds ``maxHeight``. + +``maxPixelsMessage`` +~~~~~~~~~~~~~~~~~~~~ + +**type**: ``string`` **default**: ``The video has too many pixels ({{ pixels }} pixels). Maximum amount expected is {{ max_pixels }} pixels.`` + +The message displayed if the total number of pixels of the video exceeds ``maxPixels``. + +``maxRatioMessage`` +~~~~~~~~~~~~~~~~~~~ + +**type**: ``string`` **default**: ``The video ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}.`` + +The message displayed if the aspect ratio of the video exceeds ``maxRatio``. + +``maxWidthMessage`` +~~~~~~~~~~~~~~~~~~~ + +**type**: ``string`` **default**: ``The video width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px.`` + +The message displayed if the width of the video exceeds ``maxWidth``. + +``mimeTypesMessage`` +~~~~~~~~~~~~~~~~~~~~ + +**type**: ``string`` **default**: ``This file is not a valid video.`` + +The message displayed if the media type of the video is not a valid media type +per the ``mimeTypes`` option. + +``minHeightMessage`` +~~~~~~~~~~~~~~~~~~~~ + +**type**: ``string`` **default**: ``The video height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px.`` + +The message displayed if the height of the video is less than ``minHeight``. + +``minPixelsMessage`` +~~~~~~~~~~~~~~~~~~~~ + +**type**: ``string`` **default**: ``The video has too few pixels ({{ pixels }} pixels). Minimum amount expected is {{ min_pixels }} pixels.`` + +The message displayed if the total number of pixels of the video is less than ``minPixels``. + +``minRatioMessage`` +~~~~~~~~~~~~~~~~~~~ + +**type**: ``string`` **default**: ``The video ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}.`` + +The message displayed if the aspect ratio of the video is less than ``minRatio``. + +``minWidthMessage`` +~~~~~~~~~~~~~~~~~~~ + +**type**: ``string`` **default**: ``The video width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px.`` + +The message displayed if the width of the video is less than ``minWidth``. + +``multipleVideoStreamsMessage`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**type**: ``string`` **default**: ``The video contains multiple streams. Only one stream is allowed.`` + +The message displayed if the video file contains multiple video streams. + +``sizeNotDetectedMessage`` +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**type**: ``string`` **default**: ``The size of the video could not be detected.`` + +The message displayed if ffprobe cannot detect the dimensions of the video. + +``unsupportedCodecMessage`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**type**: ``string`` **default**: ``Unsupported video codec "{{ codec }}".`` + +The message displayed if the video uses a codec that is not in the ``allowedCodecs`` list. + +``unsupportedContainerMessage`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**type**: ``string`` **default**: ``Unsupported video container "{{ container }}".`` + +The message displayed if the video uses a container format that is not in the +``allowedContainers`` list. + +.. _`FFmpeg package`: https://ffmpeg.org/ +.. _`video codecs`: https://en.wikipedia.org/wiki/Comparison_of_video_codecs +.. _`video container formats`: https://en.wikipedia.org/wiki/Comparison_of_video_container_formats diff --git a/reference/constraints/map.rst.inc b/reference/constraints/map.rst.inc index 06680e42207..15e924d1182 100644 --- a/reference/constraints/map.rst.inc +++ b/reference/constraints/map.rst.inc @@ -88,6 +88,7 @@ File Constraints * :doc:`File ` * :doc:`Image ` +* :doc:`Video ` Financial and other Number Constraints ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From 96802d1671c90d81b83df6bf34c618a1cb3e6437 Mon Sep 17 00:00:00 2001 From: Dmytro Liashko Date: Wed, 24 Sep 2025 22:13:44 +0200 Subject: [PATCH 068/122] [Serializer] Support preserving array keys with XmlEncoder --- serializer/encoders.rst | 42 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/serializer/encoders.rst b/serializer/encoders.rst index c3fb2e26b50..3087dcaddd4 100644 --- a/serializer/encoders.rst +++ b/serializer/encoders.rst @@ -211,6 +211,8 @@ These are the options available on the :ref:`serializer context `` nodes. .. versionadded:: 7.1 @@ -224,6 +226,8 @@ These are the options available on the :ref:`serializer context 2019-10-24 // +Example with ``preserve_numeric_keys``:: + + use Symfony\Component\Serializer\Encoder\XmlEncoder; + + $data = [ + 'person' => [ + ['firstname' => 'Benjamin', 'lastname' => 'Alexandre'], + ['firstname' => 'Damien', 'lastname' => 'Clay'], + ], + ]; + + $xmlEncoder->encode($data, 'xml', ['preserve_numeric_keys' => false]); + // outputs: + // + // + // Benjamin + // Alexandre + // + // + // Damien + // Clay + // + // + $xmlEncoder->encode($data, 'xml', ['preserve_numeric_keys' => true]); + // outputs: + // + // + // + // Benjamin + // Alexandre + // + // + // Damien + // Clay + // + // + // + The ``YamlEncoder`` ------------------- From 2bfa72acdabf47abe77e5ba50370fda4ac36e042 Mon Sep 17 00:00:00 2001 From: Andrey Lebedev Date: Thu, 25 Sep 2025 09:03:06 +0400 Subject: [PATCH 069/122] Update webhook.rst Fix 'Usage in Combination with the Notifier Component': add LOX24 service parser configurartion name --- webhook.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/webhook.rst b/webhook.rst index d27a6e6d906..2117c98deee 100644 --- a/webhook.rst +++ b/webhook.rst @@ -175,9 +175,10 @@ Currently, the following third-party SMS transports support webhooks: ============ ========================================== SMS service Parser service name ============ ========================================== -Twilio ``notifier.webhook.request_parser.twilio`` +LOX24 ``notifier.webhook.request_parser.lox24`` Smsbox ``notifier.webhook.request_parser.smsbox`` Sweego ``notifier.webhook.request_parser.sweego`` +Twilio ``notifier.webhook.request_parser.twilio`` Vonage ``notifier.webhook.request_parser.vonage`` ============ ========================================== From 4c9f94d5d6a31abf9bc88a7534390f48e7062d72 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Thu, 25 Sep 2025 12:21:49 +0200 Subject: [PATCH 070/122] Minor tweaks --- serializer/encoders.rst | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/serializer/encoders.rst b/serializer/encoders.rst index 3087dcaddd4..d40027f49ef 100644 --- a/serializer/encoders.rst +++ b/serializer/encoders.rst @@ -212,7 +212,8 @@ These are the options available on the :ref:`serializer context `` nodes. + If set to true, it keeps numeric array indexes (e.g. ````) + instead of collapsing them into ```` nodes. .. versionadded:: 7.1 @@ -224,9 +225,8 @@ These are the options available on the :ref:`serializer context Clay // // + $xmlEncoder->encode($data, 'xml', ['preserve_numeric_keys' => true]); // outputs: // From 97eba27b4208e7fbbec2c4cf4fd47f92c2fe2049 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Thu, 25 Sep 2025 12:50:56 +0200 Subject: [PATCH 071/122] Addee the versionadded directive --- webhook.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/webhook.rst b/webhook.rst index 2117c98deee..a12a82ffc76 100644 --- a/webhook.rst +++ b/webhook.rst @@ -182,6 +182,10 @@ Twilio ``notifier.webhook.request_parser.twilio`` Vonage ``notifier.webhook.request_parser.vonage`` ============ ========================================== +.. versionadded:: 7.4 + + The support for ``LOX24`` was introduced in Symfony 7.4. + For SMS webhooks, react to the :class:`Symfony\\Component\\RemoteEvent\\Event\\Sms\\SmsEvent` event:: From 5330b20a8fe4dbd12eabc681d11bddadc7aed720 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Thu, 25 Sep 2025 13:09:04 +0200 Subject: [PATCH 072/122] Update Monolog branch --- logging.rst | 2 +- reference/configuration/monolog.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/logging.rst b/logging.rst index 99c09f3dec5..e8c93335038 100644 --- a/logging.rst +++ b/logging.rst @@ -420,5 +420,5 @@ Learn more .. _`Monolog`: https://github.com/Seldaek/monolog .. _`LoggerInterface`: https://github.com/php-fig/log/blob/master/src/LoggerInterface.php .. _`logrotate`: https://github.com/logrotate/logrotate -.. _`Monolog Configuration`: https://github.com/symfony/monolog-bundle/blob/3.x/src/DependencyInjection/Configuration.php +.. _`Monolog Configuration`: https://github.com/symfony/monolog-bundle/blob/4.x/src/DependencyInjection/Configuration.php .. _`STDERR PHP stream`: https://www.php.net/manual/en/features.commandline.io-streams.php diff --git a/reference/configuration/monolog.rst b/reference/configuration/monolog.rst index 3e1ffab21a8..9c781f4e1e4 100644 --- a/reference/configuration/monolog.rst +++ b/reference/configuration/monolog.rst @@ -30,4 +30,4 @@ in your application configuration. messages in the profiler. The profiler uses the name "debug" so it is reserved and cannot be used in the configuration. -.. _`Monolog Configuration`: https://github.com/symfony/monolog-bundle/blob/3.x/src/DependencyInjection/Configuration.php +.. _`Monolog Configuration`: https://github.com/symfony/monolog-bundle/blob/4.x/src/DependencyInjection/Configuration.php From da3870ed9b314a9a3c5a17ad73cbef0c741bc604 Mon Sep 17 00:00:00 2001 From: wkania Date: Thu, 25 Sep 2025 22:15:59 +0200 Subject: [PATCH 073/122] [DoctrineBridge] Add new DayPointType and TimePointType Doctrine type --- components/clock.rst | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/components/clock.rst b/components/clock.rst index c4ac88e9092..83795278873 100644 --- a/components/clock.rst +++ b/components/clock.rst @@ -271,7 +271,17 @@ Storing DatePoints in the Database ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If you :doc:`use Doctrine ` to work with databases, consider using the -``date_point`` Doctrine type, which converts to/from ``DatePoint`` objects automatically:: +new Doctrine types: + +======================= ====================== ===== +DatePoint Doctrine type Extends Doctrine type Class +======================= ====================== ===== +``date_point`` ``datetime_immutable`` :class:`Symfony\\Bridge\\Doctrine\\Types\\DatePointType` +``day_point`` ``date_immutable`` :class:`Symfony\\Bridge\\Doctrine\\Types\\DayPointType` +``time_point`` ``time_immutable`` :class:`Symfony\\Bridge\\Doctrine\\Types\\TimePointType` +======================= ====================== ===== + +They convert to/from ``DatePoint`` objects automatically:: // src/Entity/Product.php namespace App\Entity; @@ -282,7 +292,7 @@ If you :doc:`use Doctrine ` to work with databases, consider using th #[ORM\Entity] class Product { - // if you don't define the Doctrine type explicitly, Symfony will autodetect it: + // if you don't define the Doctrine type explicitly, Symfony will autodetect 'date_point': #[ORM\Column] private DatePoint $createdAt; @@ -290,6 +300,12 @@ If you :doc:`use Doctrine ` to work with databases, consider using th #[ORM\Column(type: 'date_point')] private DatePoint $updatedAt; + #[ORM\Column(type: 'day_point')] + public DatePoint $birthday; + + #[ORM\Column(type: 'time_point')] + public DatePoint $openAt; + // ... } @@ -297,6 +313,10 @@ If you :doc:`use Doctrine ` to work with databases, consider using th The ``DatePointType`` was introduced in Symfony 7.3. +.. versionadded:: 7.4 + + The ``DayPointType`` and ``TimePointType`` were introduced in Symfony 7.4. + .. _clock_writing-tests: Writing Time-Sensitive Tests From bb56f0cc285286dc5540597705bc5d25f322cda4 Mon Sep 17 00:00:00 2001 From: wkania Date: Thu, 25 Sep 2025 22:45:31 +0200 Subject: [PATCH 074/122] [Validator] Fix typo in Video constraint --- reference/constraints/Video.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reference/constraints/Video.rst b/reference/constraints/Video.rst index 644060d4c6c..e3aa7fbb657 100644 --- a/reference/constraints/Video.rst +++ b/reference/constraints/Video.rst @@ -298,7 +298,7 @@ The message displayed if the video file contains multiple video streams. **type**: ``string`` **default**: ``The size of the video could not be detected.`` -The message displayed if ffprobe cannot detect the dimensions of the video. +The message displayed if ``ffprobe`` cannot detect the dimensions of the video. ``unsupportedCodecMessage`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~ From e918166239713cef8d5a95687306f8d82a21003f Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Wed, 24 Sep 2025 16:34:51 +0200 Subject: [PATCH 075/122] [Uid] Default to UuidV7 when using UuidFactory --- components/uid.rst | 84 ++++++++++++++++++++++++++-------------------- 1 file changed, 48 insertions(+), 36 deletions(-) diff --git a/components/uid.rst b/components/uid.rst index 46c710a0fd5..bd67cdb7fed 100644 --- a/components/uid.rst +++ b/components/uid.rst @@ -158,8 +158,42 @@ following methods to create a ``Uuid`` object from it:: $uuid = Uuid::fromBase58('TuetYWNHhmuSQ3xPoVLv9M'); $uuid = Uuid::fromRfc4122('d9e7a184-5d5b-11ea-a62a-3499710062d0'); -You can also use the ``UuidFactory`` to generate UUIDs. First, you may -configure the behavior of the factory using configuration files:: +You can also use the ``UuidFactory`` to generate UUIDs. Inject the factory in +your services and use it as follows: + + namespace App\Service; + + use Symfony\Component\Uid\Factory\UuidFactory; + + class FooService + { + public function __construct( + private UuidFactory $uuidFactory, + ) { + } + + public function generate(): void + { + $uuid = $this->uuidFactory->create(); + + $randomBasedUuid = $this->uuidFactory->randomBased()->create(); + // $namespace can be omitted if a default namespace is configured in the factory (see below) + $nameBasedUuid = $this->uuidFactory->nameBased($namespace)->create($name); + // $node can be omitted if a default node is configured in the factory (see below) + $timestampBased = $this->uuidFactory->timeBased($node)->create(); + + // ... + } + } + +By default, this factory generates the folllowing UUIDs: + +* Default and time-based UUIDs: UUIDv7 +* Name-based UUIDs: UUIDv5 +* Random-based UUIDs: UUIDv4 +* Time-based node and UUID namespace: ``null`` + +You can configure these default values:: .. configuration-block:: @@ -168,10 +202,10 @@ configure the behavior of the factory using configuration files:: # config/packages/uid.yaml framework: uid: - default_uuid_version: 7 - name_based_uuid_version: 5 + default_uuid_version: 6 + name_based_uuid_version: 3 name_based_uuid_namespace: 6ba7b810-9dad-11d1-80b4-00c04fd430c8 - time_based_uuid_version: 7 + time_based_uuid_version: 6 time_based_uuid_node: 121212121212 .. code-block:: xml @@ -187,10 +221,10 @@ configure the behavior of the factory using configuration files:: @@ -209,41 +243,19 @@ configure the behavior of the factory using configuration files:: $container->extension('framework', [ 'uid' => [ - 'default_uuid_version' => 7, - 'name_based_uuid_version' => 5, + 'default_uuid_version' => 6, + 'name_based_uuid_version' => 3, 'name_based_uuid_namespace' => '6ba7b810-9dad-11d1-80b4-00c04fd430c8', - 'time_based_uuid_version' => 7, + 'time_based_uuid_version' => 6, 'time_based_uuid_node' => 121212121212, ], ]); }; -Then, you can inject the factory in your services and use it to generate UUIDs based -on the configuration you defined:: - - namespace App\Service; - - use Symfony\Component\Uid\Factory\UuidFactory; - - class FooService - { - public function __construct( - private UuidFactory $uuidFactory, - ) { - } - - public function generate(): void - { - // This creates a UUID of the version given in the configuration file (v7 by default) - $uuid = $this->uuidFactory->create(); - - $nameBasedUuid = $this->uuidFactory->nameBased(/** ... */); - $randomBasedUuid = $this->uuidFactory->randomBased(); - $timestampBased = $this->uuidFactory->timeBased(); +.. versionadded:: 7.4 - // ... - } - } + Starting from Symfony 7.4, the default version for both UUIDs and time-based + UUIDs is v7. In previous versions, the default was v6. Converting UUIDs ~~~~~~~~~~~~~~~~ From 17080a6ed02a202fc8288846253b412034af7cf0 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Fri, 26 Sep 2025 13:26:59 +0200 Subject: [PATCH 076/122] [Validator] deprecate passing choices as $options argument to Choice constraint --- reference/constraints/Choice.rst | 21 +++++++++++++++------ validation/raw_values.rst | 2 +- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/reference/constraints/Choice.rst b/reference/constraints/Choice.rst index 72e1ae6ecf7..5bd9ae2d9e9 100644 --- a/reference/constraints/Choice.rst +++ b/reference/constraints/Choice.rst @@ -34,7 +34,7 @@ If your valid choice list is simple, you can pass them in directly via the { public const GENRES = ['fiction', 'non-fiction']; - #[Assert\Choice(['New York', 'Berlin', 'Tokyo'])] + #[Assert\Choice(choices: ['New York', 'Berlin', 'Tokyo'])] protected string $city; #[Assert\Choice(choices: Author::GENRES, message: 'Choose a valid genre.')] @@ -47,7 +47,8 @@ If your valid choice list is simple, you can pass them in directly via the App\Entity\Author: properties: city: - - Choice: [New York, Berlin, Tokyo] + - Choice: + choices: [New York, Berlin, Tokyo] genre: - Choice: choices: [fiction, non-fiction] @@ -64,9 +65,11 @@ If your valid choice list is simple, you can pass them in directly via the - New York - Berlin - Tokyo + @@ -97,7 +100,7 @@ If your valid choice list is simple, you can pass them in directly via the { $metadata->addPropertyConstraint( 'city', - new Assert\Choice(['New York', 'Berlin', 'Tokyo']) + new Assert\Choice(choices: ['New York', 'Berlin', 'Tokyo']) ); $metadata->addPropertyConstraint('genre', new Assert\Choice( @@ -107,6 +110,12 @@ If your valid choice list is simple, you can pass them in directly via the } } +.. deprecated:: 7.4 + + Passing an array of choices as the first argument of the ``Choice`` constraint + is deprecated and will stop working in Symfony 8.0. Instead, pass the choices + using the ``choices:`` named argument. + Supplying the Choices with a Callback Function ---------------------------------------------- diff --git a/validation/raw_values.rst b/validation/raw_values.rst index 9c900ff2b36..4fecb7c44ee 100644 --- a/validation/raw_values.rst +++ b/validation/raw_values.rst @@ -75,7 +75,7 @@ Validation of arrays is possible using the ``Collection`` constraint:: ]), 'email' => new Assert\Email(), 'simple' => new Assert\Length(['min' => 102]), - 'eye_color' => new Assert\Choice([3, 4]), + 'eye_color' => new Assert\Choice(choices: [3, 4]), 'file' => new Assert\File(), 'password' => new Assert\Length(['min' => 60]), 'tags' => new Assert\Optional([ From 65c53512967635d5cbaffba9c811c60c6837b5c4 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Wed, 1 Oct 2025 10:05:34 +0200 Subject: [PATCH 077/122] [Form] Add new active_at, not_active_at and legal_tender options to CurrencyType --- reference/forms/types/currency.rst | 68 +++++++++++++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/reference/forms/types/currency.rst b/reference/forms/types/currency.rst index 94c0d2cddc8..70881f38994 100644 --- a/reference/forms/types/currency.rst +++ b/reference/forms/types/currency.rst @@ -28,22 +28,87 @@ Field Options Overridden Options ------------------ +``active_at`` +~~~~~~~~~~~~~ + +**type**: ``\DateTimeInterface::class`` or ``null`` **default**: ``null`` + +An active currency is one that is still in use today as `legal tender`_ +somewhere. This option allows you to show only the currencies that are active +at that date: + + use Symfony\Component\Form\Extension\Core\Type\CurrencyType; + + $builder->add('currency', CurrencyType::class, [ + 'active_at' => new \DateTimeImmutable('2007-01-15', new \DateTimeZone('Etc/UTC')), + ]); + +In the previous example, the list of currences won't include items like the +Slovenian Tolar, which stopped being used on January 14, 2007. + +.. versionadded:: 7.4 + + The ``active_at`` option was introduced in Symfony 7.4. + ``choices`` ~~~~~~~~~~~ **default**: ``Symfony\Component\Intl\Currencies::getNames()`` -The choices option defaults to all currencies. +The ``choices`` option defaults to all currencies that are `legal tender`_ at +the moment of creating the form type. .. warning:: If you want to override the built-in choices of the currency type, you will also have to set the ``choice_loader`` option to ``null``. +.. versionadded:: 7.4 + + The default value of ``choices`` changed in Symfony 7.4. In previous versions, + this option contained all currencies, including those that were no longer legal + tender in their countries. + .. include:: /reference/forms/types/options/choice_translation_domain_disabled.rst.inc .. include:: /reference/forms/types/options/invalid_message.rst.inc +``legal_tender`` +~~~~~~~~~~~~~~~~ + +**type**: ``boolean`` or ``null`` **default**: ``true`` + +Set this option to ``false`` to only display the currencies that are no longer +`legal tender`_ in their countries. Set it to ``null`` to include all curencies, +regardless of their legal tender status. + +.. versionadded:: 7.4 + + The ``legal_tender`` option was introduced in Symfony 7.4. + +.. include:: /reference/forms/types/options/choice_translation_domain.rst.inc + +``not_active_at`` +~~~~~~~~~~~~~~~~~ + +**type**: ``\DateTimeInterface::class`` or ``null`` **default**: ``null`` + +An inactive currency is one that is a legacy currency, no longer in circulation. +This option allows you to show only the currencies that are inactive at that date: + + use Symfony\Component\Form\Extension\Core\Type\CurrencyType; + + $builder->add('currency', CurrencyType::class, [ + 'not_active_at' => new \DateTimeImmutable('2007-01-15', new \DateTimeZone('Etc/UTC')), + ]); + +In the previous example, the list of currencies will include items like the +Slovenian Tolar, which stopped being used on January 14, 2007. + +.. versionadded:: 7.4 + + The ``not_active_at`` option was introduced in Symfony 7.4. + Inherited Options ----------------- @@ -104,3 +169,4 @@ The actual default value of this option depends on other field options: .. include:: /reference/forms/types/options/row_attr.rst.inc .. _`3-letter ISO 4217`: https://en.wikipedia.org/wiki/ISO_4217 +.. _`legal tender`: https://en.wikipedia.org/wiki/Legal_tender From 745b07b153c25b970dcc7c9f7b4aef87aa7f4dcb Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Thu, 2 Oct 2025 10:22:49 +0200 Subject: [PATCH 078/122] [DependencyInjection] Deprecate getNamespace() and getXsdValidationBasePath() methods --- bundles/configuration.rst | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/bundles/configuration.rst b/bundles/configuration.rst index dedfada2ea2..eee7b9f7c37 100644 --- a/bundles/configuration.rst +++ b/bundles/configuration.rst @@ -477,6 +477,14 @@ the extension. You might want to change this to a more professional URL:: } } +.. deprecated:: 7.4 + + The ``getNamespace()`` method, together with XML support, is deprecated + since Symfony 7.4 and will be removed in Symfony 8.0. + + If your bundle needs to remain compatible with older Symfony versions that + still support XML, keep this method and add the ``@deprecated`` annotation to it. + Providing an XML Schema ~~~~~~~~~~~~~~~~~~~~~~~ @@ -510,6 +518,14 @@ can place it anywhere you like. You should return this path as the base path:: } } +.. deprecated:: 7.4 + + The ``getXsdValidationBasePath()`` method, together with XML support, is + deprecated since Symfony 7.4 and will be removed in Symfony 8.0. + + If your bundle needs to remain compatible with older Symfony versions that + still support XML, keep this method and add the ``@deprecated`` annotation to it. + Assuming the XSD file is called ``hello-1.0.xsd``, the schema location will be ``https://acme_company.com/schema/dic/hello/hello-1.0.xsd``: From b5c5035a7c59023e61295ee0dcfce1485e0cebe1 Mon Sep 17 00:00:00 2001 From: msenoussi Date: Thu, 25 Sep 2025 20:58:49 +0200 Subject: [PATCH 079/122] [Uid] Add documentation for MockUuidFactory usage in tests --- components/uid.rst | 60 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/components/uid.rst b/components/uid.rst index 46c710a0fd5..51a1ea0b9b8 100644 --- a/components/uid.rst +++ b/components/uid.rst @@ -433,6 +433,66 @@ of the UUID parameters:: } } +MockUuidFactory +=============== + +.. versionadded:: 7.4 + + The :class:`Symfony\\Component\\Uid\\Factory\\MockUuidFactory` class was introduced in Symfony 7.4. + +The :class:`Symfony\\Component\\Uid\\Factory\\MockUuidFactory` class allows you to +control the UUIDs generated during your tests, making them predictable and reproducible. + +Suppose you have a service that generates a UUID for each new user:: + + use Symfony\Component\Uid\Factory\UuidFactory; + use Symfony\Component\Uid\Uuid; + + class UserService + { + public function __construct( + private UuidFactory $uuidFactory + ){ + } + + public function createUserId(): string + { + return $this->uuidFactory->create()->toRfc4122(); + } + } + +In your tests, you can use ``MockUuidFactory`` to inject predictable UUIDs and verify the expected behavior:: + + use PHPUnit\Framework\TestCase; + use Symfony\Component\Uid\Factory\MockUuidFactory; + use Symfony\Component\Uid\UuidV4; + + class UserServiceTest extends TestCase + { + public function testCreateUserIdReturnsExpectedUuid() + { + $factory = new MockUuidFactory([ + UuidV4::fromString('11111111-1111-4111-8111-111111111111'), + UuidV4::fromString('22222222-2222-4222-8222-222222222222'), + ]); + + $service = new UserService($factory); + + $this->assertSame('11111111-1111-4111-8111-111111111111', $service->createUserId()); + $this->assertSame('22222222-2222-4222-8222-222222222222', $service->createUserId()); + } + } + +.. warning:: + + ``MockUuidFactory`` is intended for use in tests only and should never be used in production. + +.. note:: + + - Supports the :method:`Symfony\\Component\\Uid\\Factory\\MockUuidFactory::create`, :method:`Symfony\\Component\\Uid\\Factory\\MockUuidFactory::randomBased`, :method:`Symfony\\Component\\Uid\\Factory\\MockUuidFactory::timeBased`, and :method:`Symfony\\Component\\Uid\\Factory\\MockUuidFactory::nameBased` methods. + - You can mix different UUID versions in the same sequence. + - Throws an exception if the sequence is exhausted or the type does not match. + .. _ulid: ULIDs From 5ef15b799f093473bb521bbc1875f1f1b1e48413 Mon Sep 17 00:00:00 2001 From: Antoine Lamirault Date: Sun, 5 Oct 2025 12:34:19 +0200 Subject: [PATCH 080/122] [HttpFoundation] Remove deprecated Request::get() in favor of using properties --- components/http_kernel.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/http_kernel.rst b/components/http_kernel.rst index 62d1e92d89b..95dfd1e5344 100644 --- a/components/http_kernel.rst +++ b/components/http_kernel.rst @@ -639,7 +639,7 @@ else that can be used to create a working example:: $routes->add('hello', new Route('/hello/{name}', [ '_controller' => function (Request $request): Response { return new Response( - sprintf("Hello %s", $request->get('name')) + sprintf("Hello %s", $request->attributes->get('name')) ); }] )); From d02feaa16099dece311f1bfd0f3317530ee8fad2 Mon Sep 17 00:00:00 2001 From: Antoine Lamirault Date: Sun, 5 Oct 2025 12:47:34 +0200 Subject: [PATCH 081/122] [FrameworkBundle] Add support for union types on #[AsEventListener] --- event_dispatcher.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/event_dispatcher.rst b/event_dispatcher.rst index ffa9e67aa0d..269304f4357 100644 --- a/event_dispatcher.rst +++ b/event_dispatcher.rst @@ -210,6 +210,12 @@ can also be applied to methods directly:: // ... } + #[AsEventListener] + public function onMultipleCustomEvent(CustomEvent|AnotherCustomEvent $event): void + { + // ... + } + #[AsEventListener(event: 'foo', priority: 42)] public function onFoo(): void { @@ -228,6 +234,10 @@ can also be applied to methods directly:: Note that the attribute doesn't require its ``event`` parameter to be set if the method already type-hints the expected event. +.. versionadded:: 7.4 + + The support for union types in the method type-hints was introduced in Symfony 7.4. + .. _events-subscriber: Creating an Event Subscriber From 57eca76aa7cff9cbe161f368fd6882404228b67d Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Mon, 6 Oct 2025 08:42:35 +0200 Subject: [PATCH 082/122] Minor tweak --- event_dispatcher.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/event_dispatcher.rst b/event_dispatcher.rst index 269304f4357..e8c807beb29 100644 --- a/event_dispatcher.rst +++ b/event_dispatcher.rst @@ -236,7 +236,8 @@ can also be applied to methods directly:: .. versionadded:: 7.4 - The support for union types in the method type-hints was introduced in Symfony 7.4. + Support for union types in the ``$event`` argument of methods using the + ``#[AsEventListener]`` attribute was introduced in Symfony 7.4. .. _events-subscriber: From a4e01802895f407c9a33cec177477b4ac97855cd Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Thu, 25 Sep 2025 16:02:59 +0200 Subject: [PATCH 083/122] [Security] improve VoteObject adding extraData --- security/voters.rst | 65 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/security/voters.rst b/security/voters.rst index 803715ea4dc..b8784b8195e 100644 --- a/security/voters.rst +++ b/security/voters.rst @@ -212,6 +212,17 @@ would look like this:: } } +.. tip:: + + Votes define an ``$extraData`` property that you can use to store any data + that you might need later:: + + $vote->extraData['key'] = 'value'; // values can be of any type + + .. versionadded:: 7.4 + + The ``$extraData`` property was introduced in Symfony 7.4. + That's it! The voter is done! Next, :ref:`configure it `. To recap, here's what's expected from the two abstract methods: @@ -512,6 +523,60 @@ option to use a custom service (your service must implement the ; }; +When creating custom decision strategies, you can store additional data in votes +to be used later when making a decision. For example, if not all votes should +have the same weight, you could store a ``score`` value for each vote:: + + // src/Security/PostVoter.php + namespace App\Security; + + 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 + { + // ... + + protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token, ?Vote $vote = null): bool + { + // ... + $vote->extraData['score'] = 10; + + // ... + } + } + +Then, access that value when counting votes to make a decision:: + + // src/Security/MyCustomAccessDecisionStrategy.php + use Symfony\Component\Security\Core\Authorization\Strategy\AccessDecisionStrategyInterface; + + class MyCustomAccessDecisionStrategy implements AccessDecisionStrategyInterface + { + public function decide(\Traversable $results, $accessDecision = null): bool + { + $score = 0; + + foreach ($results as $key => $result) { + $vote = $accessDecision->votes[$key]; + if (array_key_exists('score', $vote->extraData)) { + $score += $vote->extraData['score']; + } else { + $score += $vote->result; + } + } + + // ... + } + } + +.. versionadded:: 7.4 + + The feature to store arbitrary data inside votes was introduced in Symfony 7.4. + Custom Access Decision Manager ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From 8a6001d8583734a5765e6329e466c4e013b5b5dc Mon Sep 17 00:00:00 2001 From: wkania Date: Thu, 9 Oct 2025 21:05:45 +0200 Subject: [PATCH 084/122] [Validator] Video Constraint - fix options order and types --- reference/constraints/Video.rst | 38 ++++++++++++++++----------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/reference/constraints/Video.rst b/reference/constraints/Video.rst index e3aa7fbb657..a7b9e284700 100644 --- a/reference/constraints/Video.rst +++ b/reference/constraints/Video.rst @@ -121,10 +121,18 @@ cannot be equal). If set, the height of the video file must be less than or equal to this value in pixels. +``maxPixels`` +~~~~~~~~~~~~~ + +**type**: ``integer`` | ``float`` + +If set, the total number of pixels (``width * height``) of the video file must be less +than or equal to this value. + ``maxRatio`` ~~~~~~~~~~~~ -**type**: ``float`` +**type**: ``integer`` | ``float`` If set, the aspect ratio (``width / height``) of the video file must be less than or equal to this value. For example, a square video has a ratio of 1, @@ -138,6 +146,14 @@ a 16:9 video has a ratio of 1.78, and a 4:3 video has a ratio of 1.33. If set, the width of the video file must be less than or equal to this value in pixels. +``mimeTypes`` +~~~~~~~~~~~~~ + +**type**: ``array`` or ``string`` **default**: ``video/*`` + +See the :ref:`File mimeTypes option ` for a +description of this option and a list of common video MIME types. + ``minHeight`` ~~~~~~~~~~~~~ @@ -149,23 +165,15 @@ value in pixels. ``minPixels`` ~~~~~~~~~~~~~ -**type**: ``float`` +**type**: ``integer`` | ``float`` If set, the total number of pixels (``width * height``) of the video file must be greater than or equal to this value. -``maxPixels`` -~~~~~~~~~~~~~ - -**type**: ``float`` - -If set, the total number of pixels (``width * height``) of the video file must be less -than or equal to this value. - ``minRatio`` ~~~~~~~~~~~~ -**type**: ``float`` +**type**: ``integer`` | ``float`` If set, the aspect ratio (``width / height``) of the video file must be greater than or equal to this value. @@ -178,14 +186,6 @@ than or equal to this value. If set, the width of the video file must be greater than or equal to this value in pixels. -``mimeTypes`` -~~~~~~~~~~~~~ - -**type**: ``array`` or ``string`` **default**: ``video/*`` - -See the :ref:`File mimeTypes option ` for a -description of this option and a list of common video MIME types. - .. include:: /reference/constraints/_groups-option.rst.inc .. include:: /reference/constraints/_payload-option.rst.inc From e4c78f89cc081f6d4473a2cf3c2cb1303a6fa14e Mon Sep 17 00:00:00 2001 From: "hubert.lenoir" Date: Mon, 13 Oct 2025 14:49:54 +0200 Subject: [PATCH 085/122] [Security] Add documentation for the security:oidc:generate-token command --- security/access_token.rst | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/security/access_token.rst b/security/access_token.rst index 2c070f72e92..a6a3e16af6a 100644 --- a/security/access_token.rst +++ b/security/access_token.rst @@ -877,6 +877,34 @@ create your own User from the claims, you must } } +Creating a OIDC token from the command line +------------------------------------------- + +.. versionadded:: 7.4 + + The ``security:oidc:generate-token`` command was introduced in Symfony 7.4. + +The ``security:oidc:generate-token`` command helps you generate JWTs. This is +particularly useful when developing or testing applications that use OIDC +authentication. + +To generate a token using the default configuration: + +.. code-block:: terminal + + # generate a token for the user named "john.doe@example.com" + $ php bin/console security:oidc:generate-token john.doe@example.com + + # generate a token when multiple firewall, algorithm or issuer are available + $ php bin/console security:oidc:generate-token john.doe@example.com \ + --firewall="api" \ + --algorithm="HS256" \ + --issuer="https://example.com" + +.. note:: + + The JWK used for signing must have the appropriate `key operation flags`_ set. + Using CAS 2.0 ------------- @@ -1099,3 +1127,4 @@ for :ref:`stateless firewalls `. .. _`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 +.. _`key operation flags`: https://www.iana.org/assignments/jose/jose.xhtml#web-key-operations From 110077e1fac64c0859d6f874945dc2cabb170ab3 Mon Sep 17 00:00:00 2001 From: Ruud Kamphuis Date: Tue, 14 Oct 2025 11:04:29 +0200 Subject: [PATCH 086/122] Explain how to use multiple OIDC discovery endpoints Fixes #21496 --- security/access_token.rst | 89 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/security/access_token.rst b/security/access_token.rst index 2c070f72e92..4e022be4c7e 100644 --- a/security/access_token.rst +++ b/security/access_token.rst @@ -717,6 +717,10 @@ it, and retrieves the user information from it. Optionally, the token can be enc Support for encryption algorithms to decrypt JWEs was introduced in Symfony 7.3. +.. versionadded:: 7.4 + + Support for multiple OIDC discovery endpoints was introduced in Symfony 7.4. + 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: @@ -796,6 +800,91 @@ from the OpenID Connect Discovery), and configure the ``discovery`` option: ; }; +Configuring Multiple OIDC Discovery Endpoints +............................................. + +The ``OidcTokenHandler`` supports multiple OIDC discovery endpoints. This allows +validating tokens from multiple identity providers: + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/security.yaml + security: + firewalls: + main: + access_token: + token_handler: + oidc: + algorithms: ['ES256', 'RS256'] + audience: 'api-example' + issuers: ['https://oidc1.example.com', 'https://oidc2.example.com'] + discovery: + base_uri: + - https://idp1.example.com/realms/demo/ + - https://idp2.example.com/realms/demo/ + cache: + id: cache.app + + .. code-block:: xml + + + + + + + + + + + ES256 + RS256 + https://oidc1.example.com + https://oidc2.example.com + + https://idp1.example.com/realms/demo/ + https://idp2.example.com/realms/demo/ + + + + + + + + + .. code-block:: php + + // config/packages/security.php + use Symfony\Config\SecurityConfig; + + return static function (SecurityConfig $security) { + $security->firewall('main') + ->accessToken() + ->tokenHandler() + ->oidc() + ->algorithms(['ES256', 'RS256']) + ->audience('api-example') + ->issuers(['https://oidc1.example.com', 'https://oidc2.example.com']) + ->discovery() + ->baseUri([ + 'https://idp1.example.com/realms/demo/', + 'https://idp2.example.com/realms/demo/', + ]) + ->cache(['id' => 'cache.app']) + ; + }; + +The token handler fetches the JWK sets from all configured discovery endpoints +and builds a combined JWK set for token validation. This enables your application +to accept and validate tokens from multiple identity providers in a single firewall. + Following the `OpenID Connect Specification`_, the ``sub`` claim is used by default as user identifier. To use another claim, specify it on the configuration: From 1e48456236122bdc6fa9c30ee90bda09ee404137 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Tue, 14 Oct 2025 17:38:49 +0200 Subject: [PATCH 087/122] Minor tweaks --- security/access_token.rst | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/security/access_token.rst b/security/access_token.rst index a6a3e16af6a..d992143c3fd 100644 --- a/security/access_token.rst +++ b/security/access_token.rst @@ -884,18 +884,15 @@ Creating a OIDC token from the command line The ``security:oidc:generate-token`` command was introduced in Symfony 7.4. -The ``security:oidc:generate-token`` command helps you generate JWTs. This is -particularly useful when developing or testing applications that use OIDC -authentication. - -To generate a token using the default configuration: +The ``security:oidc:generate-token`` command helps you generate JWTs. It's mostly +useful when developing or testing applications that use OIDC authentication: .. code-block:: terminal - # generate a token for the user named "john.doe@example.com" + # generate a token using the default configuration $ php bin/console security:oidc:generate-token john.doe@example.com - # generate a token when multiple firewall, algorithm or issuer are available + # specify the firewall, algorithm, and issuer if multiple are available $ php bin/console security:oidc:generate-token john.doe@example.com \ --firewall="api" \ --algorithm="HS256" \ From ac1e0a25c230ba93e38fc9754d59327a005490c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20J=2E=20Garc=C3=ADa=20Lagar?= Date: Tue, 14 Oct 2025 11:23:17 +0200 Subject: [PATCH 088/122] [HttpClient] Document new option `auto_upgrade_http_version` --- http_client.rst | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/http_client.rst b/http_client.rst index a1c8f09bc1d..b7643859f23 100644 --- a/http_client.rst +++ b/http_client.rst @@ -1789,6 +1789,17 @@ You can also pass a set of default options to your client thanks to the // ... +.. _auto-upgrade-http-version: + +.. versionadded:: 7.4 + + Starting in Symfony 7.4, the option `auto_upgrade_http_version` can + be set to false to disable automatic HTTP protocol version upgrade when + desired. + + The automatic HTTP version upgrade is always disabled for HTTP/1.0 requests, + regardless of the option value. + HTTPlug ~~~~~~~ @@ -1890,6 +1901,9 @@ You can also pass a set of default options to your client thanks to the // ... + +See :ref:`auto_upgrade_http_version ` option for details about how the HTTP protocol version selection works. + Native PHP Streams ~~~~~~~~~~~~~~~~~~ From 8f7bcf734814e57c11a60d0fe1cbfbf35d061718 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Wed, 15 Oct 2025 08:12:04 +0200 Subject: [PATCH 089/122] Tweaks and rewords --- http_client.rst | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/http_client.rst b/http_client.rst index b7643859f23..d1352cbc5c4 100644 --- a/http_client.rst +++ b/http_client.rst @@ -1791,14 +1791,25 @@ You can also pass a set of default options to your client thanks to the .. _auto-upgrade-http-version: -.. versionadded:: 7.4 +You can use the ``auto_upgrade_http_version`` option to control whether the HTTP +protocol version is automatically upgraded:: + + $client = (new Psr18Client()) + ->withOptions([ + // set to false to always use the HTTP version defined in your request; + // set to true to allow the server to upgrade the HTTP version in its response + 'auto_upgrade_http_version' => false + // ... + ]); + +.. note:: - Starting in Symfony 7.4, the option `auto_upgrade_http_version` can - be set to false to disable automatic HTTP protocol version upgrade when - desired. + The ``auto_upgrade_http_version`` option is ignored for HTTP/1.0 requests, + which always keep that protocol version. + +.. versionadded:: 7.4 - The automatic HTTP version upgrade is always disabled for HTTP/1.0 requests, - regardless of the option value. + The ``auto_upgrade_http_version`` option was introduced in Symfony 7.4. HTTPlug ~~~~~~~ @@ -1902,7 +1913,8 @@ You can also pass a set of default options to your client thanks to the // ... -See :ref:`auto_upgrade_http_version ` option for details about how the HTTP protocol version selection works. +See the :ref:`auto_upgrade_http_version ` option for +details about how the HTTP protocol version selection works. Native PHP Streams ~~~~~~~~~~~~~~~~~~ From c9a365496466d163fa038de411654879f8b083d1 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Wed, 15 Oct 2025 09:13:34 +0200 Subject: [PATCH 090/122] Minor tweak --- security/access_token.rst | 116 +++++++++++++++++++------------------- 1 file changed, 58 insertions(+), 58 deletions(-) diff --git a/security/access_token.rst b/security/access_token.rst index ae39fed6a84..1729540af10 100644 --- a/security/access_token.rst +++ b/security/access_token.rst @@ -717,10 +717,6 @@ it, and retrieves the user information from it. Optionally, the token can be enc Support for encryption algorithms to decrypt JWEs was introduced in Symfony 7.3. -.. versionadded:: 7.4 - - Support for multiple OIDC discovery endpoints was introduced in Symfony 7.4. - 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: @@ -800,11 +796,9 @@ from the OpenID Connect Discovery), and configure the ``discovery`` option: ; }; -Configuring Multiple OIDC Discovery Endpoints -............................................. - -The ``OidcTokenHandler`` supports multiple OIDC discovery endpoints. This allows -validating tokens from multiple identity providers: +Following the `OpenID Connect Specification`_, the ``sub`` claim is used by +default as user identifier. To use another claim, specify it on the +configuration: .. configuration-block:: @@ -817,15 +811,11 @@ validating tokens from multiple identity providers: access_token: token_handler: oidc: + claim: email algorithms: ['ES256', 'RS256'] + keyset: '{"keys":[{"kty":"...","k":"..."}]}' audience: 'api-example' - issuers: ['https://oidc1.example.com', 'https://oidc2.example.com'] - discovery: - base_uri: - - https://idp1.example.com/realms/demo/ - - https://idp2.example.com/realms/demo/ - cache: - id: cache.app + issuers: ['https://oidc.example.com'] .. code-block:: xml @@ -843,15 +833,10 @@ validating tokens from multiple identity providers: - + ES256 RS256 - https://oidc1.example.com - https://oidc2.example.com - - https://idp1.example.com/realms/demo/ - https://idp2.example.com/realms/demo/ - + https://oidc.example.com @@ -869,25 +854,38 @@ validating tokens from multiple identity providers: ->accessToken() ->tokenHandler() ->oidc() + ->claim('email') ->algorithms(['ES256', 'RS256']) + ->keyset('{"keys":[{"kty":"...","k":"..."}]}') ->audience('api-example') - ->issuers(['https://oidc1.example.com', 'https://oidc2.example.com']) - ->discovery() - ->baseUri([ - 'https://idp1.example.com/realms/demo/', - 'https://idp2.example.com/realms/demo/', - ]) - ->cache(['id' => 'cache.app']) + ->issuers(['https://oidc.example.com']) ; }; -The token handler fetches the JWK sets from all configured discovery endpoints -and builds a combined JWK set for token validation. This enables your application -to accept and validate tokens from multiple identity providers in a single firewall. +By default, the ``OidcTokenHandler`` creates an ``OidcUser`` with the claims. To +create your own User from the claims, you must +:doc:`create your own UserProvider `:: -Following the `OpenID Connect Specification`_, the ``sub`` claim is used by -default as user identifier. To use another claim, specify it on the -configuration: + // src/Security/Core/User/OidcUserProvider.php + use Symfony\Component\Security\Core\User\AttributesBasedUserProviderInterface; + + class OidcUserProvider implements AttributesBasedUserProviderInterface + { + public function loadUserByIdentifier(string $identifier, array $attributes = []): UserInterface + { + // implement your own logic to load and return the user object + } + } + +Configuring Multiple OIDC Discovery Endpoints +............................................. + +.. versionadded:: 7.4 + + Support for multiple OIDC discovery endpoints was introduced in Symfony 7.4. + +The ``OidcTokenHandler`` supports multiple OIDC discovery endpoints, allowing it +to validate tokens from different identity providers: .. configuration-block:: @@ -900,11 +898,15 @@ configuration: access_token: token_handler: oidc: - claim: email algorithms: ['ES256', 'RS256'] - keyset: '{"keys":[{"kty":"...","k":"..."}]}' audience: 'api-example' - issuers: ['https://oidc.example.com'] + issuers: ['https://oidc1.example.com', 'https://oidc2.example.com'] + discovery: + base_uri: + - https://idp1.example.com/realms/demo/ + - https://idp2.example.com/realms/demo/ + cache: + id: cache.app .. code-block:: xml @@ -922,10 +924,15 @@ configuration: - + ES256 RS256 - https://oidc.example.com + https://oidc1.example.com + https://oidc2.example.com + + https://idp1.example.com/realms/demo/ + https://idp2.example.com/realms/demo/ + @@ -943,28 +950,21 @@ configuration: ->accessToken() ->tokenHandler() ->oidc() - ->claim('email') ->algorithms(['ES256', 'RS256']) - ->keyset('{"keys":[{"kty":"...","k":"..."}]}') ->audience('api-example') - ->issuers(['https://oidc.example.com']) + ->issuers(['https://oidc1.example.com', 'https://oidc2.example.com']) + ->discovery() + ->baseUri([ + 'https://idp1.example.com/realms/demo/', + 'https://idp2.example.com/realms/demo/', + ]) + ->cache(['id' => 'cache.app']) ; }; -By default, the ``OidcTokenHandler`` creates an ``OidcUser`` with the claims. To -create your own User from the claims, you must -:doc:`create your own UserProvider `:: - - // src/Security/Core/User/OidcUserProvider.php - use Symfony\Component\Security\Core\User\AttributesBasedUserProviderInterface; - - class OidcUserProvider implements AttributesBasedUserProviderInterface - { - public function loadUserByIdentifier(string $identifier, array $attributes = []): UserInterface - { - // implement your own logic to load and return the user object - } - } +The token handler fetches the JWK sets from all configured discovery endpoints +and builds a combined JWK set for token validation. This lets your application +accept and validate tokens from multiple identity providers within a single firewall. Creating a OIDC token from the command line ------------------------------------------- From 66726a7966e214ab1d7b7ea1bce8ce2aa47f9395 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Wed, 15 Oct 2025 12:31:09 +0200 Subject: [PATCH 091/122] [Routing] Initialize router.request_context's _locale parameter to %kernel.default_locale% --- routing.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/routing.rst b/routing.rst index bf016faad02..753b4d4719d 100644 --- a/routing.rst +++ b/routing.rst @@ -2833,6 +2833,18 @@ Now you'll get the expected results when generating URLs in your commands:: value, but you can change it with the ``asset.request_context.base_path`` and ``asset.request_context.secure`` container parameters. +.. note:: + + By default, routes generated outside the HTTP context use the + :ref:`default locale ` as the value of the + ``_locale`` parameter. You can override this by providing a different value + for the ``_locale`` parameter when generating each route. + + .. versionadded:: 7.4 + + The default locale is used as the value of the ``_locale`` parameter + starting from Symfony 7.4. + Checking if a Route Exists ~~~~~~~~~~~~~~~~~~~~~~~~~~ From 4819340fd73aeba838fddab0eda21a8bb5e1e72a Mon Sep 17 00:00:00 2001 From: Antoine Lamirault Date: Sun, 19 Oct 2025 19:39:13 +0200 Subject: [PATCH 092/122] [Validator] Deprecate implicit constraint option names in YAML/XML mapping files --- forms.rst | 7 +++-- reference/constraints/All.rst | 7 ++--- reference/constraints/AtLeastOneOf.rst | 9 ++++--- reference/constraints/Callback.rst | 16 ++++++++---- reference/constraints/Charset.rst | 3 ++- reference/constraints/DivisibleBy.rst | 5 ++-- reference/constraints/EqualTo.rst | 5 ++-- reference/constraints/GreaterThan.rst | 19 +++++++++----- reference/constraints/GreaterThanOrEqual.rst | 26 +++++++++++++------ reference/constraints/IdenticalTo.rst | 5 ++-- reference/constraints/LessThan.rst | 26 +++++++++++++------ reference/constraints/LessThanOrEqual.rst | 26 +++++++++++++------ reference/constraints/Negative.rst | 2 +- reference/constraints/NegativeOrZero.rst | 2 +- .../constraints/NotCompromisedPassword.rst | 2 +- reference/constraints/NotEqualTo.rst | 5 ++-- reference/constraints/NotIdenticalTo.rst | 5 ++-- reference/constraints/PasswordStrength.rst | 2 +- reference/constraints/Positive.rst | 2 +- reference/constraints/PositiveOrZero.rst | 2 +- reference/constraints/Regex.rst | 3 ++- reference/constraints/Sequentially.rst | 10 ++++--- reference/constraints/Traverse.rst | 7 +++-- reference/constraints/Type.rst | 6 +++-- reference/constraints/UniqueEntity.rst | 3 ++- reference/constraints/When.rst | 7 +++-- 26 files changed, 141 insertions(+), 71 deletions(-) diff --git a/forms.rst b/forms.rst index 83065d7524b..aeeb4fb9385 100644 --- a/forms.rst +++ b/forms.rst @@ -513,7 +513,8 @@ object. - NotBlank: ~ dueDate: - NotBlank: ~ - - Type: \DateTimeInterface + - Type: + type: \DateTimeInterface .. code-block:: xml @@ -530,7 +531,9 @@ object. - \DateTimeInterface + + + diff --git a/reference/constraints/All.rst b/reference/constraints/All.rst index 43ff4d6ac9d..127b005a01c 100644 --- a/reference/constraints/All.rst +++ b/reference/constraints/All.rst @@ -41,9 +41,10 @@ entry in that array: properties: favoriteColors: - All: - - NotBlank: ~ - - Length: - min: 5 + constraints: + - NotBlank: ~ + - Length: + min: 5 .. code-block:: xml diff --git a/reference/constraints/AtLeastOneOf.rst b/reference/constraints/AtLeastOneOf.rst index fecbe617f5a..c4be1657dfc 100644 --- a/reference/constraints/AtLeastOneOf.rst +++ b/reference/constraints/AtLeastOneOf.rst @@ -53,7 +53,8 @@ The following constraints ensure that: properties: password: - AtLeastOneOf: - - Regex: '/#/' + - Regex: + pattern: '/#/' - Length: min: 10 grades: @@ -61,7 +62,9 @@ The following constraints ensure that: - Count: min: 3 - All: - - GreaterThanOrEqual: 5 + constraints: + - GreaterThanOrEqual: + value: 5 .. code-block:: xml @@ -93,7 +96,7 @@ The following constraints ensure that: diff --git a/reference/constraints/Callback.rst b/reference/constraints/Callback.rst index 017b9435cff..197312f1812 100644 --- a/reference/constraints/Callback.rst +++ b/reference/constraints/Callback.rst @@ -50,7 +50,8 @@ Configuration # config/validator/validation.yaml App\Entity\Author: constraints: - - Callback: validate + - Callback: + callback: validate .. code-block:: xml @@ -61,7 +62,9 @@ Configuration xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping https://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd"> - validate + + + @@ -177,7 +180,8 @@ You can then use the following configuration to invoke this validator: # config/validator/validation.yaml App\Entity\Author: constraints: - - Callback: [Acme\Validator, validate] + - Callback: + callback: [Acme\Validator, validate] .. code-block:: xml @@ -189,8 +193,10 @@ You can then use the following configuration to invoke this validator: - Acme\Validator - validate + diff --git a/reference/constraints/Charset.rst b/reference/constraints/Charset.rst index 084f24cdf76..746d0e44aa7 100644 --- a/reference/constraints/Charset.rst +++ b/reference/constraints/Charset.rst @@ -41,7 +41,8 @@ class uses UTF-8, you could do the following: App\Entity\FileDTO: properties: content: - - Charset: 'UTF-8' + - Charset: + charset: 'UTF-8' .. code-block:: xml diff --git a/reference/constraints/DivisibleBy.rst b/reference/constraints/DivisibleBy.rst index 23b36023cff..9685f46fe82 100644 --- a/reference/constraints/DivisibleBy.rst +++ b/reference/constraints/DivisibleBy.rst @@ -49,7 +49,8 @@ The following constraints ensure that: App\Entity\Item: properties: weight: - - DivisibleBy: 0.25 + - DivisibleBy: + value: 0.25 quantity: - DivisibleBy: value: 5 @@ -65,7 +66,7 @@ The following constraints ensure that: - 0.25 + diff --git a/reference/constraints/EqualTo.rst b/reference/constraints/EqualTo.rst index fdc402b1a97..d0b9c2b2c75 100644 --- a/reference/constraints/EqualTo.rst +++ b/reference/constraints/EqualTo.rst @@ -48,7 +48,8 @@ and that the ``age`` is ``20``, you could do the following: App\Entity\Person: properties: firstName: - - EqualTo: Mary + - EqualTo: + value: Mary age: - EqualTo: value: 20 @@ -64,7 +65,7 @@ and that the ``age`` is ``20``, you could do the following: - Mary + diff --git a/reference/constraints/GreaterThan.rst b/reference/constraints/GreaterThan.rst index d1b79028acd..c8b72851068 100644 --- a/reference/constraints/GreaterThan.rst +++ b/reference/constraints/GreaterThan.rst @@ -46,7 +46,8 @@ The following constraints ensure that: App\Entity\Person: properties: siblings: - - GreaterThan: 5 + - GreaterThan: + value: 5 age: - GreaterThan: value: 18 @@ -62,7 +63,7 @@ The following constraints ensure that: - 5 + @@ -182,7 +183,8 @@ dates. If you want to fix the timezone, append it to the date string: App\Entity\Order: properties: deliveryDate: - - GreaterThan: today UTC + - GreaterThan: + value: today UTC .. code-block:: xml @@ -194,7 +196,9 @@ dates. If you want to fix the timezone, append it to the date string: - today UTC + + + @@ -242,7 +246,8 @@ current time: App\Entity\Order: properties: deliveryDate: - - GreaterThan: +5 hours + - GreaterThan: + value: +5 hours .. code-block:: xml @@ -254,7 +259,9 @@ current time: - +5 hours + + + diff --git a/reference/constraints/GreaterThanOrEqual.rst b/reference/constraints/GreaterThanOrEqual.rst index 63c2ade6197..39eb81e917b 100644 --- a/reference/constraints/GreaterThanOrEqual.rst +++ b/reference/constraints/GreaterThanOrEqual.rst @@ -45,7 +45,8 @@ The following constraints ensure that: App\Entity\Person: properties: siblings: - - GreaterThanOrEqual: 5 + - GreaterThanOrEqual: + value: 5 age: - GreaterThanOrEqual: value: 18 @@ -61,7 +62,7 @@ The following constraints ensure that: - 5 + @@ -122,7 +123,8 @@ that a date must at least be the current day: App\Entity\Order: properties: deliveryDate: - - GreaterThanOrEqual: today + - GreaterThanOrEqual: + value: today .. code-block:: xml @@ -134,7 +136,9 @@ that a date must at least be the current day: - today + + + @@ -181,7 +185,8 @@ dates. If you want to fix the timezone, append it to the date string: App\Entity\Order: properties: deliveryDate: - - GreaterThanOrEqual: today UTC + - GreaterThanOrEqual: + value: today UTC .. code-block:: xml @@ -193,7 +198,9 @@ dates. If you want to fix the timezone, append it to the date string: - today UTC + + + @@ -241,7 +248,8 @@ current time: App\Entity\Order: properties: deliveryDate: - - GreaterThanOrEqual: +5 hours + - GreaterThanOrEqual: + value: +5 hours .. code-block:: xml @@ -253,7 +261,9 @@ current time: - +5 hours + + + diff --git a/reference/constraints/IdenticalTo.rst b/reference/constraints/IdenticalTo.rst index f8844f90a72..024ad7ad2a1 100644 --- a/reference/constraints/IdenticalTo.rst +++ b/reference/constraints/IdenticalTo.rst @@ -51,7 +51,8 @@ The following constraints ensure that: App\Entity\Person: properties: firstName: - - IdenticalTo: Mary + - IdenticalTo: + value: Mary age: - IdenticalTo: value: 20 @@ -67,7 +68,7 @@ The following constraints ensure that: - Mary + diff --git a/reference/constraints/LessThan.rst b/reference/constraints/LessThan.rst index 3d23bcda445..859d7bae83f 100644 --- a/reference/constraints/LessThan.rst +++ b/reference/constraints/LessThan.rst @@ -46,7 +46,8 @@ The following constraints ensure that: App\Entity\Person: properties: siblings: - - LessThan: 5 + - LessThan: + value: 5 age: - LessThan: value: 80 @@ -62,7 +63,7 @@ The following constraints ensure that: - 5 + @@ -123,7 +124,8 @@ that a date must be in the past like this: App\Entity\Person: properties: dateOfBirth: - - LessThan: today + - LessThan: + value: today .. code-block:: xml @@ -135,7 +137,9 @@ that a date must be in the past like this: - today + + + @@ -182,7 +186,8 @@ dates. If you want to fix the timezone, append it to the date string: App\Entity\Person: properties: dateOfBirth: - - LessThan: today UTC + - LessThan: + value: today UTC .. code-block:: xml @@ -194,7 +199,9 @@ dates. If you want to fix the timezone, append it to the date string: - today UTC + + + @@ -241,7 +248,8 @@ can check that a person must be at least 18 years old like this: App\Entity\Person: properties: dateOfBirth: - - LessThan: -18 years + - LessThan: + value: -18 years .. code-block:: xml @@ -253,7 +261,9 @@ can check that a person must be at least 18 years old like this: - -18 years + + + diff --git a/reference/constraints/LessThanOrEqual.rst b/reference/constraints/LessThanOrEqual.rst index ac66c62d7d0..40bdbebc739 100644 --- a/reference/constraints/LessThanOrEqual.rst +++ b/reference/constraints/LessThanOrEqual.rst @@ -45,7 +45,8 @@ The following constraints ensure that: App\Entity\Person: properties: siblings: - - LessThanOrEqual: 5 + - LessThanOrEqual: + value: 5 age: - LessThanOrEqual: value: 80 @@ -61,7 +62,7 @@ The following constraints ensure that: - 5 + @@ -122,7 +123,8 @@ that a date must be today or in the past like this: App\Entity\Person: properties: dateOfBirth: - - LessThanOrEqual: today + - LessThanOrEqual: + value: today .. code-block:: xml @@ -134,7 +136,9 @@ that a date must be today or in the past like this: - today + + + @@ -181,7 +185,8 @@ dates. If you want to fix the timezone, append it to the date string: App\Entity\Person: properties: dateOfBirth: - - LessThanOrEqual: today UTC + - LessThanOrEqual: + value: today UTC .. code-block:: xml @@ -193,7 +198,9 @@ dates. If you want to fix the timezone, append it to the date string: - today UTC + + + @@ -240,7 +247,8 @@ can check that a person must be at least 18 years old like this: App\Entity\Person: properties: dateOfBirth: - - LessThanOrEqual: -18 years + - LessThanOrEqual: + value: -18 years .. code-block:: xml @@ -252,7 +260,9 @@ can check that a person must be at least 18 years old like this: - -18 years + + + diff --git a/reference/constraints/Negative.rst b/reference/constraints/Negative.rst index 0d043ee8f6e..b47806f5291 100644 --- a/reference/constraints/Negative.rst +++ b/reference/constraints/Negative.rst @@ -50,7 +50,7 @@ The following constraint ensures that the ``withdraw`` of a bank account - + diff --git a/reference/constraints/NegativeOrZero.rst b/reference/constraints/NegativeOrZero.rst index 5f221950528..f6d94af84ee 100644 --- a/reference/constraints/NegativeOrZero.rst +++ b/reference/constraints/NegativeOrZero.rst @@ -49,7 +49,7 @@ is a negative number or equal to zero: - + diff --git a/reference/constraints/NotCompromisedPassword.rst b/reference/constraints/NotCompromisedPassword.rst index 6641f9d8cb2..b2ae775b0ce 100644 --- a/reference/constraints/NotCompromisedPassword.rst +++ b/reference/constraints/NotCompromisedPassword.rst @@ -49,7 +49,7 @@ The following constraint ensures that the ``rawPassword`` property of the - + diff --git a/reference/constraints/NotEqualTo.rst b/reference/constraints/NotEqualTo.rst index dd3f633b4a1..ce8ecb7dea5 100644 --- a/reference/constraints/NotEqualTo.rst +++ b/reference/constraints/NotEqualTo.rst @@ -50,7 +50,8 @@ the following: App\Entity\Person: properties: firstName: - - NotEqualTo: Mary + - NotEqualTo: + value: Mary age: - NotEqualTo: value: 15 @@ -66,7 +67,7 @@ the following: - Mary + diff --git a/reference/constraints/NotIdenticalTo.rst b/reference/constraints/NotIdenticalTo.rst index b2c20027292..a45731fa2e3 100644 --- a/reference/constraints/NotIdenticalTo.rst +++ b/reference/constraints/NotIdenticalTo.rst @@ -51,7 +51,8 @@ The following constraints ensure that: App\Entity\Person: properties: firstName: - - NotIdenticalTo: Mary + - NotIdenticalTo: + value: Mary age: - NotIdenticalTo: value: 15 @@ -67,7 +68,7 @@ The following constraints ensure that: - Mary + diff --git a/reference/constraints/PasswordStrength.rst b/reference/constraints/PasswordStrength.rst index 0b242cacf08..d1489938236 100644 --- a/reference/constraints/PasswordStrength.rst +++ b/reference/constraints/PasswordStrength.rst @@ -53,7 +53,7 @@ By default, the minimum required score is ``2``. - + diff --git a/reference/constraints/Positive.rst b/reference/constraints/Positive.rst index b43fdde67d8..84c3bc11099 100644 --- a/reference/constraints/Positive.rst +++ b/reference/constraints/Positive.rst @@ -50,7 +50,7 @@ positive number (greater than zero): - + diff --git a/reference/constraints/PositiveOrZero.rst b/reference/constraints/PositiveOrZero.rst index 4aa8420993c..f3c54c85394 100644 --- a/reference/constraints/PositiveOrZero.rst +++ b/reference/constraints/PositiveOrZero.rst @@ -49,7 +49,7 @@ is positive or zero: - + diff --git a/reference/constraints/Regex.rst b/reference/constraints/Regex.rst index e3b4d4711b2..1b48eba1dff 100644 --- a/reference/constraints/Regex.rst +++ b/reference/constraints/Regex.rst @@ -38,7 +38,8 @@ more word characters at the beginning of your string: App\Entity\Author: properties: description: - - Regex: '/^\w+/' + - Regex: + pattern: '/^\w+/' .. code-block:: xml diff --git a/reference/constraints/Sequentially.rst b/reference/constraints/Sequentially.rst index 078be338cdf..f2ec1cb0cb6 100644 --- a/reference/constraints/Sequentially.rst +++ b/reference/constraints/Sequentially.rst @@ -64,9 +64,11 @@ You can validate each of these constraints sequentially to solve these issues: address: - Sequentially: - NotNull: ~ - - Type: string + - Type: + value: string - Length: { min: 10 } - - Regex: !php/const App\Localization\Place::ADDRESS_REGEX + - Regex: + pattern: !php/const App\Localization\Place::ADDRESS_REGEX - App\Validator\Constraints\Geolocalizable: ~ .. code-block:: xml @@ -81,7 +83,9 @@ You can validate each of these constraints sequentially to solve these issues: - string + + + diff --git a/reference/constraints/Traverse.rst b/reference/constraints/Traverse.rst index 56d400fb964..d0e1254884d 100644 --- a/reference/constraints/Traverse.rst +++ b/reference/constraints/Traverse.rst @@ -167,7 +167,8 @@ disable validating: # config/validator/validation.yaml App\Entity\BookCollection: constraints: - - Traverse: false + - Traverse: + traverse: false .. code-block:: xml @@ -178,7 +179,9 @@ disable validating: xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping https://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd"> - false + + + diff --git a/reference/constraints/Type.rst b/reference/constraints/Type.rst index b49536dff8b..7d2b4ffaa89 100644 --- a/reference/constraints/Type.rst +++ b/reference/constraints/Type.rst @@ -58,10 +58,12 @@ The following example checks if ``emailAddress`` is an instance of ``Symfony\Com App\Entity\Author: properties: emailAddress: - - Type: Symfony\Component\Mime\Address + - Type: + type: Symfony\Component\Mime\Address firstName: - - Type: string + - Type: + type: string age: - Type: diff --git a/reference/constraints/UniqueEntity.rst b/reference/constraints/UniqueEntity.rst index 219ce4ed4ef..1ba0bcd27f6 100644 --- a/reference/constraints/UniqueEntity.rst +++ b/reference/constraints/UniqueEntity.rst @@ -56,7 +56,8 @@ between all of the rows in your user table: # config/validator/validation.yaml App\Entity\User: constraints: - - Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity: email + - Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity: + fields: email properties: email: - Email: ~ diff --git a/reference/constraints/When.rst b/reference/constraints/When.rst index 4b2e8eb7590..1c1292b8f17 100644 --- a/reference/constraints/When.rst +++ b/reference/constraints/When.rst @@ -85,7 +85,8 @@ One way to accomplish this is with the When constraint: App\Model\Discount: properties: value: - - GreaterThan: 0 + - GreaterThan: + value: 0 - When: expression: "this.getType() == 'percent'" constraints: @@ -106,7 +107,9 @@ One way to accomplish this is with the When constraint: xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping https://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd"> - 0 + + +