From c14a4e733e4ba53e7fc5746356465d8924b22f5c Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Sat, 7 Dec 2024 10:48:52 +0100 Subject: [PATCH 001/153] [AssetMapper] Document the feature that precompresses assets --- frontend/asset_mapper.rst | 61 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/frontend/asset_mapper.rst b/frontend/asset_mapper.rst index f519c7c6109..708de32979f 100644 --- a/frontend/asset_mapper.rst +++ b/frontend/asset_mapper.rst @@ -656,7 +656,9 @@ which will automatically do most of these things for you: - **Compress your assets**: Your web server should compress (e.g. using gzip) your assets (JavaScript, CSS, images) before sending them to the browser. This is automatically enabled in Caddy and can be activated in Nginx and Apache. - In Cloudflare, assets are compressed by default. + In Cloudflare, assets are compressed by default. AssetMapper also supports + :ref:`precompressing your web assets ` to further + improve performance. - **Set long-lived cache expiry**: Your web server should set a long-lived ``Cache-Control`` HTTP header on your assets. Because the AssetMapper component includes a version @@ -704,6 +706,57 @@ even though it hasn't yet seen the ``import`` statement for them. Additionally, if the :doc:`WebLink Component ` is available in your application, Symfony will add a ``Link`` header in the response to preload the CSS files. +.. _performance-precompressing: + +Pre-Compressing Assets +---------------------- + +Although most servers (Caddy, Nginx, Apache, FrankenPHP) and services like Cloudflare +provide asset compression features, AssetMapper also allows you to compress all +your assets before serving them. + +This improves performance because you can compress assets using the highest (and +slowest) compression ratios beforehand and provide those compressed assets to the +server, which then returns them to the client without wasting CPU resources on +compression. + +AssetMapper supports `Brotli`_, `Zstandard`_ and `gzip`_ compression formats. +Before using any of them, the server that pre-compresses assets must have +installed the following PHP extensions or CLI commands: + +* Brotli: ``brotli`` CLI command; `brotli PHP extension`_; +* Zstandard: ``zstd`` CLI command; `zstd PHP extension`_; +* gzip: ``gzip`` CLI command; `zlib PHP extension`_. + +Then, update your AssetMapper configuration to define which compression to use +and which file extensions should be compressed: + +.. code-block:: yaml + + # config/packages/asset_mapper.yaml + framework: + asset_mapper: + # ... + + precompress: + format: 'zstandard' + # if you don't define the following option, AssetMapper will compress all + # the extensions considered safe (css, js, json, svg, xml, ttf, otf, wasm, etc.) + extensions: ['css', 'js', 'json', 'svg', 'xml'] + +Now, when running the ``asset-map:compile`` command, all matching files will be +compressed in the configured format and at the highest compression level. The +compressed files are created with the same name as the original but with the +``.br``, ``.zst``, or ``.gz`` extension appended. Web servers that support asset +precompression will use the compressed assets automatically, so there's nothing +else to configure in your server. + +.. tip:: + + AssetMapper provides an ``assets:compress`` CLI command and a service called + ``asset_mapper.compressor`` that you can use anywhere in your application to + compress any kind of files (e.g. files uploaded by users to your application). + Frequently Asked Questions -------------------------- @@ -1195,3 +1248,9 @@ command as part of your CI to be warned anytime a new vulnerability is found. .. _strict-dynamic: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src#strict-dynamic .. _kocal/biome-js-bundle: https://github.com/Kocal/BiomeJsBundle .. _`SensioLabs Minify Bundle`: https://github.com/sensiolabs/minify-bundle +.. _`Brotli`: https://en.wikipedia.org/wiki/Brotli +.. _`Zstandard`: https://en.wikipedia.org/wiki/Zstd +.. _`gzip`: https://en.wikipedia.org/wiki/Gzip +.. _`brotli PHP extension`: https://pecl.php.net/package/brotli +.. _`zstd PHP extension`: https://pecl.php.net/package/zstd +.. _`zlib PHP extension`: https://www.php.net/manual/en/book.zlib.php From 1d690fc9714cf2ebb6506c3656e6aa6e7909c33a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Sat, 7 Dec 2024 17:04:18 +0100 Subject: [PATCH 002/153] [AssetMapper] mention Zopfli pre-compression and add config for web servers --- frontend/asset_mapper.rst | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/frontend/asset_mapper.rst b/frontend/asset_mapper.rst index b55803e157f..e83da5aa42b 100644 --- a/frontend/asset_mapper.rst +++ b/frontend/asset_mapper.rst @@ -726,7 +726,7 @@ installed the following PHP extensions or CLI commands: * Brotli: ``brotli`` CLI command; `brotli PHP extension`_; * Zstandard: ``zstd`` CLI command; `zstd PHP extension`_; -* gzip: ``gzip`` CLI command; `zlib PHP extension`_. +* gzip: ``zopfli`` (better) or ``gzip`` CLI command; `zlib PHP extension`_. Then, update your AssetMapper configuration to define which compression to use and which file extensions should be compressed: @@ -751,6 +751,27 @@ compressed files are created with the same name as the original but with the precompression will use the compressed assets automatically, so there's nothing else to configure in your server. +Finally, you need to configure your web server to serve the precompressed assets +instead of the original ones: + +.. configuration-block:: + + .. code-block:: caddy + + file_server { + precompressed br zstd gzip + } + + .. code-block:: nginx + + gzip_static on; + + # Requires https://github.com/google/ngx_brotli + brotli_static on; + + # Requires https://github.com/tokers/zstd-nginx-module + zstd_static on; + .. tip:: AssetMapper provides an ``assets:compress`` CLI command and a service called From f87bbdf690f629b3096a1f8d0848f471d5e966ed Mon Sep 17 00:00:00 2001 From: Mathias Arlaud Date: Wed, 11 Dec 2024 10:38:18 +0100 Subject: [PATCH 003/153] [HttpFoundation] Add StreamedResponse string iterable documentation --- components/http_foundation.rst | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/components/http_foundation.rst b/components/http_foundation.rst index c91ec5ced8f..8db6cd27f68 100644 --- a/components/http_foundation.rst +++ b/components/http_foundation.rst @@ -681,8 +681,19 @@ Streaming a Response ~~~~~~~~~~~~~~~~~~~~ The :class:`Symfony\\Component\\HttpFoundation\\StreamedResponse` class allows -you to stream the Response back to the client. The response content is -represented by a PHP callable instead of a string:: +you to stream the Response back to the client. The response content can be +represented by a string iterable:: + + use Symfony\Component\HttpFoundation\StreamedResponse; + + $chunks = ['Hello', ' World']; + + $response = new StreamedResponse(); + $response->setChunks($chunks); + $response->send(); + +For most complex use cases, the response content can be instead represented by +a PHP callable:: use Symfony\Component\HttpFoundation\StreamedResponse; @@ -710,6 +721,10 @@ represented by a PHP callable instead of a string:: // disables FastCGI buffering in nginx only for this response $response->headers->set('X-Accel-Buffering', 'no'); +.. versionadded:: 7.3 + + Support for using string iterables was introduced in Symfony 7.3. + Streaming a JSON Response ~~~~~~~~~~~~~~~~~~~~~~~~~ From b66e743657a527d4c14e3e4a1330893c6956cc4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20L=C3=A9v=C3=AAque?= Date: Wed, 11 Dec 2024 17:11:32 +0100 Subject: [PATCH 004/153] [Console] Add support of millisecondes for formatTime --- components/console/helpers/formatterhelper.rst | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/components/console/helpers/formatterhelper.rst b/components/console/helpers/formatterhelper.rst index 3cb87c4c307..bff0c82c90f 100644 --- a/components/console/helpers/formatterhelper.rst +++ b/components/console/helpers/formatterhelper.rst @@ -129,10 +129,12 @@ Sometimes you want to format seconds to time. This is possible with the The first argument is the seconds to format and the second argument is the precision (default ``1``) of the result:: - Helper::formatTime(42); // 42 secs - Helper::formatTime(125); // 2 mins - Helper::formatTime(125, 2); // 2 mins, 5 secs - Helper::formatTime(172799, 4); // 1 day, 23 hrs, 59 mins, 59 secs + Helper::formatTime(0.001); // 1 ms + Helper::formatTime(42); // 42 s + Helper::formatTime(125); // 2 min + Helper::formatTime(125, 2); // 2 min, 5 s + Helper::formatTime(172799, 4); // 1 d, 23 h, 59 min, 59 s + Helper::formatTime(172799.056, 5); // 1 d, 23 h, 59 min, 59 s, 56 ms Formatting Memory ----------------- From bf5dd100d61c54ed6e4ed729198d2d04a64940a0 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Thu, 12 Dec 2024 10:56:45 +0100 Subject: [PATCH 005/153] Add the versionadded directive --- components/console/helpers/formatterhelper.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/components/console/helpers/formatterhelper.rst b/components/console/helpers/formatterhelper.rst index bff0c82c90f..d2b19915a3a 100644 --- a/components/console/helpers/formatterhelper.rst +++ b/components/console/helpers/formatterhelper.rst @@ -136,6 +136,10 @@ precision (default ``1``) of the result:: Helper::formatTime(172799, 4); // 1 d, 23 h, 59 min, 59 s Helper::formatTime(172799.056, 5); // 1 d, 23 h, 59 min, 59 s, 56 ms +.. versionadded:: 7.3 + + Support for formatting up to milliseconds was introduced in Symfony 7.3. + Formatting Memory ----------------- From eb5ffa691cc0239d89c72d9ca74897b2e85b23fc Mon Sep 17 00:00:00 2001 From: Baptiste Leduc Date: Thu, 12 Dec 2024 11:48:47 +0100 Subject: [PATCH 006/153] Fix PHP block in TypeInfo documentation --- components/type_info.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/type_info.rst b/components/type_info.rst index 734ac4140ba..e7062c5f249 100644 --- a/components/type_info.rst +++ b/components/type_info.rst @@ -141,7 +141,7 @@ Checking a **simple type**:: $type->isIdentifiedBy(DummyParent::class); // true $type->isIdentifiedBy(DummyInterface::class); // true -Using callables for **complex checks**: +Using callables for **complex checks**:: class Foo { From 2fdd0dbfd61d9bd93d679c8febedcddca40d5e3a Mon Sep 17 00:00:00 2001 From: Felix Eymonot Date: Tue, 17 Dec 2024 12:19:23 +0100 Subject: [PATCH 007/153] docs(controller): add docs for key option in MapQueryString --- controller.rst | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/controller.rst b/controller.rst index c11615d93aa..5da85057bbe 100644 --- a/controller.rst +++ b/controller.rst @@ -443,6 +443,27 @@ HTTP status to return if the validation fails:: The default status code returned if the validation fails is 404. +If you want to map your object to a nested array in your query with a specific key, +you can use the ``key`` option in your :class:`Symfony\\Component\\HttpKernel\\Attribute\\MapQueryString` +attribute:: + + use App\Model\SearchDto; + use Symfony\Component\HttpFoundation\Response; + use Symfony\Component\HttpKernel\Attribute\MapQueryString; + + // ... + + public function dashboard( + #[MapQueryString(key: 'search')] SearchDto $searchDto + ): Response + { + // ... + } + +.. versionadded:: 7.3 + + The ``key`` option of ``#[MapQueryString]`` was introduced in Symfony 7.3. + If you need a valid DTO even when the request query string is empty, set a default value for your controller arguments:: From c196b447547b28048a9f1d5849f6964e982933ad Mon Sep 17 00:00:00 2001 From: Nate Wiebe Date: Thu, 9 Mar 2023 09:10:30 -0500 Subject: [PATCH 008/153] Add tip for using new isGrantedForUser() function --- security.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/security.rst b/security.rst index c8dfc8d6233..5da16121b30 100644 --- a/security.rst +++ b/security.rst @@ -2585,6 +2585,18 @@ want to include extra details only for users that have a ``ROLE_SALES_ADMIN`` ro // ... } +.. tip:: + + Using ``isGranted()`` checks authorization against the currently logged in user. If you need to check + against a user that is not the one logged in or if checking authorization when the user session is not + available in a CLI context (example: message queue, cronjob) ``isGrantedForUser()`` can be used to set the + target user explicitly. + + .. versionadded:: 7.3 + + The :method:`Symfony\\Bundle\\SecurityBundle\\Security::isGrantedForUser` + method was introduced in Symfony 7.3. + If you're using the :ref:`default services.yaml configuration `, Symfony will automatically pass the ``security.helper`` to your service thanks to autowiring and the ``Security`` type-hint. From ccdeed257e1e882374a185b18437933694b94bb7 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Thu, 19 Dec 2024 17:47:14 +0100 Subject: [PATCH 009/153] Minor tweak --- controller.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/controller.rst b/controller.rst index 5da85057bbe..026e76ed0a6 100644 --- a/controller.rst +++ b/controller.rst @@ -443,9 +443,8 @@ HTTP status to return if the validation fails:: The default status code returned if the validation fails is 404. -If you want to map your object to a nested array in your query with a specific key, -you can use the ``key`` option in your :class:`Symfony\\Component\\HttpKernel\\Attribute\\MapQueryString` -attribute:: +If you want to map your object to a nested array in your query using a specific key, +set the ``key`` option in the ``#[MapQueryString]`` attribute:: use App\Model\SearchDto; use Symfony\Component\HttpFoundation\Response; From 47e65dfb47d70815d3bbe8e7c809c421b2ad4ec9 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Thu, 26 Dec 2024 11:17:56 +0100 Subject: [PATCH 010/153] Update the Symfony version to install in 7.3 branch --- setup.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/setup.rst b/setup.rst index 853b53f2400..b269da2c476 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.2.x" --webapp + $ symfony new my_project_directory --version="7.3.x-dev" --webapp # run this if you are building a microservice, console application or API - $ symfony new my_project_directory --version="7.2.x" + $ symfony new my_project_directory --version="7.3.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.2.x" my_project_directory + $ composer create-project symfony/skeleton:"7.3.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.2.x" my_project_directory + $ composer create-project symfony/skeleton:"7.3.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 04790be521b670ddffbee654c55da3bc165dfd55 Mon Sep 17 00:00:00 2001 From: Matthieu Lempereur Date: Mon, 30 Dec 2024 08:38:13 +0100 Subject: [PATCH 011/153] [Validator] Validate SVG ratio in Image validator --- reference/constraints/Image.rst | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/reference/constraints/Image.rst b/reference/constraints/Image.rst index 48dc1d5e0ed..23fbe5a157a 100644 --- a/reference/constraints/Image.rst +++ b/reference/constraints/Image.rst @@ -210,6 +210,10 @@ add several other options. If this option is false, the image cannot be landscape oriented. +.. versionadded:: 7.3 + + The ``allowLandscape`` option support for SVG files was introduced in Symfony 7.3. + ``allowLandscapeMessage`` ~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -234,6 +238,10 @@ Parameter Description If this option is false, the image cannot be portrait oriented. +.. versionadded:: 7.3 + + The ``allowPortrait`` option support for SVG files was introduced in Symfony 7.3. + ``allowPortraitMessage`` ~~~~~~~~~~~~~~~~~~~~~~~~ @@ -260,6 +268,10 @@ If this option is false, the image cannot be a square. If you want to force a square image, then leave this option as its default ``true`` value and set `allowLandscape`_ and `allowPortrait`_ both to ``false``. +.. versionadded:: 7.3 + + The ``allowSquare`` option support for SVG files was introduced in Symfony 7.3. + ``allowSquareMessage`` ~~~~~~~~~~~~~~~~~~~~~~ @@ -358,6 +370,10 @@ Parameter Description If set, the aspect ratio (``width / height``) of the image file must be less than or equal to this value. +.. versionadded:: 7.3 + + The ``maxRatio`` option support for SVG files was introduced in Symfony 7.3. + ``maxRatioMessage`` ~~~~~~~~~~~~~~~~~~~ @@ -477,6 +493,10 @@ Parameter Description If set, the aspect ratio (``width / height``) of the image file must be greater than or equal to this value. +.. versionadded:: 7.3 + + The ``minRatio`` option support for SVG files was introduced in Symfony 7.3. + ``minRatioMessage`` ~~~~~~~~~~~~~~~~~~~ From 3596f8c2b09f3380773a42a38b30f152fada315c Mon Sep 17 00:00:00 2001 From: Oskar Stark Date: Thu, 2 Jan 2025 12:59:47 +0100 Subject: [PATCH 012/153] Add ifFalse() --- components/config/definition.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/components/config/definition.rst b/components/config/definition.rst index 0e626931568..24c142ec5a5 100644 --- a/components/config/definition.rst +++ b/components/config/definition.rst @@ -815,6 +815,7 @@ A validation rule always has an "if" part. You can specify this part in the following ways: - ``ifTrue()`` +- ``ifFalse()`` - ``ifString()`` - ``ifNull()`` - ``ifEmpty()`` @@ -833,6 +834,10 @@ A validation rule also requires a "then" part: Usually, "then" is a closure. Its return value will be used as a new value for the node, instead of the node's original value. +.. versionadded:: 7.3 + + The ``ifFalse()`` method was introduced in Symfony 7.3. + Configuring the Node Path Separator ----------------------------------- From e1c23c76557c1bad82005f61effb685fba775c6c Mon Sep 17 00:00:00 2001 From: Nate Wiebe Date: Thu, 2 Jan 2025 10:20:16 -0500 Subject: [PATCH 013/153] Add docs for is_granted_for_user() function --- reference/twig_reference.rst | 21 +++++++++++++++++++++ security.rst | 11 +++++++++++ 2 files changed, 32 insertions(+) diff --git a/reference/twig_reference.rst b/reference/twig_reference.rst index d8b802dd73e..4485494bd9c 100644 --- a/reference/twig_reference.rst +++ b/reference/twig_reference.rst @@ -174,6 +174,27 @@ Returns ``true`` if the current user has the given role. Optionally, an object can be passed to be used by the voter. More information can be found in :ref:`security-template`. +is_granted_for_user +~~~~~~~~~~~~~~~~~~~ + +.. code-block:: twig + + {{ is_granted_for_user(user, attribute, subject = null, field = null) }} + +``user`` + **type**: ``object`` +``attribute`` + **type**: ``string`` +``subject`` *(optional)* + **type**: ``object`` +``field`` *(optional)* + **type**: ``string`` + +Returns ``true`` if the user is authorized for the specified attribute. + +Optionally, an object can be passed to be used by the voter. More information +can be found in :ref:`security-template`. + logout_path ~~~~~~~~~~~ diff --git a/security.rst b/security.rst index 8026c63484b..7fe452e4fae 100644 --- a/security.rst +++ b/security.rst @@ -2549,6 +2549,17 @@ the built-in ``is_granted()`` helper function in any Twig template: .. _security-isgranted: +Similarly, if you want to check if a specific user has a certain role, you can use +the built-in ``is_granted_for_user()`` helper function: + +.. code-block:: html+twig + + {% if is_granted_for_user(user, 'ROLE_ADMIN') %} + Delete + {% endif %} + +.. _security-isgrantedforuser: + Securing other Services ....................... From ddf1ad3975a30bd1b3fce6528c4795646674508e Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Fri, 3 Jan 2025 13:17:39 +0100 Subject: [PATCH 014/153] Add the missing versionadded directive --- reference/twig_reference.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/reference/twig_reference.rst b/reference/twig_reference.rst index 4485494bd9c..e01fa3f80e3 100644 --- a/reference/twig_reference.rst +++ b/reference/twig_reference.rst @@ -177,6 +177,10 @@ can be found in :ref:`security-template`. is_granted_for_user ~~~~~~~~~~~~~~~~~~~ +.. versionadded:: 7.3 + + The ``is_granted_for_user()`` function was introduced in Symfony 7.3. + .. code-block:: twig {{ is_granted_for_user(user, attribute, subject = null, field = null) }} From effffa3bb0bcdbb7016d7bd415cd23c4f01fd948 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Fri, 3 Jan 2025 14:55:02 +0100 Subject: [PATCH 015/153] Minor tweak --- frontend/asset_mapper.rst | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/frontend/asset_mapper.rst b/frontend/asset_mapper.rst index 81e91c1fc9d..d68c77c0105 100644 --- a/frontend/asset_mapper.rst +++ b/frontend/asset_mapper.rst @@ -747,11 +747,9 @@ and which file extensions should be compressed: Now, when running the ``asset-map:compile`` command, all matching files will be compressed in the configured format and at the highest compression level. The compressed files are created with the same name as the original but with the -``.br``, ``.zst``, or ``.gz`` extension appended. Web servers that support asset -precompression will use the compressed assets automatically, so there's nothing -else to configure in your server. +``.br``, ``.zst``, or ``.gz`` extension appended. -Finally, you need to configure your web server to serve the precompressed assets +Then, you need to configure your web server to serve the precompressed assets instead of the original ones: .. configuration-block:: From 9b65586c1ee8ec15873d290b6eaa01e8bd2dc062 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Fri, 3 Jan 2025 16:43:16 +0100 Subject: [PATCH 016/153] Fix build --- validation/custom_constraint.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/validation/custom_constraint.rst b/validation/custom_constraint.rst index 990e1de9371..59da9e60568 100644 --- a/validation/custom_constraint.rst +++ b/validation/custom_constraint.rst @@ -66,7 +66,7 @@ Constraint with Private Properties ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Constraints are cached for performance reasons. To achieve this, the base -``Constraint`` class uses PHP's :phpfunction:`get_object_vars()` function, which +``Constraint`` class uses PHP's :phpfunction:`get_object_vars` function, which excludes private properties of child classes. If your constraint defines private properties, you must explicitly include them From 1272701ec6e1d15e9bf21d6ab55445d6f68838ba Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Fri, 3 Jan 2025 16:19:25 +0100 Subject: [PATCH 017/153] [DependencyInjection] Make #[AsTaggedItem] repeatable --- service_container/tags.rst | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/service_container/tags.rst b/service_container/tags.rst index 270d6702f5a..bf50a191ba3 100644 --- a/service_container/tags.rst +++ b/service_container/tags.rst @@ -1281,4 +1281,19 @@ be used directly on the class of the service you want to configure:: // ... } +You can apply the ``#[AsTaggedItem]`` attribute multiple times to register the +same service under different indexes: + + #[AsTaggedItem(index: 'handler_one', priority: 5)] + #[AsTaggedItem(index: 'handler_two', priority: 20)] + class SomeService + { + // ... + } + +.. versionadded:: 7.3 + + The feature to apply the ``#[AsTaggedItem]`` attribute multiple times was + introduced in Symfony 7.3. + .. _`PHP constructor promotion`: https://www.php.net/manual/en/language.oop5.decon.php#language.oop5.decon.constructor.promotion From 9d4972de1863f3a028c6ce5d5e8d4ad2ac866201 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Fri, 3 Jan 2025 16:52:36 +0100 Subject: [PATCH 018/153] Minor reword --- security.rst | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/security.rst b/security.rst index baa1a32a3a7..92367f29a88 100644 --- a/security.rst +++ b/security.rst @@ -2596,12 +2596,13 @@ want to include extra details only for users that have a ``ROLE_SALES_ADMIN`` ro // ... } + .. tip:: - Using ``isGranted()`` checks authorization against the currently logged in user. If you need to check - against a user that is not the one logged in or if checking authorization when the user session is not - available in a CLI context (example: message queue, cronjob) ``isGrantedForUser()`` can be used to set the - target user explicitly. + The ``isGranted()`` method checks authorization for the currently logged-in user. + If you need to check authorization for a different user or when the user session + is unavailable (e.g., in a CLI context such as a message queue or cron job), you + can use the ``isGrantedForUser()`` method to explicitly set the target user. .. versionadded:: 7.3 From 1afb8ebf3663a634108eac91b3d74ee20631d0af Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Fri, 3 Jan 2025 15:57:26 +0100 Subject: [PATCH 019/153] [HttpFoundation] Generate url-safe hashes for signed urls --- routing.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/routing.rst b/routing.rst index ef3287dd1be..4ab0dce2a82 100644 --- a/routing.rst +++ b/routing.rst @@ -2802,6 +2802,11 @@ argument of :method:`Symfony\\Component\\HttpFoundation\\UriSigner::sign`:: The feature to add an expiration date for a signed URI was introduced in Symfony 7.1. +.. versionadded:: 7.3 + + Starting with Symfony 7.3, signed URI hashes no longer include the ``/`` or + ``+`` characters, as these may cause issues with certain clients. + Troubleshooting --------------- From ab691630aaeeabe52ae7717be558faa560968169 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Mon, 6 Jan 2025 09:11:25 +0100 Subject: [PATCH 020/153] [Yaml] Add support for dumping `null` as an empty value by using the `Yaml::DUMP_NULL_AS_EMPTY` flag --- components/yaml.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/components/yaml.rst b/components/yaml.rst index 30f715a7a24..58436adffc2 100644 --- a/components/yaml.rst +++ b/components/yaml.rst @@ -428,6 +428,16 @@ you can dump them as ``~`` with the ``DUMP_NULL_AS_TILDE`` flag:: $dumped = Yaml::dump(['foo' => null], 2, 4, Yaml::DUMP_NULL_AS_TILDE); // foo: ~ +Another valid representation of the ``null`` value is an empty string. You can +use the ``DUMP_NULL_AS_EMPTY`` flag to dump null values as empty strings:: + + $dumped = Yaml::dump(['foo' => null], 2, 4, Yaml::DUMP_NULL_AS_EMPTY); + // foo: + +.. versionadded:: 7.3 + + The ``DUMP_NULL_AS_EMPTY`` flag was introduced in Symfony 7.3. + Dumping Numeric Keys as Strings ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From 7d7e77cafbabba22b2443f4edc9fc13e3aba1bdf Mon Sep 17 00:00:00 2001 From: Antoine Lamirault Date: Mon, 6 Jan 2025 20:48:52 +0100 Subject: [PATCH 021/153] [PropertyInfo] Add PropertyDescriptionExtractorInterface to PhpStanExtractor --- components/property_info.rst | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/components/property_info.rst b/components/property_info.rst index 2e1ee42dd3f..0ff1768441a 100644 --- a/components/property_info.rst +++ b/components/property_info.rst @@ -469,7 +469,18 @@ information from annotations of properties and methods, such as ``@var``, use App\Domain\Foo; $phpStanExtractor = new PhpStanExtractor(); + + // Type information. $phpStanExtractor->getTypesFromConstructor(Foo::class, 'bar'); + // Description information. + $phpStanExtractor->getShortDescription($class, 'bar'); + $phpStanExtractor->getLongDescription($class, 'bar'); + +.. versionadded:: 7.3 + + The :method:`Symfony\\Component\\PropertyInfo\\Extractor\\PhpStanExtractor::getShortDescription` + and :method:`Symfony\\Component\\PropertyInfo\\Extractor\\PhpStanExtractor::getLongDescription` + methods were introduced in Symfony 7.3. SerializerExtractor ~~~~~~~~~~~~~~~~~~~ From 6d16cd8b4e41fa0b1686c44edb93b1f77023cd6f Mon Sep 17 00:00:00 2001 From: Antoine Lamirault Date: Mon, 6 Jan 2025 21:15:30 +0100 Subject: [PATCH 022/153] [Mailer] Add retry_period option for email transport --- mailer.rst | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/mailer.rst b/mailer.rst index 4f0619147ad..8cc0d8641e3 100644 --- a/mailer.rst +++ b/mailer.rst @@ -331,6 +331,17 @@ The failover-transport starts using the first transport and if it fails, it will retry the same delivery with the next transports until one of them succeeds (or until all of them fail). +By default, the delivery will be retried 60 seconds after previous sending failed. +You can change the retry period by setting the ``retry_period`` option in the DSN: + +.. code-block:: env + + MAILER_DSN="failover(postmark+api://ID@default sendgrid+smtp://KEY@default)?retry_period=15" + +.. versionadded:: 7.3 + + The ``retry_period`` option was introduced in Symfony 7.3. + Load Balancing ~~~~~~~~~~~~~~ @@ -351,6 +362,17 @@ As with the failover transport, round-robin retries deliveries until a transport succeeds (or all fail). In contrast to the failover transport, it *spreads* the load across all its transports. +By default, the delivery will be retried 60 seconds after previous sending failed. +You can change the retry period by setting the ``retry_period`` option in the DSN: + +.. code-block:: env + + MAILER_DSN="roundrobin(postmark+api://ID@default sendgrid+smtp://KEY@default)?retry_period=15" + +.. versionadded:: 7.3 + + The ``retry_period`` option was introduced in Symfony 7.3. + TLS Peer Verification ~~~~~~~~~~~~~~~~~~~~~ From 7d94c8e8fc64032721af5415d8a3c0fe018ecf71 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Tue, 7 Jan 2025 08:42:04 +0100 Subject: [PATCH 023/153] Minor reword --- mailer.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mailer.rst b/mailer.rst index 8cc0d8641e3..4fd02eb84d7 100644 --- a/mailer.rst +++ b/mailer.rst @@ -331,8 +331,8 @@ The failover-transport starts using the first transport and if it fails, it will retry the same delivery with the next transports until one of them succeeds (or until all of them fail). -By default, the delivery will be retried 60 seconds after previous sending failed. -You can change the retry period by setting the ``retry_period`` option in the DSN: +By default, delivery is retried 60 seconds after a failed attempt. You can adjust +the retry period by setting the ``retry_period`` option in the DSN: .. code-block:: env @@ -362,8 +362,8 @@ As with the failover transport, round-robin retries deliveries until a transport succeeds (or all fail). In contrast to the failover transport, it *spreads* the load across all its transports. -By default, the delivery will be retried 60 seconds after previous sending failed. -You can change the retry period by setting the ``retry_period`` option in the DSN: +By default, delivery is retried 60 seconds after a failed attempt. You can adjust +the retry period by setting the ``retry_period`` option in the DSN: .. code-block:: env From d1400d406951f664bd159e38eab33c5a93cb73e9 Mon Sep 17 00:00:00 2001 From: Iker Ibarguren Date: Tue, 7 Jan 2025 09:51:40 +0100 Subject: [PATCH 024/153] Update notifier.rst Brevo third-party service now supports webhooks. --- notifier.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/notifier.rst b/notifier.rst index 36fbd5ada89..5b3f12a67fa 100644 --- a/notifier.rst +++ b/notifier.rst @@ -76,7 +76,7 @@ Service **Webhook support**: No `Brevo`_ **Install**: ``composer require symfony/brevo-notifier`` \ **DSN**: ``brevo://API_KEY@default?sender=SENDER`` \ - **Webhook support**: No + **Webhook support**: Yes `Clickatell`_ **Install**: ``composer require symfony/clickatell-notifier`` \ **DSN**: ``clickatell://ACCESS_TOKEN@default?from=FROM`` \ **Webhook support**: No From e4f8ff86daa93eab17172a0fd001caccd5c93e55 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Tue, 7 Jan 2025 12:04:07 +0100 Subject: [PATCH 025/153] Add a versionadded directive --- notifier.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/notifier.rst b/notifier.rst index 5b3f12a67fa..090f4db2007 100644 --- a/notifier.rst +++ b/notifier.rst @@ -236,6 +236,10 @@ Service The ``Primotexto``, ``Sipgate`` and ``Sweego`` integrations were introduced in Symfony 7.2. +.. versionadded:: 7.3 + + Webhook support for the ``Brevo`` integration was introduced in Symfony 7.3. + .. deprecated:: 7.1 The `Sms77`_ integration is deprecated since From 17a46adc143905ee469e48118039c7e8f66760b5 Mon Sep 17 00:00:00 2001 From: Maxime Doutreluingne Date: Sun, 5 Jan 2025 17:34:40 +0100 Subject: [PATCH 026/153] [Validator] [DateTime] Add ``format`` to error messages --- reference/constraints/DateTime.rst | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/reference/constraints/DateTime.rst b/reference/constraints/DateTime.rst index f6bcce7e5f5..ffcfbf55dda 100644 --- a/reference/constraints/DateTime.rst +++ b/reference/constraints/DateTime.rst @@ -99,11 +99,16 @@ This message is shown if the underlying data is not a valid datetime. You can use the following parameters in this message: -=============== ============================================================== -Parameter Description -=============== ============================================================== -``{{ value }}`` The current (invalid) value -``{{ label }}`` Corresponding form field label -=============== ============================================================== +================ ============================================================== +Parameter Description +================ ============================================================== +``{{ value }}`` The current (invalid) value +``{{ label }}`` Corresponding form field label +``{{ format }}`` The date format defined in ``format`` +================ ============================================================== + +.. versionadded:: 7.3 + + The ``{{ format }}`` parameter was introduced in Symfony 7.3. .. include:: /reference/constraints/_payload-option.rst.inc From 45113124f7b2affdcceae4460f218b4efc740839 Mon Sep 17 00:00:00 2001 From: tcoch Date: Mon, 6 Jan 2025 09:22:34 +0100 Subject: [PATCH 027/153] [String] Add `AbstractString::pascal()` method --- string.rst | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/string.rst b/string.rst index 43d3a236ab6..e51e7d1b502 100644 --- a/string.rst +++ b/string.rst @@ -234,8 +234,10 @@ Methods to Change Case u('Foo: Bar-baz.')->snake(); // 'foo_bar_baz' // changes all graphemes/code points to kebab-case u('Foo: Bar-baz.')->kebab(); // 'foo-bar-baz' - // other cases can be achieved by chaining methods. E.g. PascalCase: - u('Foo: Bar-baz.')->camel()->title(); // 'FooBarBaz' + // changes all graphemes/code points to PascalCase + u('Foo: Bar-baz.')->pascal(); // 'FooBarBaz' + // other cases can be achieved by chaining methods, e.g. : + u('Foo: Bar-baz.')->camel()->upper(); // 'FOOBARBAZ' .. versionadded:: 7.1 @@ -246,6 +248,10 @@ Methods to Change Case The ``kebab()`` method was introduced in Symfony 7.2. +.. versionadded:: 7.3 + + The ``pascal()`` method was introduced in Symfony 7.3. + The methods of all string classes are case-sensitive by default. You can perform case-insensitive operations with the ``ignoreCase()`` method:: From 951f461e329cb0c8f275ca9100b2a5789844afec Mon Sep 17 00:00:00 2001 From: Mathias Arlaud Date: Tue, 7 Jan 2025 16:08:07 +0100 Subject: [PATCH 028/153] [TypeInfo] Add `accepts` method --- components/type_info.rst | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/components/type_info.rst b/components/type_info.rst index e7062c5f249..70280d9a348 100644 --- a/components/type_info.rst +++ b/components/type_info.rst @@ -114,7 +114,7 @@ Advanced Usages The TypeInfo component provides various methods to manipulate and check types, depending on your needs. -Checking a **simple type**:: +**Identify** a type:: // define a simple integer type $type = Type::int(); @@ -141,6 +141,23 @@ Checking a **simple type**:: $type->isIdentifiedBy(DummyParent::class); // true $type->isIdentifiedBy(DummyInterface::class); // true +Checking if a type **accepts a value**:: + + $type = Type::int(); + // check if the type accepts a given value + $type->accepts(123); // true + $type->accepts('z'); // false + + $type = Type::union(Type::string(), Type::int()); + // now the second check is true because the union type accepts either a int and a string value + $type->accepts(123); // true + $type->accepts('z'); // true + +.. versionadded:: 7.3 + + The :method:`Symfony\\Component\\TypeInfo\\Type::accepts` + method was introduced in Symfony 7.3. + Using callables for **complex checks**:: class Foo From b4f8f6bf5f596664eb36c6311107b0b0056fd166 Mon Sep 17 00:00:00 2001 From: tcoch Date: Mon, 6 Jan 2025 17:22:10 +0100 Subject: [PATCH 029/153] [Validator] Add Slug constraint --- reference/constraints/Slug.rst | 119 ++++++++++++++++++++++++++++++ reference/constraints/map.rst.inc | 1 + 2 files changed, 120 insertions(+) create mode 100644 reference/constraints/Slug.rst diff --git a/reference/constraints/Slug.rst b/reference/constraints/Slug.rst new file mode 100644 index 00000000000..5b7661b4aba --- /dev/null +++ b/reference/constraints/Slug.rst @@ -0,0 +1,119 @@ +Slug +==== + +.. versionadded:: 7.3 + + The ``Slug`` constraint was introduced in Symfony 7.3. + +Validates that a value is a slug. +A slug is a string that, by default, matches the regex ``/^[a-z0-9]+(?:-[a-z0-9]+)*$/``. + +.. include:: /reference/constraints/_empty-values-are-valid.rst.inc + +========== =================================================================== +Applies to :ref:`property or method ` +Class :class:`Symfony\\Component\\Validator\\Constraints\\Slug` +Validator :class:`Symfony\\Component\\Validator\\Constraints\\SlugValidator` +========== =================================================================== + +Basic Usage +----------- + +The ``Slug`` constraint can be applied to a property or a "getter" method: + +.. configuration-block:: + + .. code-block:: php-attributes + + // src/Entity/Author.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + + class Author + { + #[Assert\Slug] + protected string $slug; + } + + .. code-block:: yaml + + # config/validator/validation.yaml + App\Entity\Author: + properties: + slug: + - Slug: ~ + + .. code-block:: xml + + + + + + + + + + + + + .. code-block:: php + + // src/Entity/Author.php + namespace App\Entity; + + use Symfony\Component\Validator\Constraints as Assert; + use Symfony\Component\Validator\Mapping\ClassMetadata; + + class Author + { + // ... + + public static function loadValidatorMetadata(ClassMetadata $metadata): void + { + $metadata->addPropertyConstraint('slug', new Assert\Slug()); + } + } + +Examples of valid values : + +* foobar +* foo-bar +* foo123 +* foo-123bar + +Upper case characters would result in an violation of this constraint. + +Options +------- + +``regex`` +~~~~~~~~~ + +**type**: ``string`` default: ``/^[a-z0-9]+(?:-[a-z0-9]+)*$/`` + +This option allows you to modify the regular expression pattern that the input will be matched against +via the :phpfunction:`preg_match` PHP function. + +If you need to use it, you might also want to take a look at the :doc:`Regex constraint `. + +``message`` +~~~~~~~~~~~ + +**type**: ``string`` **default**: ``This value is not a valid slug`` + +This is the message that will be shown if this validator fails. + +You can use the following parameters in this message: + +================= ============================================================== +Parameter Description +================= ============================================================== +``{{ value }}`` The current (invalid) value +================= ============================================================== + +.. include:: /reference/constraints/_groups-option.rst.inc + +.. include:: /reference/constraints/_payload-option.rst.inc diff --git a/reference/constraints/map.rst.inc b/reference/constraints/map.rst.inc index f23f5b71aa3..58801a8c653 100644 --- a/reference/constraints/map.rst.inc +++ b/reference/constraints/map.rst.inc @@ -33,6 +33,7 @@ String Constraints * :doc:`NotCompromisedPassword ` * :doc:`PasswordStrength ` * :doc:`Regex ` +* :doc:`Slug ` * :doc:`Ulid ` * :doc:`Url ` * :doc:`UserPassword ` From d688f6312c64050123f0c27b0871f80ab63b677c Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Tue, 7 Jan 2025 17:56:09 +0100 Subject: [PATCH 030/153] Minor tweaks --- reference/constraints/Slug.rst | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/reference/constraints/Slug.rst b/reference/constraints/Slug.rst index 5b7661b4aba..2eb82cd9c10 100644 --- a/reference/constraints/Slug.rst +++ b/reference/constraints/Slug.rst @@ -5,8 +5,8 @@ Slug The ``Slug`` constraint was introduced in Symfony 7.3. -Validates that a value is a slug. -A slug is a string that, by default, matches the regex ``/^[a-z0-9]+(?:-[a-z0-9]+)*$/``. +Validates that a value is a slug. By default, a slug is a string that matches +the following regular expression: ``/^[a-z0-9]+(?:-[a-z0-9]+)*$/``. .. include:: /reference/constraints/_empty-values-are-valid.rst.inc @@ -19,7 +19,7 @@ Validator :class:`Symfony\\Component\\Validator\\Constraints\\SlugValidator` Basic Usage ----------- -The ``Slug`` constraint can be applied to a property or a "getter" method: +The ``Slug`` constraint can be applied to a property or a getter method: .. configuration-block:: @@ -77,14 +77,14 @@ The ``Slug`` constraint can be applied to a property or a "getter" method: } } -Examples of valid values : +Examples of valid values: * foobar * foo-bar * foo123 * foo-123bar -Upper case characters would result in an violation of this constraint. +Uppercase characters would result in an violation of this constraint. Options ------- @@ -94,8 +94,8 @@ Options **type**: ``string`` default: ``/^[a-z0-9]+(?:-[a-z0-9]+)*$/`` -This option allows you to modify the regular expression pattern that the input will be matched against -via the :phpfunction:`preg_match` PHP function. +This option allows you to modify the regular expression pattern that the input +will be matched against via the :phpfunction:`preg_match` PHP function. If you need to use it, you might also want to take a look at the :doc:`Regex constraint `. From 693b758160d580d9e5bbdb6e0e8b4310baf2d5f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Andr=C3=A9?= Date: Wed, 8 Jan 2025 06:29:55 +0100 Subject: [PATCH 031/153] [Security] Remove mention of is_granted_ `$field` argument The third argument of is_granted / is_granted_for_user is undocumented (except on this page) (neither on Symfony docs, code, nor on... Symfony ACL Bundle docs). It must be kept and maintained for BC reasons. But it can be missleading/frustrating to show this argument to users, when they cannot use it with a standard/recommended Symfony installation. And if they just test it, the error could lead them to believe the AclBundle needs to be installed to use the `is_granted` function(s). As suggested in https://github.com/symfony/symfony/pull/59282#issuecomment-2569716817 I believe "not showing it" is the "best" solution here. --- reference/twig_reference.rst | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/reference/twig_reference.rst b/reference/twig_reference.rst index e01fa3f80e3..acbab2f3817 100644 --- a/reference/twig_reference.rst +++ b/reference/twig_reference.rst @@ -160,14 +160,12 @@ is_granted .. code-block:: twig - {{ is_granted(role, object = null, field = null) }} + {{ is_granted(role, object = null) }} ``role`` **type**: ``string`` ``object`` *(optional)* **type**: ``object`` -``field`` *(optional)* - **type**: ``string`` Returns ``true`` if the current user has the given role. @@ -183,7 +181,7 @@ is_granted_for_user .. code-block:: twig - {{ is_granted_for_user(user, attribute, subject = null, field = null) }} + {{ is_granted_for_user(user, attribute, subject = null) }} ``user`` **type**: ``object`` @@ -191,8 +189,6 @@ is_granted_for_user **type**: ``string`` ``subject`` *(optional)* **type**: ``object`` -``field`` *(optional)* - **type**: ``string`` Returns ``true`` if the user is authorized for the specified attribute. From 23800a97fa5114849320dac166b80ff97b3d2d46 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Wed, 8 Jan 2025 10:18:55 +0100 Subject: [PATCH 032/153] Minor tweak --- components/type_info.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/type_info.rst b/components/type_info.rst index 70280d9a348..47fe9dfd9ba 100644 --- a/components/type_info.rst +++ b/components/type_info.rst @@ -149,7 +149,7 @@ Checking if a type **accepts a value**:: $type->accepts('z'); // false $type = Type::union(Type::string(), Type::int()); - // now the second check is true because the union type accepts either a int and a string value + // now the second check is true because the union type accepts either an int or a string value $type->accepts(123); // true $type->accepts('z'); // true From ed80f8b78a6714ed0c092e7cf90eb98afecf75be Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Wed, 8 Jan 2025 22:05:23 +0100 Subject: [PATCH 033/153] revert webhook support for the Brevo Notifier bridge --- notifier.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/notifier.rst b/notifier.rst index 090f4db2007..f74ff92009e 100644 --- a/notifier.rst +++ b/notifier.rst @@ -76,7 +76,7 @@ Service **Webhook support**: No `Brevo`_ **Install**: ``composer require symfony/brevo-notifier`` \ **DSN**: ``brevo://API_KEY@default?sender=SENDER`` \ - **Webhook support**: Yes + **Webhook support**: No `Clickatell`_ **Install**: ``composer require symfony/clickatell-notifier`` \ **DSN**: ``clickatell://ACCESS_TOKEN@default?from=FROM`` \ **Webhook support**: No From fb028aa7e77e8a2324b3b65e4e9823045d26ef73 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Thu, 9 Jan 2025 12:24:39 +0100 Subject: [PATCH 034/153] Remove an uneeded versionadded directive --- notifier.rst | 4 ---- 1 file changed, 4 deletions(-) diff --git a/notifier.rst b/notifier.rst index f74ff92009e..36fbd5ada89 100644 --- a/notifier.rst +++ b/notifier.rst @@ -236,10 +236,6 @@ Service The ``Primotexto``, ``Sipgate`` and ``Sweego`` integrations were introduced in Symfony 7.2. -.. versionadded:: 7.3 - - Webhook support for the ``Brevo`` integration was introduced in Symfony 7.3. - .. deprecated:: 7.1 The `Sms77`_ integration is deprecated since From f8667f57029ad20b615c6c7f42d13bc02df86fa5 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Mon, 6 Jan 2025 21:16:06 +0100 Subject: [PATCH 035/153] [OptionsResolver] Support union of types --- components/options_resolver.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/components/options_resolver.rst b/components/options_resolver.rst index ff25f9e0fc4..266911bf13d 100644 --- a/components/options_resolver.rst +++ b/components/options_resolver.rst @@ -305,13 +305,20 @@ correctly. To validate the types of the options, call // specify multiple allowed types $resolver->setAllowedTypes('port', ['null', 'int']); + // which is similar to + $resolver->setAllowedTypes('port', 'int|null'); // check all items in an array recursively for a type $resolver->setAllowedTypes('dates', 'DateTime[]'); $resolver->setAllowedTypes('ports', 'int[]'); + $resolver->setAllowedTypes('endpoints', '(int|string)[]'); } } +.. versionadded:: 7.3 + + Union of type, using the ``|`` syntax, was introduced in Symfony 7.3. + You can pass any type for which an ``is_()`` function is defined in PHP. You may also pass fully qualified class or interface names (which is checked using ``instanceof``). Additionally, you can validate all items in an array From ca50eeb570eb13b2c35b7fa02f199bfc509755d8 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Thu, 9 Jan 2025 15:29:40 +0100 Subject: [PATCH 036/153] Minor tweaks --- components/options_resolver.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/components/options_resolver.rst b/components/options_resolver.rst index 266911bf13d..95741a7e372 100644 --- a/components/options_resolver.rst +++ b/components/options_resolver.rst @@ -305,19 +305,20 @@ correctly. To validate the types of the options, call // specify multiple allowed types $resolver->setAllowedTypes('port', ['null', 'int']); - // which is similar to + // if you prefer, you can also use the following equivalent syntax $resolver->setAllowedTypes('port', 'int|null'); // check all items in an array recursively for a type $resolver->setAllowedTypes('dates', 'DateTime[]'); $resolver->setAllowedTypes('ports', 'int[]'); + // the following syntax means "an array of integers or an array of strings" $resolver->setAllowedTypes('endpoints', '(int|string)[]'); } } .. versionadded:: 7.3 - Union of type, using the ``|`` syntax, was introduced in Symfony 7.3. + Defining type unions with the ``|`` syntax was introduced in Symfony 7.3. You can pass any type for which an ``is_()`` function is defined in PHP. You may also pass fully qualified class or interface names (which is checked From c532aed720a486a85ba26209cf7ad4185db5c0ec Mon Sep 17 00:00:00 2001 From: chx Date: Mon, 6 Jan 2025 14:36:23 +0100 Subject: [PATCH 037/153] [DependencyInjection] Documented the new @> shorthand --- service_container/service_closures.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/service_container/service_closures.rst b/service_container/service_closures.rst index cedbaaa2bf9..73dc17961fd 100644 --- a/service_container/service_closures.rst +++ b/service_container/service_closures.rst @@ -52,6 +52,14 @@ argument of type ``service_closure``: # In case the dependency is optional # arguments: [!service_closure '@?mailer'] +.. versionadded:: 7.3 + + # A shorthand is available + # arguments: ['@>mailer'] + + # It also works when the dependency is optional + # arguments: ['@>?mailer'] + .. code-block:: xml From 250c35b812814ed105d1790213b9938a2e244666 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Fri, 10 Jan 2025 16:08:17 +0100 Subject: [PATCH 038/153] Reword --- service_container/service_closures.rst | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/service_container/service_closures.rst b/service_container/service_closures.rst index 73dc17961fd..88b0ab64002 100644 --- a/service_container/service_closures.rst +++ b/service_container/service_closures.rst @@ -52,12 +52,11 @@ argument of type ``service_closure``: # In case the dependency is optional # arguments: [!service_closure '@?mailer'] -.. versionadded:: 7.3 - - # A shorthand is available - # arguments: ['@>mailer'] + # you can also use the special '@>' syntax as a shortcut of '!service_closure' + App\Service\AnotherService: + arguments: ['@>mailer'] - # It also works when the dependency is optional + # the shortcut also works for optional dependencies # arguments: ['@>?mailer'] .. code-block:: xml @@ -98,6 +97,10 @@ argument of type ``service_closure``: // ->args([service_closure('mailer')->ignoreOnInvalid()]); }; +.. versionadded:: 7.3 + + The ``@>`` shortcut syntax for YAML was introduced in Symfony 7.3. + .. seealso:: Service closures can be injected :ref:`by using autowiring ` From 207933321946e82e537eece0022f2045e22c0ff8 Mon Sep 17 00:00:00 2001 From: Maxime Doutreluingne Date: Sun, 12 Jan 2025 11:09:02 +0100 Subject: [PATCH 039/153] [FrameworkBundle] Remove ``--show-arguments`` option for ``debug:container`` --- controller/value_resolver.rst | 2 +- service_container/debug.rst | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/controller/value_resolver.rst b/controller/value_resolver.rst index dbbea7bcc87..1844ff0c9be 100644 --- a/controller/value_resolver.rst +++ b/controller/value_resolver.rst @@ -396,7 +396,7 @@ command to see which argument resolvers are present and in which order they run: .. code-block:: terminal - $ php bin/console debug:container debug.argument_resolver.inner --show-arguments + $ php bin/console debug:container debug.argument_resolver.inner You can also configure the name passed to the ``ValueResolver`` attribute to target your resolver. Otherwise it will default to the service's id. diff --git a/service_container/debug.rst b/service_container/debug.rst index c09413e7213..0a7898108fb 100644 --- a/service_container/debug.rst +++ b/service_container/debug.rst @@ -51,6 +51,3 @@ its id: .. code-block:: terminal $ php bin/console debug:container App\Service\Mailer - - # to show the service arguments: - $ php bin/console debug:container App\Service\Mailer --show-arguments From e12afdd378e43587f1d9cfb06e8f5708146b2e76 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Mon, 13 Jan 2025 09:34:01 +0100 Subject: [PATCH 040/153] Add a versionadded directive --- service_container/debug.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/service_container/debug.rst b/service_container/debug.rst index 0a7898108fb..aab6fa9529f 100644 --- a/service_container/debug.rst +++ b/service_container/debug.rst @@ -51,3 +51,9 @@ its id: .. code-block:: terminal $ php bin/console debug:container App\Service\Mailer + +.. versionadded:: 7.3 + + Starting in Symfony 7.3, this command displays the service arguments by default. + In earlier Symfony versions, you needed to use the ``--show-arguments`` option, + which is now deprecated. From 1c51901e95a0d05284e311df419bc9828dc983c7 Mon Sep 17 00:00:00 2001 From: Farhad Hedayatifard Date: Tue, 29 Oct 2024 21:10:14 +0330 Subject: [PATCH 041/153] [Mailer] feat(mailer): Add AhaSend Mailer doc --- .doctor-rst.yaml | 4 +- .github/workflows/ci.yaml | 2 +- _build/redirection_map | 5 +- .../serializer/serializer_workflow.svg | 0 .../serializer/serializer_workflow.dia | Bin bundles.rst | 6 +- bundles/best_practices.rst | 2 +- bundles/extension.rst | 2 +- bundles/override.rst | 2 +- cache.rst | 2 +- components/cache/adapters/apcu_adapter.rst | 4 +- .../adapters/couchbasebucket_adapter.rst | 2 +- .../adapters/couchbasecollection_adapter.rst | 2 +- .../cache/adapters/filesystem_adapter.rst | 2 +- .../cache/adapters/memcached_adapter.rst | 4 +- .../cache/adapters/php_files_adapter.rst | 2 +- components/cache/adapters/redis_adapter.rst | 84 +- components/clock.rst | 6 +- components/config/definition.rst | 24 +- .../console/changing_default_command.rst | 2 +- components/console/events.rst | 2 +- .../console/helpers/formatterhelper.rst | 14 +- components/console/helpers/progressbar.rst | 2 +- components/console/helpers/questionhelper.rst | 6 +- components/event_dispatcher.rst | 6 +- components/expression_language.rst | 8 +- components/finder.rst | 2 +- components/form.rst | 2 +- components/http_foundation.rst | 25 +- components/http_kernel.rst | 2 +- components/ldap.rst | 2 +- components/lock.rst | 38 +- components/options_resolver.rst | 4 +- components/phpunit_bridge.rst | 4 +- components/process.rst | 4 +- components/property_access.rst | 8 +- components/property_info.rst | 17 +- components/runtime.rst | 5 +- components/serializer.rst | 1938 -------------- components/type_info.rst | 136 +- components/uid.rst | 4 +- components/validator/resources.rst | 2 +- components/yaml.rst | 10 + configuration.rst | 8 +- configuration/env_var_processors.rst | 2 +- configuration/micro_kernel_trait.rst | 2 +- configuration/multiple_kernels.rst | 2 +- configuration/override_dir_structure.rst | 2 +- console.rst | 10 +- console/calling_commands.rst | 5 +- console/command_in_controller.rst | 2 +- console/commands_as_services.rst | 4 +- console/input.rst | 2 +- contributing/code/bc.rst | 8 +- contributing/code/bugs.rst | 2 +- contributing/code/maintenance.rst | 3 + contributing/documentation/format.rst | 2 +- contributing/documentation/standards.rst | 2 +- controller.rst | 32 +- controller/error_pages.rst | 2 +- deployment.rst | 2 +- deployment/proxies.rst | 2 +- doctrine.rst | 8 +- doctrine/custom_dql_functions.rst | 2 +- doctrine/multiple_entity_managers.rst | 6 +- event_dispatcher.rst | 11 + form/bootstrap5.rst | 6 +- form/create_custom_field_type.rst | 2 +- form/data_mappers.rst | 4 +- form/data_transformers.rst | 6 +- form/direct_submit.rst | 2 +- form/events.rst | 4 +- form/form_collections.rst | 6 +- form/form_customization.rst | 4 +- form/form_themes.rst | 2 +- form/inherit_data_option.rst | 2 +- form/type_guesser.rst | 2 +- form/unit_testing.rst | 6 +- form/without_class.rst | 2 +- forms.rst | 2 +- frontend.rst | 10 + frontend/asset_mapper.rst | 129 +- frontend/encore/installation.rst | 2 +- frontend/encore/simple-example.rst | 4 +- frontend/encore/virtual-machine.rst | 4 +- http_cache/cache_invalidation.rst | 2 +- http_cache/esi.rst | 2 +- http_cache/varnish.rst | 33 +- http_client.rst | 18 +- lock.rst | 2 +- logging/channels_handlers.rst | 2 +- logging/monolog_exclude_http_codes.rst | 2 +- mailer.rst | 62 +- mercure.rst | 93 +- messenger.rst | 29 +- notifier.rst | 85 +- profiler.rst | 4 +- reference/attributes.rst | 12 +- reference/configuration/framework.rst | 11 +- reference/configuration/web_profiler.rst | 2 +- reference/constraints/Bic.rst | 15 +- reference/constraints/Callback.rst | 8 +- reference/constraints/DateTime.rst | 17 +- reference/constraints/EqualTo.rst | 2 +- reference/constraints/File.rst | 9 +- reference/constraints/IdenticalTo.rst | 2 +- reference/constraints/Image.rst | 20 + reference/constraints/MacAddress.rst | 1 + reference/constraints/NotEqualTo.rst | 2 +- reference/constraints/NotIdenticalTo.rst | 2 +- reference/constraints/PasswordStrength.rst | 6 +- reference/constraints/Slug.rst | 119 + reference/constraints/UniqueEntity.rst | 6 +- reference/constraints/_payload-option.rst.inc | 2 - reference/constraints/map.rst.inc | 1 + reference/dic_tags.rst | 4 +- reference/formats/expression_language.rst | 2 +- reference/formats/message_format.rst | 2 +- reference/forms/types/choice.rst | 2 +- reference/forms/types/collection.rst | 6 +- reference/forms/types/country.rst | 2 +- reference/forms/types/currency.rst | 2 +- reference/forms/types/date.rst | 9 +- reference/forms/types/dateinterval.rst | 4 +- reference/forms/types/entity.rst | 2 +- reference/forms/types/language.rst | 2 +- reference/forms/types/locale.rst | 2 +- reference/forms/types/money.rst | 5 +- .../types/options/_date_limitation.rst.inc | 2 +- .../forms/types/options/choice_lazy.rst.inc | 2 +- .../forms/types/options/choice_name.rst.inc | 2 +- reference/forms/types/options/data.rst.inc | 2 +- .../options/empty_data_description.rst.inc | 2 +- .../forms/types/options/inherit_data.rst.inc | 2 +- reference/forms/types/options/value.rst.inc | 2 +- reference/forms/types/password.rst | 2 +- reference/forms/types/textarea.rst | 2 +- reference/forms/types/time.rst | 10 +- reference/forms/types/timezone.rst | 2 +- reference/forms/types/url.rst | 7 + reference/twig_reference.rst | 33 +- routing.rst | 78 +- scheduler.rst | 8 + security.rst | 37 +- security/access_control.rst | 6 +- security/access_token.rst | 4 +- security/custom_authenticator.rst | 27 +- security/impersonating_user.rst | 9 +- security/ldap.rst | 4 +- security/login_link.rst | 2 +- security/passwords.rst | 2 +- security/remember_me.rst | 25 +- security/user_providers.rst | 2 +- security/voters.rst | 31 +- serializer.rst | 2331 ++++++++++++++--- serializer/custom_context_builders.rst | 8 +- serializer/custom_encoders.rst | 61 - serializer/custom_name_converter.rst | 112 + serializer/custom_normalizer.rst | 66 +- serializer/encoders.rst | 373 +++ service_container.rst | 4 +- service_container/compiler_passes.rst | 80 +- service_container/definitions.rst | 4 +- service_container/lazy_services.rst | 2 +- service_container/service_decoration.rst | 76 +- .../service_subscribers_locators.rst | 2 +- service_container/tags.rst | 17 +- session.rst | 26 +- setup.rst | 8 +- setup/_update_all_packages.rst.inc | 2 +- setup/file_permissions.rst | 8 +- setup/symfony_server.rst | 8 +- setup/upgrade_major.rst | 2 +- setup/web_server_configuration.rst | 110 +- string.rst | 10 +- templates.rst | 4 +- testing.rst | 16 +- testing/end_to_end.rst | 2 +- testing/insulating_clients.rst | 2 +- translation.rst | 8 +- validation.rst | 2 +- validation/custom_constraint.rst | 41 + validation/groups.rst | 2 +- validation/sequence_provider.rst | 4 +- web_link.rst | 22 +- webhook.rst | 7 +- workflow.rst | 2 +- 187 files changed, 4032 insertions(+), 3028 deletions(-) rename _images/{components => }/serializer/serializer_workflow.svg (100%) rename _images/sources/{components => }/serializer/serializer_workflow.dia (100%) delete mode 100644 components/serializer.rst create mode 100644 reference/constraints/Slug.rst delete mode 100644 serializer/custom_encoders.rst create mode 100644 serializer/custom_name_converter.rst create mode 100644 serializer/encoders.rst diff --git a/.doctor-rst.yaml b/.doctor-rst.yaml index 5ea86e6abc4..04e720bf653 100644 --- a/.doctor-rst.yaml +++ b/.doctor-rst.yaml @@ -23,6 +23,8 @@ rules: forbidden_directives: directives: - '.. index::' + - directive: '.. caution::' + replacements: ['.. warning::', '.. danger::'] indention: ~ lowercase_as_in_use_statements: ~ max_blank_lines: @@ -46,6 +48,7 @@ rules: no_namespace_after_use_statements: ~ no_php_open_tag_in_code_block_php_directive: ~ no_space_before_self_xml_closing_tag: ~ + non_static_phpunit_assertions: ~ only_backslashes_in_namespace_in_php_code_block: ~ only_backslashes_in_use_statements_in_php_code_block: ~ ordered_use_statements: ~ @@ -99,7 +102,6 @@ whitelist: - '#. The most important config file is ``app/config/services.yml``, which now is' - 'The bin/console Command' - '.. _`LDAP injection`: http://projects.webappsec.org/w/page/13246947/LDAP%20Injection' - - '.. versionadded:: 2.7.2' # Doctrine - '.. versionadded:: 2.8.0' # Doctrine - '.. versionadded:: 1.9.0' # Encore - '.. versionadded:: 1.18' # Flex in setup/upgrade_minor.rst diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index fcbdbe0477b..061b0bb85b0 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -73,7 +73,7 @@ jobs: key: ${{ runner.os }}-doctor-rst-${{ steps.extract_base_branch.outputs.branch }} - name: "Run DOCtor-RST" - uses: docker://oskarstark/doctor-rst:1.62.3 + uses: docker://oskarstark/doctor-rst:1.64.0 with: args: --short --error-format=github --cache-file=/github/workspace/.cache/doctor-rst.cache diff --git a/_build/redirection_map b/_build/redirection_map index 115ec75ade2..1701f4a8f70 100644 --- a/_build/redirection_map +++ b/_build/redirection_map @@ -525,8 +525,7 @@ /testing/functional_tests_assertions /testing#testing-application-assertions /components https://symfony.com/components /components/index https://symfony.com/components -/serializer/normalizers /components/serializer#normalizers -/components/serializer#component-serializer-attributes-groups-annotations /components/serializer#component-serializer-attributes-groups-attributes +/serializer/normalizers /serializer#serializer-built-in-normalizers /logging/monolog_regex_based_excludes /logging/monolog_exclude_http_codes /security/named_encoders /security/named_hashers /components/inflector /string#inflector @@ -572,3 +571,5 @@ /doctrine/registration_form /security#security-make-registration-form /form/form_dependencies /form/create_custom_field_type /doctrine/reverse_engineering /doctrine#doctrine-adding-mapping +/components/serializer /serializer +/serializer/custom_encoder /serializer/encoders#serializer-custom-encoder diff --git a/_images/components/serializer/serializer_workflow.svg b/_images/serializer/serializer_workflow.svg similarity index 100% rename from _images/components/serializer/serializer_workflow.svg rename to _images/serializer/serializer_workflow.svg diff --git a/_images/sources/components/serializer/serializer_workflow.dia b/_images/sources/serializer/serializer_workflow.dia similarity index 100% rename from _images/sources/components/serializer/serializer_workflow.dia rename to _images/sources/serializer/serializer_workflow.dia diff --git a/bundles.rst b/bundles.rst index ba3a2209999..878bee3af4a 100644 --- a/bundles.rst +++ b/bundles.rst @@ -3,7 +3,7 @@ The Bundle System ================= -.. caution:: +.. warning:: In Symfony versions prior to 4.0, it was recommended to organize your own application code using bundles. This is :ref:`no longer recommended ` and bundles @@ -58,7 +58,7 @@ Start by creating a new class called ``AcmeBlogBundle``:: { } -.. caution:: +.. warning:: If your bundle must be compatible with previous Symfony versions you have to extend from the :class:`Symfony\\Component\\HttpKernel\\Bundle\\Bundle` instead. @@ -118,7 +118,7 @@ to be adjusted if needed: .. _bundles-legacy-directory-structure: -.. caution:: +.. warning:: The recommended bundle structure was changed in Symfony 5, read the `Symfony 4.4 bundle documentation`_ for information about the old diff --git a/bundles/best_practices.rst b/bundles/best_practices.rst index 5996bcbe43d..376984388db 100644 --- a/bundles/best_practices.rst +++ b/bundles/best_practices.rst @@ -246,7 +246,7 @@ with Symfony Flex to install a specific Symfony version: # recommended to have a better output and faster download time) composer update --prefer-dist --no-progress -.. caution:: +.. warning:: If you want to cache your Composer dependencies, **do not** cache the ``vendor/`` directory as this has side-effects. Instead cache diff --git a/bundles/extension.rst b/bundles/extension.rst index 347f63b7af5..0537eb00c3e 100644 --- a/bundles/extension.rst +++ b/bundles/extension.rst @@ -200,7 +200,7 @@ Patterns are transformed into the actual class namespaces using the classmap generated by Composer. Therefore, before using these patterns, you must generate the full classmap executing the ``dump-autoload`` command of Composer. -.. caution:: +.. warning:: This technique can't be used when the classes to compile use the ``__DIR__`` or ``__FILE__`` constants, because their values will change when loading diff --git a/bundles/override.rst b/bundles/override.rst index 36aea69b231..f25bd785373 100644 --- a/bundles/override.rst +++ b/bundles/override.rst @@ -19,7 +19,7 @@ For example, to override the ``templates/registration/confirmed.html.twig`` template from the AcmeUserBundle, create this template: ``/templates/bundles/AcmeUserBundle/registration/confirmed.html.twig`` -.. caution:: +.. warning:: If you add a template in a new location, you *may* need to clear your cache (``php bin/console cache:clear``), even if you are in debug mode. diff --git a/cache.rst b/cache.rst index 7264585f233..833e4d77007 100644 --- a/cache.rst +++ b/cache.rst @@ -829,7 +829,7 @@ Then, register the ``SodiumMarshaller`` service using this key: //->addArgument(['env(base64:CACHE_DECRYPTION_KEY)', 'env(base64:OLD_CACHE_DECRYPTION_KEY)']) ->addArgument(new Reference('.inner')); -.. caution:: +.. danger:: This will encrypt the values of the cache items, but not the cache keys. Be careful not to leak sensitive data in the keys. diff --git a/components/cache/adapters/apcu_adapter.rst b/components/cache/adapters/apcu_adapter.rst index 99d76ce5d27..f2e92850cd8 100644 --- a/components/cache/adapters/apcu_adapter.rst +++ b/components/cache/adapters/apcu_adapter.rst @@ -5,7 +5,7 @@ This adapter is a high-performance, shared memory cache. It can *significantly* increase an application's performance, as its cache contents are stored in shared memory, a component appreciably faster than many others, such as the filesystem. -.. caution:: +.. warning:: **Requirement:** The `APCu extension`_ must be installed and active to use this adapter. @@ -30,7 +30,7 @@ and cache items version string as constructor arguments:: $version = null ); -.. caution:: +.. warning:: Use of this adapter is discouraged in write/delete heavy workloads, as these operations cause memory fragmentation that results in significantly degraded performance. diff --git a/components/cache/adapters/couchbasebucket_adapter.rst b/components/cache/adapters/couchbasebucket_adapter.rst index aaf400319f4..29c9e26f83c 100644 --- a/components/cache/adapters/couchbasebucket_adapter.rst +++ b/components/cache/adapters/couchbasebucket_adapter.rst @@ -14,7 +14,7 @@ shared memory; you can store contents independent of your PHP environment. The ability to utilize a cluster of servers to provide redundancy and/or fail-over is also available. -.. caution:: +.. warning:: **Requirements:** The `Couchbase PHP extension`_ as well as a `Couchbase server`_ must be installed, active, and running to use this adapter. Version ``2.6`` or diff --git a/components/cache/adapters/couchbasecollection_adapter.rst b/components/cache/adapters/couchbasecollection_adapter.rst index 25640a20b0f..ba78cc46eff 100644 --- a/components/cache/adapters/couchbasecollection_adapter.rst +++ b/components/cache/adapters/couchbasecollection_adapter.rst @@ -8,7 +8,7 @@ shared memory; you can store contents independent of your PHP environment. The ability to utilize a cluster of servers to provide redundancy and/or fail-over is also available. -.. caution:: +.. warning:: **Requirements:** The `Couchbase PHP extension`_ as well as a `Couchbase server`_ must be installed, active, and running to use this adapter. Version ``3.0`` or diff --git a/components/cache/adapters/filesystem_adapter.rst b/components/cache/adapters/filesystem_adapter.rst index 26ef48af27c..db877454859 100644 --- a/components/cache/adapters/filesystem_adapter.rst +++ b/components/cache/adapters/filesystem_adapter.rst @@ -33,7 +33,7 @@ and cache root path as constructor parameters:: $directory = null ); -.. caution:: +.. warning:: The overhead of filesystem IO often makes this adapter one of the *slower* choices. If throughput is paramount, the in-memory adapters diff --git a/components/cache/adapters/memcached_adapter.rst b/components/cache/adapters/memcached_adapter.rst index d68d3e3b9ac..64baf0d4702 100644 --- a/components/cache/adapters/memcached_adapter.rst +++ b/components/cache/adapters/memcached_adapter.rst @@ -8,7 +8,7 @@ shared memory; you can store contents independent of your PHP environment. The ability to utilize a cluster of servers to provide redundancy and/or fail-over is also available. -.. caution:: +.. warning:: **Requirements:** The `Memcached PHP extension`_ as well as a `Memcached server`_ must be installed, active, and running to use this adapter. Version ``2.2`` or @@ -256,7 +256,7 @@ Available Options executed in a "fire-and-forget" manner; no attempt to ensure the operation has been received or acted on will be made once the client has executed it. - .. caution:: + .. warning:: Not all library operations are tested in this mode. Mixed TCP and UDP servers are not allowed. diff --git a/components/cache/adapters/php_files_adapter.rst b/components/cache/adapters/php_files_adapter.rst index efd2cf0e964..6f171f0fede 100644 --- a/components/cache/adapters/php_files_adapter.rst +++ b/components/cache/adapters/php_files_adapter.rst @@ -28,7 +28,7 @@ file similar to the following:: handles file includes, this adapter has the potential to be much faster than other filesystem-based caches. -.. caution:: +.. warning:: While it supports updates and because it is using OPcache as a backend, this adapter is better suited for append-mostly needs. Using it in other scenarios might lead to diff --git a/components/cache/adapters/redis_adapter.rst b/components/cache/adapters/redis_adapter.rst index 719d6056f19..3362f4cc2db 100644 --- a/components/cache/adapters/redis_adapter.rst +++ b/components/cache/adapters/redis_adapter.rst @@ -15,7 +15,7 @@ Unlike the :doc:`APCu adapter `, and si shared memory; you can store contents independent of your PHP environment. The ability to utilize a cluster of servers to provide redundancy and/or fail-over is also available. -.. caution:: +.. warning:: **Requirements:** At least one `Redis server`_ must be installed and running to use this adapter. Additionally, this adapter requires a compatible extension or library that implements @@ -38,7 +38,13 @@ as the second and third parameters:: // the default lifetime (in seconds) for cache items that do not define their // own lifetime, with a value 0 causing items to be stored indefinitely (i.e. // until RedisAdapter::clear() is invoked or the server(s) are purged) - $defaultLifetime = 0 + $defaultLifetime = 0, + + // $marshaller (optional) An instance of MarshallerInterface to control the serialization + // and deserialization of cache items. By default, native PHP serialization is used. + // This can be useful for compressing data, applying custom serialization logic, or + // optimizing the size and performance of cached items + ?MarshallerInterface $marshaller = null ); Configure the Connection @@ -266,6 +272,80 @@ performance when using tag-based invalidation:: Read more about this topic in the official `Redis LRU Cache Documentation`_. +Working with Marshaller +----------------------- + +TagAwareMarshaller for Tag-Based Caching +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Optimizes caching for tag-based retrieval, allowing efficient management of related items:: + + $marshaller = new TagAwareMarshaller(); + + $cache = new RedisAdapter($redis, 'tagged_namespace', 3600, $marshaller); + + $item = $cache->getItem('tagged_key'); + $item->set(['value' => 'some_data', 'tags' => ['tag1', 'tag2']]); + $cache->save($item); + +SodiumMarshaller for Encrypted Caching +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Encrypts cached data using Sodium for enhanced security:: + + $encryptionKeys = [sodium_crypto_box_keypair()]; + $marshaller = new SodiumMarshaller($encryptionKeys); + + $cache = new RedisAdapter($redis, 'secure_namespace', 3600, $marshaller); + + $item = $cache->getItem('secure_key'); + $item->set('confidential_data'); + $cache->save($item); + +DefaultMarshaller with igbinary Serialization +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Uses ``igbinary` for faster and more efficient serialization when available:: + + $marshaller = new DefaultMarshaller(true); + + $cache = new RedisAdapter($redis, 'optimized_namespace', 3600, $marshaller); + + $item = $cache->getItem('optimized_key'); + $item->set(['data' => 'optimized_data']); + $cache->save($item); + +DefaultMarshaller with Exception on Failure +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Throws an exception if serialization fails, facilitating error handling:: + + $marshaller = new DefaultMarshaller(false, true); + + $cache = new RedisAdapter($redis, 'error_namespace', 3600, $marshaller); + + try { + $item = $cache->getItem('error_key'); + $item->set('data'); + $cache->save($item); + } catch (\ValueError $e) { + echo 'Serialization failed: '.$e->getMessage(); + } + +SodiumMarshaller with Key Rotation +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Supports key rotation, ensuring secure decryption with both old and new keys:: + + $keys = [sodium_crypto_box_keypair(), sodium_crypto_box_keypair()]; + $marshaller = new SodiumMarshaller($keys); + + $cache = new RedisAdapter($redis, 'rotated_namespace', 3600, $marshaller); + + $item = $cache->getItem('rotated_key'); + $item->set('data_to_encrypt'); + $cache->save($item); + .. _`Data Source Name (DSN)`: https://en.wikipedia.org/wiki/Data_source_name .. _`Redis server`: https://redis.io/ .. _`Redis`: https://github.com/phpredis/phpredis diff --git a/components/clock.rst b/components/clock.rst index cdbbdd56e6b..5b20e6000b9 100644 --- a/components/clock.rst +++ b/components/clock.rst @@ -129,18 +129,18 @@ is expired or not, by modifying the clock's time:: $validUntil = new DateTimeImmutable('2022-11-16 15:25:00'); // $validUntil is in the future, so it is not expired - static::assertFalse($expirationChecker->isExpired($validUntil)); + $this->assertFalse($expirationChecker->isExpired($validUntil)); // Clock sleeps for 10 minutes, so now is '2022-11-16 15:30:00' $clock->sleep(600); // Instantly changes time as if we waited for 10 minutes (600 seconds) // modify the clock, accepts all formats supported by DateTimeImmutable::modify() - static::assertTrue($expirationChecker->isExpired($validUntil)); + $this->assertTrue($expirationChecker->isExpired($validUntil)); $clock->modify('2022-11-16 15:00:00'); // $validUntil is in the future again, so it is no longer expired - static::assertFalse($expirationChecker->isExpired($validUntil)); + $this->assertFalse($expirationChecker->isExpired($validUntil)); } } diff --git a/components/config/definition.rst b/components/config/definition.rst index 929246fa915..24c142ec5a5 100644 --- a/components/config/definition.rst +++ b/components/config/definition.rst @@ -83,9 +83,19 @@ reflect the real structure of the configuration values:: ->scalarNode('default_connection') ->defaultValue('mysql') ->end() + ->stringNode('username') + ->defaultValue('root') + ->end() + ->stringNode('password') + ->defaultValue('root') + ->end() ->end() ; +.. versionadded:: 7.2 + + The ``stringNode()`` method was introduced in Symfony 7.2. + The root node itself is an array node, and has children, like the boolean node ``auto_connect`` and the scalar node ``default_connection``. In general: after defining a node, a call to ``end()`` takes you one step up in the @@ -100,6 +110,7 @@ node definition. Node types are available for: * scalar (generic type that includes booleans, strings, integers, floats and ``null``) * boolean +* string * integer * float * enum (similar to scalar, but it only allows a finite set of values) @@ -109,6 +120,10 @@ node definition. Node types are available for: and are created with ``node($name, $type)`` or their associated shortcut ``xxxxNode($name)`` method. +.. versionadded:: 7.2 + + Support for the ``string`` type was introduced in Symfony 7.2. + Numeric Node Constraints ~~~~~~~~~~~~~~~~~~~~~~~~ @@ -670,7 +685,7 @@ The separator used in keys is typically ``_`` in YAML and ``-`` in XML. For example, ``auto_connect`` in YAML and ``auto-connect`` in XML. The normalization would make both of these ``auto_connect``. -.. caution:: +.. warning:: The target key will not be altered if it's mixed like ``foo-bar_moo`` or if it already exists. @@ -800,6 +815,7 @@ A validation rule always has an "if" part. You can specify this part in the following ways: - ``ifTrue()`` +- ``ifFalse()`` - ``ifString()`` - ``ifNull()`` - ``ifEmpty()`` @@ -818,6 +834,10 @@ A validation rule also requires a "then" part: Usually, "then" is a closure. Its return value will be used as a new value for the node, instead of the node's original value. +.. versionadded:: 7.3 + + The ``ifFalse()`` method was introduced in Symfony 7.3. + Configuring the Node Path Separator ----------------------------------- @@ -889,7 +909,7 @@ Otherwise the result is a clean array of configuration values:: $configs ); -.. caution:: +.. warning:: When processing the configuration tree, the processor assumes that the top level array key (which matches the extension name) is already stripped off. diff --git a/components/console/changing_default_command.rst b/components/console/changing_default_command.rst index b739e3b39ba..c69995ea395 100644 --- a/components/console/changing_default_command.rst +++ b/components/console/changing_default_command.rst @@ -52,7 +52,7 @@ This will print the following to the command line: Hello World -.. caution:: +.. warning:: This feature has a limitation: you cannot pass any argument or option to the default command because they are ignored. diff --git a/components/console/events.rst b/components/console/events.rst index f0edf2205ac..e550025b7dd 100644 --- a/components/console/events.rst +++ b/components/console/events.rst @@ -14,7 +14,7 @@ the wheel, it uses the Symfony EventDispatcher component to do the work:: $application->setDispatcher($dispatcher); $application->run(); -.. caution:: +.. warning:: Console events are only triggered by the main command being executed. Commands called by the main command will not trigger any event, unless diff --git a/components/console/helpers/formatterhelper.rst b/components/console/helpers/formatterhelper.rst index 3cb87c4c307..d2b19915a3a 100644 --- a/components/console/helpers/formatterhelper.rst +++ b/components/console/helpers/formatterhelper.rst @@ -129,10 +129,16 @@ Sometimes you want to format seconds to time. This is possible with the The first argument is the seconds to format and the second argument is the precision (default ``1``) of the result:: - Helper::formatTime(42); // 42 secs - Helper::formatTime(125); // 2 mins - Helper::formatTime(125, 2); // 2 mins, 5 secs - Helper::formatTime(172799, 4); // 1 day, 23 hrs, 59 mins, 59 secs + Helper::formatTime(0.001); // 1 ms + Helper::formatTime(42); // 42 s + Helper::formatTime(125); // 2 min + Helper::formatTime(125, 2); // 2 min, 5 s + Helper::formatTime(172799, 4); // 1 d, 23 h, 59 min, 59 s + Helper::formatTime(172799.056, 5); // 1 d, 23 h, 59 min, 59 s, 56 ms + +.. versionadded:: 7.3 + + Support for formatting up to milliseconds was introduced in Symfony 7.3. Formatting Memory ----------------- diff --git a/components/console/helpers/progressbar.rst b/components/console/helpers/progressbar.rst index 4d524a2008e..19e2d0daef5 100644 --- a/components/console/helpers/progressbar.rst +++ b/components/console/helpers/progressbar.rst @@ -323,7 +323,7 @@ to display it can be customized:: // the bar width $progressBar->setBarWidth(50); -.. caution:: +.. warning:: For performance reasons, Symfony redraws the screen once every 100ms. If this is too fast or too slow for your application, use the methods diff --git a/components/console/helpers/questionhelper.rst b/components/console/helpers/questionhelper.rst index e33c4ed5fa7..2670ec3084a 100644 --- a/components/console/helpers/questionhelper.rst +++ b/components/console/helpers/questionhelper.rst @@ -329,7 +329,7 @@ convenient for passwords:: return Command::SUCCESS; } -.. caution:: +.. warning:: When you ask for a hidden response, Symfony will use either a binary, change ``stty`` mode or use another trick to hide the response. If none is available, @@ -392,7 +392,7 @@ method:: return Command::SUCCESS; } -.. caution:: +.. warning:: The normalizer is called first and the returned value is used as the input of the validator. If the answer is invalid, don't throw exceptions in the @@ -540,7 +540,7 @@ This way you can test any user interaction (even complex ones) by passing the ap simulates a user hitting ``ENTER`` after each input, no need for passing an additional input. -.. caution:: +.. warning:: On Windows systems Symfony uses a special binary to implement hidden questions. This means that those questions don't use the default ``Input`` diff --git a/components/event_dispatcher.rst b/components/event_dispatcher.rst index 83cead3d19c..8cd676dd5fe 100644 --- a/components/event_dispatcher.rst +++ b/components/event_dispatcher.rst @@ -476,11 +476,7 @@ with some other dispatchers: Learn More ---------- -.. toctree:: - :maxdepth: 1 - - /components/event_dispatcher/generic_event - +* :doc:`/components/event_dispatcher/generic_event` * :ref:`The kernel.event_listener tag ` * :ref:`The kernel.event_subscriber tag ` diff --git a/components/expression_language.rst b/components/expression_language.rst index 785beebd9da..b0dd10b0f42 100644 --- a/components/expression_language.rst +++ b/components/expression_language.rst @@ -106,6 +106,10 @@ if the expression is not valid:: $expressionLanguage->lint('1 + 2', []); // doesn't throw anything + $expressionLanguage->lint('1 + a', []); + // throws a SyntaxError exception: + // "Variable "a" is not valid around position 5 for expression `1 + a`." + The behavior of these methods can be configured with some flags defined in the :class:`Symfony\\Component\\ExpressionLanguage\\Parser` class: @@ -121,8 +125,8 @@ This is how you can use these flags:: $expressionLanguage = new ExpressionLanguage(); - // this returns true because the unknown variables and functions are ignored - var_dump($expressionLanguage->lint('unknown_var + unknown_function()', Parser::IGNORE_UNKNOWN_VARIABLES | Parser::IGNORE_UNKNOWN_FUNCTIONS)); + // does not throw a SyntaxError because the unknown variables and functions are ignored + $expressionLanguage->lint('unknown_var + unknown_function()', [], Parser::IGNORE_UNKNOWN_VARIABLES | Parser::IGNORE_UNKNOWN_FUNCTIONS); .. versionadded:: 7.1 diff --git a/components/finder.rst b/components/finder.rst index a3b91470b62..cecc597ac64 100644 --- a/components/finder.rst +++ b/components/finder.rst @@ -41,7 +41,7 @@ The ``$file`` variable is an instance of :class:`Symfony\\Component\\Finder\\SplFileInfo` which extends PHP's own :phpclass:`SplFileInfo` to provide methods to work with relative paths. -.. caution:: +.. warning:: The ``Finder`` object doesn't reset its internal state automatically. This means that you need to create a new instance if you do not want diff --git a/components/form.rst b/components/form.rst index f463ef5911b..44f407e4c8e 100644 --- a/components/form.rst +++ b/components/form.rst @@ -644,7 +644,7 @@ method: // ... -.. caution:: +.. warning:: The form's ``createView()`` method should be called *after* ``handleRequest()`` is called. Otherwise, when using :doc:`form events `, changes done diff --git a/components/http_foundation.rst b/components/http_foundation.rst index 4dcf3b1e4da..8db6cd27f68 100644 --- a/components/http_foundation.rst +++ b/components/http_foundation.rst @@ -367,11 +367,13 @@ of the ``anonymize()`` method to specify the number of bytes that should be anonymized depending on the IP address format:: $ipv4 = '123.234.235.236'; - $anonymousIpv4 = IpUtils::anonymize($ipv4, v4Bytes: 3); + $anonymousIpv4 = IpUtils::anonymize($ipv4, 3); // $anonymousIpv4 = '123.0.0.0' $ipv6 = '2a01:198:603:10:396e:4789:8e99:890f'; - $anonymousIpv6 = IpUtils::anonymize($ipv6, v6Bytes: 10); + // (you must define the second argument (bytes to anonymize in IPv4 addresses) + // even when you are only anonymizing IPv6 addresses) + $anonymousIpv6 = IpUtils::anonymize($ipv6, 3, 10); // $anonymousIpv6 = '2a01:198:603::' .. versionadded:: 7.2 @@ -679,8 +681,19 @@ Streaming a Response ~~~~~~~~~~~~~~~~~~~~ The :class:`Symfony\\Component\\HttpFoundation\\StreamedResponse` class allows -you to stream the Response back to the client. The response content is -represented by a PHP callable instead of a string:: +you to stream the Response back to the client. The response content can be +represented by a string iterable:: + + use Symfony\Component\HttpFoundation\StreamedResponse; + + $chunks = ['Hello', ' World']; + + $response = new StreamedResponse(); + $response->setChunks($chunks); + $response->send(); + +For most complex use cases, the response content can be instead represented by +a PHP callable:: use Symfony\Component\HttpFoundation\StreamedResponse; @@ -708,6 +721,10 @@ represented by a PHP callable instead of a string:: // disables FastCGI buffering in nginx only for this response $response->headers->set('X-Accel-Buffering', 'no'); +.. versionadded:: 7.3 + + Support for using string iterables was introduced in Symfony 7.3. + Streaming a JSON Response ~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/components/http_kernel.rst b/components/http_kernel.rst index 97de70b66df..02791b370bc 100644 --- a/components/http_kernel.rst +++ b/components/http_kernel.rst @@ -471,7 +471,7 @@ you will trigger the ``kernel.terminate`` event where you can perform certain actions that you may have delayed in order to return the response as quickly as possible to the client (e.g. sending emails). -.. caution:: +.. warning:: Internally, the HttpKernel makes use of the :phpfunction:`fastcgi_finish_request` PHP function. This means that at the moment, only the `PHP FPM`_ server diff --git a/components/ldap.rst b/components/ldap.rst index d5f6bc3fdfe..e52a341986c 100644 --- a/components/ldap.rst +++ b/components/ldap.rst @@ -70,7 +70,7 @@ distinguished name (DN) and the password of a user:: $ldap->bind($dn, $password); -.. caution:: +.. danger:: When the LDAP server allows unauthenticated binds, a blank password will always be valid. diff --git a/components/lock.rst b/components/lock.rst index bf75fb49f7a..b8ba38c8fc7 100644 --- a/components/lock.rst +++ b/components/lock.rst @@ -359,7 +359,7 @@ lose the lock it acquired automatically:: throw new \Exception('Process failed'); } -.. caution:: +.. warning:: A common pitfall might be to use the ``isAcquired()`` method to check if a lock has already been acquired by any process. As you can see in this example @@ -427,7 +427,7 @@ when the PHP process ends):: // if none is given, sys_get_temp_dir() is used internally. $store = new FlockStore('/var/stores'); -.. caution:: +.. warning:: Beware that some file systems (such as some types of NFS) do not support locking. In those cases, it's better to use a directory on a local disk @@ -668,7 +668,7 @@ the stores:: $store = new CombinedStore($stores, new UnanimousStrategy()); -.. caution:: +.. warning:: In order to get high availability when using the ``ConsensusStrategy``, the minimum cluster size must be three servers. This allows the cluster to keep @@ -720,7 +720,7 @@ the ``Lock``. Every concurrent process must store the ``Lock`` on the same server. Otherwise two different machines may allow two different processes to acquire the same ``Lock``. -.. caution:: +.. warning:: To guarantee that the same server will always be safe, do not use Memcached behind a LoadBalancer, a cluster or round-robin DNS. Even if the main server @@ -762,12 +762,12 @@ Using the above methods, a robust code would be:: // Perform the task whose duration MUST be less than 5 seconds } -.. caution:: +.. warning:: Choose wisely the lifetime of the ``Lock`` and check whether its remaining time to live is enough to perform the task. -.. caution:: +.. warning:: Storing a ``Lock`` usually takes a few milliseconds, but network conditions may increase that time a lot (up to a few seconds). Take that into account @@ -776,7 +776,7 @@ Using the above methods, a robust code would be:: By design, locks are stored on servers with a defined lifetime. If the date or time of the machine changes, a lock could be released sooner than expected. -.. caution:: +.. warning:: To guarantee that date won't change, the NTP service should be disabled and the date should be updated when the service is stopped. @@ -798,7 +798,7 @@ deployments. Some file systems (such as some types of NFS) do not support locking. -.. caution:: +.. warning:: All concurrent processes must use the same physical file system by running on the same machine and using the same absolute path to the lock directory. @@ -827,7 +827,7 @@ and may disappear by mistake at any time. If the Memcached service or the machine hosting it restarts, every lock would be lost without notifying the running processes. -.. caution:: +.. warning:: To avoid that someone else acquires a lock after a restart, it's recommended to delay service start and wait at least as long as the longest lock TTL. @@ -835,7 +835,7 @@ be lost without notifying the running processes. By default Memcached uses a LRU mechanism to remove old entries when the service needs space to add new items. -.. caution:: +.. warning:: The number of items stored in Memcached must be under control. If it's not possible, LRU should be disabled and Lock should be stored in a dedicated @@ -853,7 +853,7 @@ method uses the Memcached's ``flush()`` method which purges and removes everythi MongoDbStore ~~~~~~~~~~~~ -.. caution:: +.. warning:: The locked resource name is indexed in the ``_id`` field of the lock collection. Beware that an indexed field's value in MongoDB can be @@ -879,7 +879,7 @@ about `Expire Data from Collections by Setting TTL`_ in MongoDB. recommended to set constructor option ``gcProbability`` to ``0.0`` to disable this behavior if you have manually dealt with TTL index creation. -.. caution:: +.. warning:: This store relies on all PHP application and database nodes to have synchronized clocks for lock expiry to occur at the correct time. To ensure @@ -896,12 +896,12 @@ PdoStore The PdoStore relies on the `ACID`_ properties of the SQL engine. -.. caution:: +.. warning:: In a cluster configured with multiple primaries, ensure writes are synchronously propagated to every node, or always use the same node. -.. caution:: +.. warning:: Some SQL engines like MySQL allow to disable the unique constraint check. Ensure that this is not the case ``SET unique_checks=1;``. @@ -910,7 +910,7 @@ In order to purge old locks, this store uses a current datetime to define an expiration date reference. This mechanism relies on all server nodes to have synchronized clocks. -.. caution:: +.. warning:: To ensure locks don't expire prematurely; the TTLs should be set with enough extra time to account for any clock drift between nodes. @@ -939,7 +939,7 @@ and may disappear by mistake at any time. If the Redis service or the machine hosting it restarts, every locks would be lost without notifying the running processes. -.. caution:: +.. warning:: To avoid that someone else acquires a lock after a restart, it's recommended to delay service start and wait at least as long as the longest lock TTL. @@ -967,7 +967,7 @@ The ``CombinedStore`` will be, at best, as reliable as the least reliable of all managed stores. As soon as one managed store returns erroneous information, the ``CombinedStore`` won't be reliable. -.. caution:: +.. warning:: All concurrent processes must use the same configuration, with the same amount of managed stored and the same endpoint. @@ -985,13 +985,13 @@ must run on the same machine, virtual machine or container. Be careful when updating a Kubernetes or Swarm service because for a short period of time, there can be two running containers in parallel. -.. caution:: +.. warning:: All concurrent processes must use the same machine. Before starting a concurrent process on a new machine, check that other processes are stopped on the old one. -.. caution:: +.. warning:: When running on systemd with non-system user and option ``RemoveIPC=yes`` (default value), locks are deleted by systemd when that user logs out. diff --git a/components/options_resolver.rst b/components/options_resolver.rst index c8052d0d395..ff25f9e0fc4 100644 --- a/components/options_resolver.rst +++ b/components/options_resolver.rst @@ -485,7 +485,7 @@ these options, you can return the desired default value:: } } -.. caution:: +.. warning:: The argument of the callable must be type hinted as ``Options``. Otherwise, the callable itself is considered as the default value of the option. @@ -699,7 +699,7 @@ to the closure to access to them:: } } -.. caution:: +.. warning:: The arguments of the closure must be type hinted as ``OptionsResolver`` and ``Options`` respectively. Otherwise, the closure itself is considered as the diff --git a/components/phpunit_bridge.rst b/components/phpunit_bridge.rst index ba37bc0ecda..5ce4c003a11 100644 --- a/components/phpunit_bridge.rst +++ b/components/phpunit_bridge.rst @@ -253,7 +253,7 @@ deprecations but: * forget to mark appropriate tests with the ``@group legacy`` annotations. By using ``SYMFONY_DEPRECATIONS_HELPER=max[self]=0``, deprecations that are -triggered outside the ``vendors`` directory will be accounted for separately, +triggered outside the ``vendor/`` directory will be accounted for separately, while deprecations triggered from a library inside it will not (unless you reach 999999 of these), giving you the best of both worlds. @@ -621,7 +621,7 @@ test:: And that's all! -.. caution:: +.. warning:: Time-based function mocking follows the `PHP namespace resolutions rules`_ so "fully qualified function calls" (e.g ``\time()``) cannot be mocked. diff --git a/components/process.rst b/components/process.rst index 9502665dde1..f6c8837d2c3 100644 --- a/components/process.rst +++ b/components/process.rst @@ -108,7 +108,7 @@ You can configure the options passed to the ``other_options`` argument of // this option allows a subprocess to continue running after the main script exited $process->setOptions(['create_new_console' => true]); -.. caution:: +.. warning:: Most of the options defined by ``proc_open()`` (such as ``create_new_console`` and ``suppress_errors``) are only supported on Windows operating systems. @@ -552,7 +552,7 @@ Use :method:`Symfony\\Component\\Process\\Process::disableOutput` and $process->disableOutput(); $process->run(); -.. caution:: +.. warning:: You cannot enable or disable the output while the process is running. diff --git a/components/property_access.rst b/components/property_access.rst index 600481dce1a..f608640fa9b 100644 --- a/components/property_access.rst +++ b/components/property_access.rst @@ -26,6 +26,8 @@ default configuration:: $propertyAccessor = PropertyAccess::createPropertyAccessor(); +.. _property-access-reading-arrays: + Reading from Arrays ------------------- @@ -112,7 +114,7 @@ To read from properties, use the "dot" notation:: var_dump($propertyAccessor->getValue($person, 'children[0].firstName')); // 'Bar' -.. caution:: +.. warning:: Accessing public properties is the last option used by ``PropertyAccessor``. It tries to access the value using the below methods first before using @@ -260,7 +262,7 @@ The ``getValue()`` method can also use the magic ``__get()`` method:: var_dump($propertyAccessor->getValue($person, 'Wouter')); // [...] -.. caution:: +.. warning:: When implementing the magic ``__get()`` method, you also need to implement ``__isset()``. @@ -301,7 +303,7 @@ enable this feature by using :class:`Symfony\\Component\\PropertyAccess\\Propert var_dump($propertyAccessor->getValue($person, 'wouter')); // [...] -.. caution:: +.. warning:: The ``__call()`` feature is disabled by default, you can enable it by calling :method:`Symfony\\Component\\PropertyAccess\\PropertyAccessorBuilder::enableMagicCall` diff --git a/components/property_info.rst b/components/property_info.rst index 892cd5345a3..0ff1768441a 100644 --- a/components/property_info.rst +++ b/components/property_info.rst @@ -469,7 +469,18 @@ information from annotations of properties and methods, such as ``@var``, use App\Domain\Foo; $phpStanExtractor = new PhpStanExtractor(); + + // Type information. $phpStanExtractor->getTypesFromConstructor(Foo::class, 'bar'); + // Description information. + $phpStanExtractor->getShortDescription($class, 'bar'); + $phpStanExtractor->getLongDescription($class, 'bar'); + +.. versionadded:: 7.3 + + The :method:`Symfony\\Component\\PropertyInfo\\Extractor\\PhpStanExtractor::getShortDescription` + and :method:`Symfony\\Component\\PropertyInfo\\Extractor\\PhpStanExtractor::getLongDescription` + methods were introduced in Symfony 7.3. SerializerExtractor ~~~~~~~~~~~~~~~~~~~ @@ -478,9 +489,9 @@ SerializerExtractor This extractor depends on the `symfony/serializer`_ library. -Using :ref:`groups metadata ` -from the :doc:`Serializer component `, -the :class:`Symfony\\Component\\PropertyInfo\\Extractor\\SerializerExtractor` +Using :ref:`groups metadata ` from the +:doc:`Serializer component `, the +:class:`Symfony\\Component\\PropertyInfo\\Extractor\\SerializerExtractor` provides list information. This extractor is *not* registered automatically with the ``property_info`` service in the Symfony Framework:: diff --git a/components/runtime.rst b/components/runtime.rst index 7d17e7e7456..4eb75de2a75 100644 --- a/components/runtime.rst +++ b/components/runtime.rst @@ -3,7 +3,7 @@ The Runtime Component The Runtime Component decouples the bootstrapping logic from any global state to make sure the application can run with runtimes like `PHP-PM`_, `ReactPHP`_, - `Swoole`_, etc. without any changes. + `Swoole`_, `FrankenPHP`_ etc. without any changes. Installation ------------ @@ -42,7 +42,7 @@ the component. This file runs the following logic: #. At last, the Runtime is used to run the application (i.e. calling ``$kernel->handle(Request::createFromGlobals())->send()``). -.. caution:: +.. warning:: If you use the Composer ``--no-plugins`` option, the ``autoload_runtime.php`` file won't be created. @@ -487,6 +487,7 @@ The end user will now be able to create front controller like:: .. _PHP-PM: https://github.com/php-pm/php-pm .. _Swoole: https://openswoole.com/ +.. _FrankenPHP: https://frankenphp.dev/ .. _ReactPHP: https://reactphp.org/ .. _`PSR-15`: https://www.php-fig.org/psr/psr-15/ .. _`runtime template file`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Runtime/Internal/autoload_runtime.template diff --git a/components/serializer.rst b/components/serializer.rst deleted file mode 100644 index 0764612e28a..00000000000 --- a/components/serializer.rst +++ /dev/null @@ -1,1938 +0,0 @@ -The Serializer Component -======================== - - The Serializer component is meant to be used to turn objects into a - specific format (XML, JSON, YAML, ...) and the other way around. - -In order to do so, the Serializer component follows the following schema. - -.. raw:: html - - - -When (de)serializing objects, the Serializer uses an array as the intermediary -between objects and serialized contents. Encoders will only deal with -turning specific **formats** into **arrays** and vice versa. The same way, -normalizers will deal with turning specific **objects** into **arrays** and -vice versa. The Serializer deals with calling the normalizers and encoders -when serializing objects or deserializing formats. - -Serialization is a complex topic. This component may not cover all your use -cases out of the box, but it can be useful for developing tools to -serialize and deserialize your objects. - -Installation ------------- - -.. code-block:: terminal - - $ composer require symfony/serializer - -.. include:: /components/require_autoload.rst.inc - -To use the ``ObjectNormalizer``, the :doc:`PropertyAccess component ` -must also be installed. - -Usage ------ - -.. seealso:: - - This article explains the philosophy of the Serializer and gets you familiar - with the concepts of normalizers and encoders. The code examples assume - that you use the Serializer as an independent component. If you are using - the Serializer in a Symfony application, read :doc:`/serializer` after you - finish this article. - -To use the Serializer component, set up the -:class:`Symfony\\Component\\Serializer\\Serializer` specifying which encoders -and normalizer are going to be available:: - - use Symfony\Component\Serializer\Encoder\JsonEncoder; - use Symfony\Component\Serializer\Encoder\XmlEncoder; - use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; - use Symfony\Component\Serializer\Serializer; - - $encoders = [new XmlEncoder(), new JsonEncoder()]; - $normalizers = [new ObjectNormalizer()]; - - $serializer = new Serializer($normalizers, $encoders); - -The preferred normalizer is the -:class:`Symfony\\Component\\Serializer\\Normalizer\\ObjectNormalizer`, -but other normalizers are available. All the examples shown below use -the ``ObjectNormalizer``. - -Serializing an Object ---------------------- - -For the sake of this example, assume the following class already -exists in your project:: - - namespace App\Model; - - class Person - { - private int $age; - private string $name; - private bool $sportsperson; - private ?\DateTimeInterface $createdAt; - - // Getters - public function getAge(): int - { - return $this->age; - } - - public function getName(): string - { - return $this->name; - } - - public function getCreatedAt(): ?\DateTimeInterface - { - return $this->createdAt; - } - - // Issers - public function isSportsperson(): bool - { - return $this->sportsperson; - } - - // Setters - public function setAge(int $age): void - { - $this->age = $age; - } - - public function setName(string $name): void - { - $this->name = $name; - } - - public function setSportsperson(bool $sportsperson): void - { - $this->sportsperson = $sportsperson; - } - - public function setCreatedAt(?\DateTimeInterface $createdAt = null): void - { - $this->createdAt = $createdAt; - } - } - -Now, if you want to serialize this object into JSON, you only need to -use the Serializer service created before:: - - use App\Model\Person; - - $person = new Person(); - $person->setName('foo'); - $person->setAge(99); - $person->setSportsperson(false); - - $jsonContent = $serializer->serialize($person, 'json'); - - // $jsonContent contains {"name":"foo","age":99,"sportsperson":false,"createdAt":null} - - echo $jsonContent; // or return it in a Response - -The first parameter of the :method:`Symfony\\Component\\Serializer\\Serializer::serialize` -is the object to be serialized and the second is used to choose the proper encoder, -in this case :class:`Symfony\\Component\\Serializer\\Encoder\\JsonEncoder`. - -Deserializing an Object ------------------------ - -You'll now learn how to do the exact opposite. This time, the information -of the ``Person`` class would be encoded in XML format:: - - use App\Model\Person; - - $data = << - foo - 99 - false - - EOF; - - $person = $serializer->deserialize($data, Person::class, 'xml'); - -In this case, :method:`Symfony\\Component\\Serializer\\Serializer::deserialize` -needs three parameters: - -#. The information to be decoded -#. The name of the class this information will be decoded to -#. The encoder used to convert that information into an array - -By default, additional attributes that are not mapped to the denormalized object -will be ignored by the Serializer component. If you prefer to throw an exception -when this happens, set the ``AbstractNormalizer::ALLOW_EXTRA_ATTRIBUTES`` context option to -``false`` and provide an object that implements ``ClassMetadataFactoryInterface`` -when constructing the normalizer:: - - use App\Model\Person; - - $data = << - foo - 99 - Paris - - EOF; - - // $loader is any of the valid loaders explained later in this article - $classMetadataFactory = new ClassMetadataFactory($loader); - $normalizer = new ObjectNormalizer($classMetadataFactory); - $serializer = new Serializer([$normalizer]); - - // this will throw a Symfony\Component\Serializer\Exception\ExtraAttributesException - // because "city" is not an attribute of the Person class - $person = $serializer->deserialize($data, Person::class, 'xml', [ - AbstractNormalizer::ALLOW_EXTRA_ATTRIBUTES => false, - ]); - -Deserializing in an Existing Object -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The serializer can also be used to update an existing object:: - - // ... - $person = new Person(); - $person->setName('bar'); - $person->setAge(99); - $person->setSportsperson(true); - - $data = << - foo - 69 - - EOF; - - $serializer->deserialize($data, Person::class, 'xml', [AbstractNormalizer::OBJECT_TO_POPULATE => $person]); - // $person = App\Model\Person(name: 'foo', age: '69', sportsperson: true) - -This is a common need when working with an ORM. - -The ``AbstractNormalizer::OBJECT_TO_POPULATE`` is only used for the top level object. If that object -is the root of a tree structure, all child elements that exist in the -normalized data will be re-created with new instances. - -When the ``AbstractObjectNormalizer::DEEP_OBJECT_TO_POPULATE`` option is set to -true, existing children of the root ``OBJECT_TO_POPULATE`` are updated from the -normalized data, instead of the denormalizer re-creating them. Note that -``DEEP_OBJECT_TO_POPULATE`` only works for single child objects, but not for -arrays of objects. Those will still be replaced when present in the normalized -data. - -Context -------- - -Many Serializer features can be configured :ref:`using a context `. - -.. _component-serializer-attributes-groups: - -Attributes Groups ------------------ - -Sometimes, you want to serialize different sets of attributes from your -entities. Groups are a handy way to achieve this need. - -Assume you have the following plain-old-PHP object:: - - namespace Acme; - - class MyObj - { - public string $foo; - - private string $bar; - - public function getBar(): string - { - return $this->bar; - } - - public function setBar($bar): string - { - return $this->bar = $bar; - } - } - -The definition of serialization can be specified using attributes, XML or YAML. -The :class:`Symfony\\Component\\Serializer\\Mapping\\Factory\\ClassMetadataFactory` -that will be used by the normalizer must be aware of the format to use. - -The following code shows how to initialize the :class:`Symfony\\Component\\Serializer\\Mapping\\Factory\\ClassMetadataFactory` -for each format: - -* Attributes in PHP files:: - - use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; - use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader; - - $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); - -* YAML files:: - - use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; - use Symfony\Component\Serializer\Mapping\Loader\YamlFileLoader; - - $classMetadataFactory = new ClassMetadataFactory(new YamlFileLoader('/path/to/your/definition.yaml')); - -* XML files:: - - use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; - use Symfony\Component\Serializer\Mapping\Loader\XmlFileLoader; - - $classMetadataFactory = new ClassMetadataFactory(new XmlFileLoader('/path/to/your/definition.xml')); - -.. _component-serializer-attributes-groups-attributes: - -Then, create your groups definition: - -.. configuration-block:: - - .. code-block:: php-attributes - - namespace Acme; - - use Symfony\Component\Serializer\Annotation\Groups; - - class MyObj - { - #[Groups(['group1', 'group2'])] - public string $foo; - - #[Groups(['group4'])] - public string $anotherProperty; - - #[Groups(['group3'])] - public function getBar() // is* methods are also supported - { - return $this->bar; - } - - // ... - } - - .. code-block:: yaml - - Acme\MyObj: - attributes: - foo: - groups: ['group1', 'group2'] - anotherProperty: - groups: ['group4'] - bar: - groups: ['group3'] - - .. code-block:: xml - - - - - - group1 - group2 - - - - group4 - - - - group3 - - - - -You are now able to serialize only attributes in the groups you want:: - - use Acme\MyObj; - use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; - use Symfony\Component\Serializer\Serializer; - - $obj = new MyObj(); - $obj->foo = 'foo'; - $obj->anotherProperty = 'anotherProperty'; - $obj->setBar('bar'); - - $normalizer = new ObjectNormalizer($classMetadataFactory); - $serializer = new Serializer([$normalizer]); - - $data = $serializer->normalize($obj, null, ['groups' => 'group1']); - // $data = ['foo' => 'foo']; - - $obj2 = $serializer->denormalize( - ['foo' => 'foo', 'anotherProperty' => 'anotherProperty', 'bar' => 'bar'], - MyObj::class, - null, - ['groups' => ['group1', 'group3']] - ); - // $obj2 = MyObj(foo: 'foo', bar: 'bar') - - // To get all groups, use the special value `*` in `groups` - $obj3 = $serializer->denormalize( - ['foo' => 'foo', 'anotherProperty' => 'anotherProperty', 'bar' => 'bar'], - MyObj::class, - null, - ['groups' => ['*']] - ); - // $obj2 = MyObj(foo: 'foo', anotherProperty: 'anotherProperty', bar: 'bar') - -.. _ignoring-attributes-when-serializing: - -Selecting Specific Attributes ------------------------------ - -It is also possible to serialize only a set of specific attributes:: - - use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; - use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; - use Symfony\Component\Serializer\Serializer; - - class User - { - public string $familyName; - public string $givenName; - public Company $company; - } - - class Company - { - public string $name; - public string $address; - } - - $company = new Company(); - $company->name = 'Les-Tilleuls.coop'; - $company->address = 'Lille, France'; - - $user = new User(); - $user->familyName = 'Dunglas'; - $user->givenName = 'KΓ©vin'; - $user->company = $company; - - $serializer = new Serializer([new ObjectNormalizer()]); - - $data = $serializer->normalize($user, null, [AbstractNormalizer::ATTRIBUTES => ['familyName', 'company' => ['name']]]); - // $data = ['familyName' => 'Dunglas', 'company' => ['name' => 'Les-Tilleuls.coop']]; - -Only attributes that are not ignored (see below) are available. -If some serialization groups are set, only attributes allowed by those groups can be used. - -As for groups, attributes can be selected during both the serialization and deserialization processes. - -.. _serializer_ignoring-attributes: - -Ignoring Attributes -------------------- - -All accessible attributes are included by default when serializing objects. -There are two options to ignore some of those attributes. - -Option 1: Using ``#[Ignore]`` Attribute -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. configuration-block:: - - .. code-block:: php-attributes - - namespace App\Model; - - use Symfony\Component\Serializer\Annotation\Ignore; - - class MyClass - { - public string $foo; - - #[Ignore] - public string $bar; - } - - .. code-block:: yaml - - App\Model\MyClass: - attributes: - bar: - ignore: true - - .. code-block:: xml - - - - - - - - -You can now ignore specific attributes during serialization:: - - use App\Model\MyClass; - use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; - use Symfony\Component\Serializer\Serializer; - - $obj = new MyClass(); - $obj->foo = 'foo'; - $obj->bar = 'bar'; - - $normalizer = new ObjectNormalizer($classMetadataFactory); - $serializer = new Serializer([$normalizer]); - - $data = $serializer->normalize($obj); - // $data = ['foo' => 'foo']; - -Option 2: Using the Context -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Pass an array with the names of the attributes to ignore using the -``AbstractNormalizer::IGNORED_ATTRIBUTES`` key in the ``context`` of the -serializer method:: - - use Acme\Person; - use Symfony\Component\Serializer\Encoder\JsonEncoder; - use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; - use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; - use Symfony\Component\Serializer\Serializer; - - $person = new Person(); - $person->setName('foo'); - $person->setAge(99); - - $normalizer = new ObjectNormalizer(); - $encoder = new JsonEncoder(); - - $serializer = new Serializer([$normalizer], [$encoder]); - $serializer->serialize($person, 'json', [AbstractNormalizer::IGNORED_ATTRIBUTES => ['age']]); // Output: {"name":"foo"} - -.. _component-serializer-converting-property-names-when-serializing-and-deserializing: - -Converting Property Names when Serializing and Deserializing ------------------------------------------------------------- - -Sometimes serialized attributes must be named differently than properties -or getter/setter methods of PHP classes. - -The Serializer component provides a handy way to translate or map PHP field -names to serialized names: The Name Converter System. - -Given you have the following object:: - - class Company - { - public string $name; - public string $address; - } - -And in the serialized form, all attributes must be prefixed by ``org_`` like -the following:: - - {"org_name": "Acme Inc.", "org_address": "123 Main Street, Big City"} - -A custom name converter can handle such cases:: - - use Symfony\Component\Serializer\NameConverter\NameConverterInterface; - - class OrgPrefixNameConverter implements NameConverterInterface - { - public function normalize(string $propertyName, ?string $class = null, ?string $format = null, array $context = []): string - { - return 'org_'.$propertyName; - } - - public function denormalize(string $propertyName, ?string $class = null, ?string $format = null, array $context = []): string - { - // removes 'org_' prefix - return str_starts_with($propertyName, 'org_') ? substr($propertyName, 4) : $propertyName; - } - } - -The custom name converter can be used by passing it as second parameter of any -class extending :class:`Symfony\\Component\\Serializer\\Normalizer\\AbstractNormalizer`, -including :class:`Symfony\\Component\\Serializer\\Normalizer\\GetSetMethodNormalizer` -and :class:`Symfony\\Component\\Serializer\\Normalizer\\PropertyNormalizer`:: - - use Symfony\Component\Serializer\Encoder\JsonEncoder; - use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; - use Symfony\Component\Serializer\Serializer; - - $nameConverter = new OrgPrefixNameConverter(); - $normalizer = new ObjectNormalizer(null, $nameConverter); - - $serializer = new Serializer([$normalizer], [new JsonEncoder()]); - - $company = new Company(); - $company->name = 'Acme Inc.'; - $company->address = '123 Main Street, Big City'; - - $json = $serializer->serialize($company, 'json'); - // {"org_name": "Acme Inc.", "org_address": "123 Main Street, Big City"} - $companyCopy = $serializer->deserialize($json, Company::class, 'json'); - // Same data as $company - -.. _using-camelized-method-names-for-underscored-attributes: - -CamelCase to snake_case -~~~~~~~~~~~~~~~~~~~~~~~ - -In many formats, it's common to use underscores to separate words (also known -as snake_case). However, in Symfony applications is common to use CamelCase to -name properties (even though the `PSR-1 standard`_ doesn't recommend any -specific case for property names). - -Symfony provides a built-in name converter designed to transform between -snake_case and CamelCased styles during serialization and deserialization -processes:: - - use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter; - use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; - - $normalizer = new ObjectNormalizer(null, new CamelCaseToSnakeCaseNameConverter()); - - class Person - { - public function __construct( - private string $firstName, - ) { - } - - public function getFirstName(): string - { - return $this->firstName; - } - } - - $kevin = new Person('KΓ©vin'); - $normalizer->normalize($kevin); - // ['first_name' => 'KΓ©vin']; - - $anne = $normalizer->denormalize(['first_name' => 'Anne'], 'Person'); - // Person object with firstName: 'Anne' - -.. _serializer_name-conversion: - -Configure name conversion using metadata -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -When using this component inside a Symfony application and the class metadata -factory is enabled as explained in the :ref:`Attributes Groups section `, -this is already set up and you only need to provide the configuration. Otherwise:: - - // ... - use Symfony\Component\Serializer\Encoder\JsonEncoder; - use Symfony\Component\Serializer\NameConverter\MetadataAwareNameConverter; - use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; - use Symfony\Component\Serializer\Serializer; - - $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); - - $metadataAwareNameConverter = new MetadataAwareNameConverter($classMetadataFactory); - - $serializer = new Serializer( - [new ObjectNormalizer($classMetadataFactory, $metadataAwareNameConverter)], - ['json' => new JsonEncoder()] - ); - -Now configure your name conversion mapping. Consider an application that -defines a ``Person`` entity with a ``firstName`` property: - -.. configuration-block:: - - .. code-block:: php-attributes - - namespace App\Entity; - - use Symfony\Component\Serializer\Annotation\SerializedName; - - class Person - { - public function __construct( - #[SerializedName('customer_name')] - private string $firstName, - ) { - } - - // ... - } - - .. code-block:: yaml - - App\Entity\Person: - attributes: - firstName: - serialized_name: customer_name - - .. code-block:: xml - - - - - - - - -This custom mapping is used to convert property names when serializing and -deserializing objects:: - - $serialized = $serializer->serialize(new Person('KΓ©vin'), 'json'); - // {"customer_name": "KΓ©vin"} - -.. _serializing-boolean-attributes: - -Handling Boolean Attributes And Values --------------------------------------- - -During Serialization -~~~~~~~~~~~~~~~~~~~~ - -If you are using isser methods (methods prefixed by ``is``, like -``App\Model\Person::isSportsperson()``), the Serializer component will -automatically detect and use it to serialize related attributes. - -The ``ObjectNormalizer`` also takes care of methods starting with ``has``, ``get``, -and ``can``. - -During Deserialization -~~~~~~~~~~~~~~~~~~~~~~ - -PHP considers many different values as true or false. For example, the -strings ``true``, ``1``, and ``yes`` are considered true, while -``false``, ``0``, and ``no`` are considered false. - -When deserializing, the Serializer component can take care of this -automatically. This can be done by using the ``AbstractNormalizer::FILTER_BOOL`` -context option:: - - use Acme\Person; - use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; - use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; - use Symfony\Component\Serializer\Serializer; - - $normalizer = new ObjectNormalizer(); - $serializer = new Serializer([$normalizer]); - - $data = $serializer->denormalize(['sportsperson' => 'yes'], Person::class, context: [AbstractNormalizer::FILTER_BOOL => true]); - -This context makes the deserialization process behave like the -:phpfunction:`filter_var` function with the ``FILTER_VALIDATE_BOOL`` flag. - -.. versionadded:: 7.1 - - The ``AbstractNormalizer::FILTER_BOOL`` context option was introduced in Symfony 7.1. - -Using Callbacks to Serialize Properties with Object Instances -------------------------------------------------------------- - -When serializing, you can set a callback to format a specific object property:: - - use App\Model\Person; - use Symfony\Component\Serializer\Encoder\JsonEncoder; - use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer; - use Symfony\Component\Serializer\Serializer; - - $encoder = new JsonEncoder(); - - // all callback parameters are optional (you can omit the ones you don't use) - $dateCallback = function (object $attributeValue, object $object, string $attributeName, ?string $format = null, array $context = []): string { - return $attributeValue instanceof \DateTime ? $attributeValue->format(\DateTime::ATOM) : ''; - }; - - $defaultContext = [ - AbstractNormalizer::CALLBACKS => [ - 'createdAt' => $dateCallback, - ], - ]; - - $normalizer = new GetSetMethodNormalizer(null, null, null, null, null, $defaultContext); - - $serializer = new Serializer([$normalizer], [$encoder]); - - $person = new Person(); - $person->setName('cordoval'); - $person->setAge(34); - $person->setCreatedAt(new \DateTime('now')); - - $serializer->serialize($person, 'json'); - // Output: {"name":"cordoval", "age": 34, "createdAt": "2014-03-22T09:43:12-0500"} - -.. _component-serializer-normalizers: - -Normalizers ------------ - -Normalizers turn **objects** into **arrays** and vice versa. They implement -:class:`Symfony\\Component\\Serializer\\Normalizer\\NormalizerInterface` for -normalizing (object to array) and -:class:`Symfony\\Component\\Serializer\\Normalizer\\DenormalizerInterface` for -denormalizing (array to object). - -Normalizers are enabled in the serializer passing them as its first argument:: - - use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; - use Symfony\Component\Serializer\Serializer; - - $normalizers = [new ObjectNormalizer()]; - $serializer = new Serializer($normalizers, []); - -Built-in Normalizers -~~~~~~~~~~~~~~~~~~~~ - -The Serializer component provides several built-in normalizers: - -:class:`Symfony\\Component\\Serializer\\Normalizer\\ObjectNormalizer` - This normalizer leverages the :doc:`PropertyAccess Component ` - to read and write in the object. It means that it can access to properties - directly and through getters, setters, hassers, issers, canners, adders and removers. - It supports calling the constructor during the denormalization process. - - Objects are normalized to a map of property names and values (names are - generated by removing the ``get``, ``set``, ``has``, ``is``, ``can``, ``add`` or ``remove`` - prefix from the method name and transforming the first letter to lowercase; e.g. - ``getFirstName()`` -> ``firstName``). - - The ``ObjectNormalizer`` is the most powerful normalizer. It is configured by - default in Symfony applications with the Serializer component enabled. - -:class:`Symfony\\Component\\Serializer\\Normalizer\\GetSetMethodNormalizer` - This normalizer reads the content of the class by calling the "getters" - (public methods starting with "get"). It will denormalize data by calling - the constructor and the "setters" (public methods starting with "set"). - - Objects are normalized to a map of property names and values (names are - generated by removing the ``get`` prefix from the method name and transforming - the first letter to lowercase; e.g. ``getFirstName()`` -> ``firstName``). - -:class:`Symfony\\Component\\Serializer\\Normalizer\\PropertyNormalizer` - This normalizer directly reads and writes public properties as well as - **private and protected** properties (from both the class and all of its - parent classes) by using `PHP reflection`_. It supports calling the constructor - during the denormalization process. - - Objects are normalized to a map of property names to property values. - - If you prefer to only normalize certain properties (e.g. only public properties) - set the ``PropertyNormalizer::NORMALIZE_VISIBILITY`` context option and - combine the following values: ``PropertyNormalizer::NORMALIZE_PUBLIC``, - ``PropertyNormalizer::NORMALIZE_PROTECTED`` or ``PropertyNormalizer::NORMALIZE_PRIVATE``. - -:class:`Symfony\\Component\\Serializer\\Normalizer\\JsonSerializableNormalizer` - This normalizer works with classes that implement :phpclass:`JsonSerializable`. - - It will call the :phpmethod:`JsonSerializable::jsonSerialize` method and - then further normalize the result. This means that nested - :phpclass:`JsonSerializable` classes will also be normalized. - - This normalizer is particularly helpful when you want to gradually migrate - from an existing codebase using simple :phpfunction:`json_encode` to the Symfony - Serializer by allowing you to mix which normalizers are used for which classes. - - Unlike with :phpfunction:`json_encode` circular references can be handled. - -:class:`Symfony\\Component\\Serializer\\Normalizer\\DateTimeNormalizer` - This normalizer converts :phpclass:`DateTimeInterface` objects (e.g. - :phpclass:`DateTime` and :phpclass:`DateTimeImmutable`) into strings, - integers or floats. By default, it converts them to strings using the `RFC3339`_ format. - To convert the objects to integers or floats, set the serializer context option - ``DateTimeNormalizer::CAST_KEY`` to ``int`` or ``float``. - - .. versionadded:: 7.1 - - The ``DateTimeNormalizer::CAST_KEY`` context option was introduced in Symfony 7.1. - -:class:`Symfony\\Component\\Serializer\\Normalizer\\DateTimeZoneNormalizer` - This normalizer converts :phpclass:`DateTimeZone` objects into strings that - represent the name of the timezone according to the `list of PHP timezones`_. - -:class:`Symfony\\Component\\Serializer\\Normalizer\\DataUriNormalizer` - This normalizer converts :phpclass:`SplFileInfo` objects into a `data URI`_ - string (``data:...``) such that files can be embedded into serialized data. - -:class:`Symfony\\Component\\Serializer\\Normalizer\\DateIntervalNormalizer` - This normalizer converts :phpclass:`DateInterval` objects into strings. - By default, it uses the ``P%yY%mM%dDT%hH%iM%sS`` format. - -:class:`Symfony\\Component\\Serializer\\Normalizer\\BackedEnumNormalizer` - This normalizer converts a \BackedEnum objects into strings or integers. - - By default, an exception is thrown when data is not a valid backed enumeration. If you - want ``null`` instead, you can set the ``BackedEnumNormalizer::ALLOW_INVALID_VALUES`` option. - -:class:`Symfony\\Component\\Serializer\\Normalizer\\FormErrorNormalizer` - This normalizer works with classes that implement - :class:`Symfony\\Component\\Form\\FormInterface`. - - It will get errors from the form and normalize them into a normalized array. - -:class:`Symfony\\Component\\Serializer\\Normalizer\\ConstraintViolationListNormalizer` - This normalizer converts objects that implement - :class:`Symfony\\Component\\Validator\\ConstraintViolationListInterface` - into a list of errors according to the `RFC 7807`_ standard. - -:class:`Symfony\\Component\\Serializer\\Normalizer\\ProblemNormalizer` - Normalizes errors according to the API Problem spec `RFC 7807`_. - -:class:`Symfony\\Component\\Serializer\\Normalizer\\CustomNormalizer` - Normalizes a PHP object using an object that implements :class:`Symfony\\Component\\Serializer\\Normalizer\\NormalizableInterface`. - -:class:`Symfony\\Component\\Serializer\\Normalizer\\UidNormalizer` - This normalizer converts objects that extend - :class:`Symfony\\Component\\Uid\\AbstractUid` into strings. - The default normalization format for objects that implement :class:`Symfony\\Component\\Uid\\Uuid` - is the `RFC 4122`_ format (example: ``d9e7a184-5d5b-11ea-a62a-3499710062d0``). - The default normalization format for objects that implement :class:`Symfony\\Component\\Uid\\Ulid` - is the Base 32 format (example: ``01E439TP9XJZ9RPFH3T1PYBCR8``). - You can change the string format by setting the serializer context option - ``UidNormalizer::NORMALIZATION_FORMAT_KEY`` to ``UidNormalizer::NORMALIZATION_FORMAT_BASE_58``, - ``UidNormalizer::NORMALIZATION_FORMAT_BASE_32`` or ``UidNormalizer::NORMALIZATION_FORMAT_RFC_4122``. - - Also it can denormalize ``uuid`` or ``ulid`` strings to :class:`Symfony\\Component\\Uid\\Uuid` - or :class:`Symfony\\Component\\Uid\\Ulid`. The format does not matter. - -:class:`Symfony\\Component\\Serializer\\Normalizer\\TranslatableNormalizer` - This normalizer converts objects that implement - :class:`Symfony\\Contracts\\Translation\\TranslatableInterface` into - translated strings, using the - :method:`Symfony\\Contracts\\Translation\\TranslatableInterface::trans` - method. You can define the locale to use to translate the object by - setting the ``TranslatableNormalizer::NORMALIZATION_LOCALE_KEY`` serializer - context option. - -.. note:: - - You can also create your own Normalizer to use another structure. Read more at - :doc:`/serializer/custom_normalizer`. - -Certain normalizers are enabled by default when using the Serializer component -in a Symfony application, additional ones can be enabled by tagging them with -:ref:`serializer.normalizer `. - -Here is an example of how to enable the built-in -:class:`Symfony\\Component\\Serializer\\Normalizer\\GetSetMethodNormalizer`, a -faster alternative to the -:class:`Symfony\\Component\\Serializer\\Normalizer\\ObjectNormalizer`: - -.. configuration-block:: - - .. code-block:: yaml - - # config/services.yaml - services: - # ... - - get_set_method_normalizer: - class: Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer - tags: [serializer.normalizer] - - .. code-block:: xml - - - - - - - - - - - - - - .. code-block:: php - - // config/services.php - namespace Symfony\Component\DependencyInjection\Loader\Configurator; - - use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer; - - return static function (ContainerConfigurator $container): void { - $container->services() - // ... - ->set('get_set_method_normalizer', GetSetMethodNormalizer::class) - ->tag('serializer.normalizer') - ; - }; - -.. _component-serializer-encoders: - -Encoders --------- - -Encoders turn **arrays** into **formats** and vice versa. They implement -:class:`Symfony\\Component\\Serializer\\Encoder\\EncoderInterface` -for encoding (array to format) and -:class:`Symfony\\Component\\Serializer\\Encoder\\DecoderInterface` for decoding -(format to array). - -You can add new encoders to a Serializer instance by using its second constructor argument:: - - use Symfony\Component\Serializer\Encoder\JsonEncoder; - use Symfony\Component\Serializer\Encoder\XmlEncoder; - use Symfony\Component\Serializer\Serializer; - - $encoders = [new XmlEncoder(), new JsonEncoder()]; - $serializer = new Serializer([], $encoders); - -Built-in Encoders -~~~~~~~~~~~~~~~~~ - -The Serializer component provides several built-in encoders: - -:class:`Symfony\\Component\\Serializer\\Encoder\\JsonEncoder` - This class encodes and decodes data in `JSON`_. - -:class:`Symfony\\Component\\Serializer\\Encoder\\XmlEncoder` - This class encodes and decodes data in `XML`_. - -:class:`Symfony\\Component\\Serializer\\Encoder\\YamlEncoder` - This encoder encodes and decodes data in `YAML`_. This encoder requires the - :doc:`Yaml Component `. - -:class:`Symfony\\Component\\Serializer\\Encoder\\CsvEncoder` - This encoder encodes and decodes data in `CSV`_. - -.. note:: - - You can also create your own Encoder to use another structure. Read more at - :doc:`/serializer/custom_encoders`. - -All these encoders are enabled by default when using the Serializer component -in a Symfony application. - -The ``JsonEncoder`` -~~~~~~~~~~~~~~~~~~~ - -The ``JsonEncoder`` encodes to and decodes from JSON strings, based on the PHP -:phpfunction:`json_encode` and :phpfunction:`json_decode` functions. It can be -useful to modify how these functions operate in certain instances by providing -options such as ``JSON_PRESERVE_ZERO_FRACTION``. You can use the serialization -context to pass in these options using the key ``json_encode_options`` or -``json_decode_options`` respectively:: - - $this->serializer->serialize($data, 'json', ['json_encode_options' => \JSON_PRESERVE_ZERO_FRACTION]); - -These are the options available: - -=============================== =========================================================================================================== ================================ -Option Description Default -=============================== ========================================================================================================== ================================ -``json_decode_associative`` If set to true returns the result as an array, returns a nested ``stdClass`` hierarchy otherwise. ``false`` -``json_decode_detailed_errors`` If set to true, exceptions thrown on parsing of JSON are more specific. Requires `seld/jsonlint`_ package. ``false`` -``json_decode_options`` `$flags`_ passed to :phpfunction:`json_decode` function. ``0`` -``json_encode_options`` `$flags`_ passed to :phpfunction:`json_encode` function. ``\JSON_PRESERVE_ZERO_FRACTION`` -``json_decode_recursion_depth`` Sets maximum recursion depth. ``512`` -=============================== ========================================================================================================== ================================ - -The ``CsvEncoder`` -~~~~~~~~~~~~~~~~~~ - -The ``CsvEncoder`` encodes to and decodes from CSV. - -The ``CsvEncoder`` Context Options -.................................. - -The ``encode()`` method defines a third optional parameter called ``context`` -which defines the configuration options for the CsvEncoder an associative array:: - - $csvEncoder->encode($array, 'csv', $context); - -These are the options available: - -======================= ============================================================= ========================== -Option Description Default -======================= ============================================================= ========================== -``csv_delimiter`` Sets the field delimiter separating values (one ``,`` - character only) -``csv_enclosure`` Sets the field enclosure (one character only) ``"`` -``csv_end_of_line`` Sets the character(s) used to mark the end of each ``\n`` - line in the CSV file -``csv_escape_char`` Deprecated. Sets the escape character (at most one character) empty string -``csv_key_separator`` Sets the separator for array's keys during its ``.`` - flattening -``csv_headers`` Sets the order of the header and data columns - E.g.: if ``$data = ['c' => 3, 'a' => 1, 'b' => 2]`` - and ``$options = ['csv_headers' => ['a', 'b', 'c']]`` - then ``serialize($data, 'csv', $options)`` returns - ``a,b,c\n1,2,3`` ``[]``, inferred from input data's keys -``csv_escape_formulas`` Escapes fields containing formulas by prepending them ``false`` - with a ``\t`` character -``as_collection`` Always returns results as a collection, even if only ``true`` - one line is decoded. -``no_headers`` Setting to ``false`` will use first row as headers. ``false`` - ``true`` generate numeric headers. -``output_utf8_bom`` Outputs special `UTF-8 BOM`_ along with encoded data ``false`` -======================= ============================================================= ========================== - -.. deprecated:: 7.2 - - The ``csv_escape_char`` option and the ``CsvEncoder::ESCAPE_CHAR_KEY`` - constant were deprecated in Symfony 7.2. - -The ``XmlEncoder`` -~~~~~~~~~~~~~~~~~~ - -This encoder transforms arrays into XML and vice versa. - -For example, take an object normalized as following:: - - ['foo' => [1, 2], 'bar' => true]; - -The ``XmlEncoder`` will encode this object like that: - -.. code-block:: xml - - - - 1 - 2 - 1 - - -The special ``#`` key can be used to define the data of a node:: - - ['foo' => ['@bar' => 'value', '#' => 'baz']]; - - // is encoded as follows: - // - // - // - // baz - // - // - -Furthermore, keys beginning with ``@`` will be considered attributes, and -the key ``#comment`` can be used for encoding XML comments:: - - $encoder = new XmlEncoder(); - $encoder->encode([ - 'foo' => ['@bar' => 'value'], - 'qux' => ['#comment' => 'A comment'], - ], 'xml'); - // will return: - // - // - // - // - // - -You can pass the context key ``as_collection`` in order to have the results -always as a collection. - -.. note:: - - You may need to add some attributes on the root node:: - - $encoder = new XmlEncoder(); - $encoder->encode([ - '@attribute1' => 'foo', - '@attribute2' => 'bar', - '#' => ['foo' => ['@bar' => 'value', '#' => 'baz']] - ], 'xml'); - - // will return: - // - // - // baz - // - -.. tip:: - - XML comments are ignored by default when decoding contents, but this - behavior can be changed with the optional context key ``XmlEncoder::DECODER_IGNORED_NODE_TYPES``. - - Data with ``#comment`` keys are encoded to XML comments by default. This can be - changed by adding the ``\XML_COMMENT_NODE`` option to the ``XmlEncoder::ENCODER_IGNORED_NODE_TYPES`` - key of the ``$defaultContext`` of the ``XmlEncoder`` constructor or - directly to the ``$context`` argument of the ``encode()`` method:: - - $xmlEncoder->encode($array, 'xml', [XmlEncoder::ENCODER_IGNORED_NODE_TYPES => [\XML_COMMENT_NODE]]); - -The ``XmlEncoder`` Context Options -.................................. - -The ``encode()`` method defines a third optional parameter called ``context`` -which defines the configuration options for the XmlEncoder an associative array:: - - $xmlEncoder->encode($array, 'xml', $context); - -These are the options available: - -============================== ================================================= ========================== -Option Description Default -============================== ================================================= ========================== -``xml_format_output`` If set to true, formats the generated XML with ``false`` - line breaks and indentation -``xml_version`` Sets the XML version attribute ``1.0`` -``xml_encoding`` Sets the XML encoding attribute ``utf-8`` -``xml_standalone`` Adds standalone attribute in the generated XML ``true`` -``xml_type_cast_attributes`` This provides the ability to forget the attribute ``true`` - type casting -``xml_root_node_name`` Sets the root node name ``response`` -``as_collection`` Always returns results as a collection, even if ``false`` - only one line is decoded -``decoder_ignored_node_types`` Array of node types (`DOM XML_* constants`_) ``[\XML_PI_NODE, \XML_COMMENT_NODE]`` - to be ignored while decoding -``encoder_ignored_node_types`` Array of node types (`DOM XML_* constants`_) ``[]`` - to be ignored while encoding -``load_options`` XML loading `options with libxml`_ ``\LIBXML_NONET | \LIBXML_NOBLANKS`` -``save_options`` XML saving `options with libxml`_ ``0`` -``remove_empty_tags`` If set to true, removes all empty tags in the ``false`` - generated XML -``cdata_wrapping`` If set to false, will not wrap any value ``true`` - matching the ``cdata_wrapping_pattern`` regex in - `a CDATA section`_ like following: - ```` -``cdata_wrapping_pattern`` A regular expression pattern to determine if a ``/[<>&]/`` - value should be wrapped in a CDATA section -============================== ================================================= ========================== - -.. versionadded:: 7.1 - - The ``cdata_wrapping_pattern`` option was introduced in Symfony 7.1. - -Example with custom ``context``:: - - use Symfony\Component\Serializer\Encoder\XmlEncoder; - - // create encoder with specified options as new default settings - $xmlEncoder = new XmlEncoder(['xml_format_output' => true]); - - $data = [ - 'id' => 'IDHNQIItNyQ', - 'date' => '2019-10-24', - ]; - - // encode with default context - $xmlEncoder->encode($data, 'xml'); - // outputs: - // - // - // IDHNQIItNyQ - // 2019-10-24 - // - - // encode with modified context - $xmlEncoder->encode($data, 'xml', [ - 'xml_root_node_name' => 'track', - 'encoder_ignored_node_types' => [ - \XML_PI_NODE, // removes XML declaration (the leading xml tag) - ], - ]); - // outputs: - // - // IDHNQIItNyQ - // 2019-10-24 - // - -The ``YamlEncoder`` -~~~~~~~~~~~~~~~~~~~ - -This encoder requires the :doc:`Yaml Component ` and -transforms from and to Yaml. - -The ``YamlEncoder`` Context Options -................................... - -The ``encode()`` method, like other encoder, uses ``context`` to set -configuration options for the YamlEncoder an associative array:: - - $yamlEncoder->encode($array, 'yaml', $context); - -These are the options available: - -=============== ======================================================== ========================== -Option Description Default -=============== ======================================================== ========================== -``yaml_inline`` The level where you switch to inline YAML ``0`` -``yaml_indent`` The level of indentation (used internally) ``0`` -``yaml_flags`` A bit field of ``Yaml::DUMP_*`` / ``PARSE_*`` constants ``0`` - to customize the encoding / decoding YAML string -=============== ======================================================== ========================== - -.. _component-serializer-context-builders: - -Context Builders ----------------- - -Instead of passing plain PHP arrays to the :ref:`serialization context `, -you can use "context builders" to define the context using a fluent interface:: - - use Symfony\Component\Serializer\Context\Encoder\CsvEncoderContextBuilder; - use Symfony\Component\Serializer\Context\Normalizer\ObjectNormalizerContextBuilder; - - $initialContext = [ - 'custom_key' => 'custom_value', - ]; - - $contextBuilder = (new ObjectNormalizerContextBuilder()) - ->withContext($initialContext) - ->withGroups(['group1', 'group2']); - - $contextBuilder = (new CsvEncoderContextBuilder()) - ->withContext($contextBuilder) - ->withDelimiter(';'); - - $serializer->serialize($something, 'csv', $contextBuilder->toArray()); - -.. note:: - - The Serializer component provides a context builder - for each :ref:`normalizer ` - and :ref:`encoder `. - - You can also :doc:`create custom context builders ` - to deal with your context values. - -.. deprecated:: 7.2 - - The ``CsvEncoderContextBuilder::withEscapeChar()`` method was deprecated - in Symfony 7.2. - -Skipping ``null`` Values ------------------------- - -By default, the Serializer will preserve properties containing a ``null`` value. -You can change this behavior by setting the ``AbstractObjectNormalizer::SKIP_NULL_VALUES`` context option -to ``true``:: - - $dummy = new class { - public ?string $foo = null; - public string $bar = 'notNull'; - }; - - $normalizer = new ObjectNormalizer(); - $result = $normalizer->normalize($dummy, 'json', [AbstractObjectNormalizer::SKIP_NULL_VALUES => true]); - // ['bar' => 'notNull'] - -Require all Properties ----------------------- - -By default, the Serializer will add ``null`` to nullable properties when the parameters for those are not provided. -You can change this behavior by setting the ``AbstractNormalizer::REQUIRE_ALL_PROPERTIES`` context option -to ``true``:: - - class Dummy - { - public function __construct( - public string $foo, - public ?string $bar, - ) { - } - } - - $data = ['foo' => 'notNull']; - - $normalizer = new ObjectNormalizer(); - $result = $normalizer->denormalize($data, Dummy::class, 'json', [AbstractNormalizer::REQUIRE_ALL_PROPERTIES => true]); - // throws Symfony\Component\Serializer\Exception\MissingConstructorArgumentException - -Skipping Uninitialized Properties ---------------------------------- - -In PHP, typed properties have an ``uninitialized`` state which is different -from the default ``null`` of untyped properties. When you try to access a typed -property before giving it an explicit value, you get an error. - -To avoid the Serializer throwing an error when serializing or normalizing an -object with uninitialized properties, by default the object normalizer catches -these errors and ignores such properties. - -You can disable this behavior by setting the ``AbstractObjectNormalizer::SKIP_UNINITIALIZED_VALUES`` -context option to ``false``:: - - class Dummy { - public string $foo = 'initialized'; - public string $bar; // uninitialized - } - - $normalizer = new ObjectNormalizer(); - $result = $normalizer->normalize(new Dummy(), 'json', [AbstractObjectNormalizer::SKIP_UNINITIALIZED_VALUES => false]); - // throws Symfony\Component\PropertyAccess\Exception\UninitializedPropertyException as normalizer cannot read uninitialized properties - -.. note:: - - Calling ``PropertyNormalizer::normalize`` or ``GetSetMethodNormalizer::normalize`` - with ``AbstractObjectNormalizer::SKIP_UNINITIALIZED_VALUES`` context option set - to ``false`` will throw an ``\Error`` instance if the given object has uninitialized - properties as the normalizer cannot read them (directly or via getter/isser methods). - -.. _component-serializer-handling-circular-references: - -Collecting Type Errors While Denormalizing ------------------------------------------- - -When denormalizing a payload to an object with typed properties, you'll get an -exception if the payload contains properties that don't have the same type as -the object. - -In those situations, use the ``COLLECT_DENORMALIZATION_ERRORS`` option to -collect all exceptions at once, and to get the object partially denormalized:: - - try { - $dto = $serializer->deserialize($request->getContent(), MyDto::class, 'json', [ - DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS => true, - ]); - } catch (PartialDenormalizationException $e) { - $violations = new ConstraintViolationList(); - /** @var NotNormalizableValueException $exception */ - foreach ($e->getErrors() as $exception) { - $message = sprintf('The type must be one of "%s" ("%s" given).', implode(', ', $exception->getExpectedTypes()), $exception->getCurrentType()); - $parameters = []; - if ($exception->canUseMessageForUser()) { - $parameters['hint'] = $exception->getMessage(); - } - $violations->add(new ConstraintViolation($message, '', $parameters, null, $exception->getPath(), null)); - } - - return $this->json($violations, 400); - } - -Handling Circular References ----------------------------- - -Circular references are common when dealing with entity relations:: - - class Organization - { - private string $name; - private array $members; - - public function setName($name): void - { - $this->name = $name; - } - - public function getName(): string - { - return $this->name; - } - - public function setMembers(array $members): void - { - $this->members = $members; - } - - public function getMembers(): array - { - return $this->members; - } - } - - class Member - { - private string $name; - private Organization $organization; - - public function setName(string $name): void - { - $this->name = $name; - } - - public function getName(): string - { - return $this->name; - } - - public function setOrganization(Organization $organization): void - { - $this->organization = $organization; - } - - public function getOrganization(): Organization - { - return $this->organization; - } - } - -To avoid infinite loops, :class:`Symfony\\Component\\Serializer\\Normalizer\\GetSetMethodNormalizer` -or :class:`Symfony\\Component\\Serializer\\Normalizer\\ObjectNormalizer` -throw a :class:`Symfony\\Component\\Serializer\\Exception\\CircularReferenceException` -when such a case is encountered:: - - $member = new Member(); - $member->setName('KΓ©vin'); - - $organization = new Organization(); - $organization->setName('Les-Tilleuls.coop'); - $organization->setMembers([$member]); - - $member->setOrganization($organization); - - echo $serializer->serialize($organization, 'json'); // Throws a CircularReferenceException - -The key ``circular_reference_limit`` in the default context sets the number of -times it will serialize the same object before considering it a circular -reference. The default value is ``1``. - -Instead of throwing an exception, circular references can also be handled -by custom callables. This is especially useful when serializing entities -having unique identifiers:: - - $encoder = new JsonEncoder(); - $defaultContext = [ - AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER => function (object $object, ?string $format, array $context): string { - return $object->getName(); - }, - ]; - $normalizer = new ObjectNormalizer(null, null, null, null, null, null, $defaultContext); - - $serializer = new Serializer([$normalizer], [$encoder]); - var_dump($serializer->serialize($org, 'json')); - // {"name":"Les-Tilleuls.coop","members":[{"name":"K\u00e9vin", organization: "Les-Tilleuls.coop"}]} - -.. _serializer_handling-serialization-depth: - -Handling Serialization Depth ----------------------------- - -The Serializer component is able to detect and limit the serialization depth. -It is especially useful when serializing large trees. Assume the following data -structure:: - - namespace Acme; - - class MyObj - { - public string $foo; - - /** - * @var self - */ - public MyObj $child; - } - - $level1 = new MyObj(); - $level1->foo = 'level1'; - - $level2 = new MyObj(); - $level2->foo = 'level2'; - $level1->child = $level2; - - $level3 = new MyObj(); - $level3->foo = 'level3'; - $level2->child = $level3; - -The serializer can be configured to set a maximum depth for a given property. -Here, we set it to 2 for the ``$child`` property: - -.. configuration-block:: - - .. code-block:: php-attributes - - namespace Acme; - - use Symfony\Component\Serializer\Annotation\MaxDepth; - - class MyObj - { - #[MaxDepth(2)] - public MyObj $child; - - // ... - } - - .. code-block:: yaml - - Acme\MyObj: - attributes: - child: - max_depth: 2 - - .. code-block:: xml - - - - - - - - -The metadata loader corresponding to the chosen format must be configured in -order to use this feature. It is done automatically when using the Serializer component -in a Symfony application. When using the standalone component, refer to -:ref:`the groups documentation ` to -learn how to do that. - -The check is only done if the ``AbstractObjectNormalizer::ENABLE_MAX_DEPTH`` key of the serializer context -is set to ``true``. In the following example, the third level is not serialized -because it is deeper than the configured maximum depth of 2:: - - $result = $serializer->normalize($level1, null, [AbstractObjectNormalizer::ENABLE_MAX_DEPTH => true]); - /* - $result = [ - 'foo' => 'level1', - 'child' => [ - 'foo' => 'level2', - 'child' => [ - 'child' => null, - ], - ], - ]; - */ - -Instead of throwing an exception, a custom callable can be executed when the -maximum depth is reached. This is especially useful when serializing entities -having unique identifiers:: - - use Symfony\Component\Serializer\Annotation\MaxDepth; - use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; - use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader; - use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer; - use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; - use Symfony\Component\Serializer\Serializer; - - class Foo - { - public int $id; - - #[MaxDepth(1)] - public MyObj $child; - } - - $level1 = new Foo(); - $level1->id = 1; - - $level2 = new Foo(); - $level2->id = 2; - $level1->child = $level2; - - $level3 = new Foo(); - $level3->id = 3; - $level2->child = $level3; - - $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); - - // all callback parameters are optional (you can omit the ones you don't use) - $maxDepthHandler = function (object $innerObject, object $outerObject, string $attributeName, ?string $format = null, array $context = []): string { - return '/foos/'.$innerObject->id; - }; - - $defaultContext = [ - AbstractObjectNormalizer::MAX_DEPTH_HANDLER => $maxDepthHandler, - ]; - $normalizer = new ObjectNormalizer($classMetadataFactory, null, null, null, null, null, $defaultContext); - - $serializer = new Serializer([$normalizer]); - - $result = $serializer->normalize($level1, null, [AbstractObjectNormalizer::ENABLE_MAX_DEPTH => true]); - /* - $result = [ - 'id' => 1, - 'child' => [ - 'id' => 2, - 'child' => '/foos/3', - ], - ]; - */ - -Handling Arrays ---------------- - -The Serializer component is capable of handling arrays of objects as well. -Serializing arrays works just like serializing a single object:: - - use Acme\Person; - - $person1 = new Person(); - $person1->setName('foo'); - $person1->setAge(99); - $person1->setSportsman(false); - - $person2 = new Person(); - $person2->setName('bar'); - $person2->setAge(33); - $person2->setSportsman(true); - - $persons = [$person1, $person2]; - $data = $serializer->serialize($persons, 'json'); - - // $data contains [{"name":"foo","age":99,"sportsman":false},{"name":"bar","age":33,"sportsman":true}] - -If you want to deserialize such a structure, you need to add the -:class:`Symfony\\Component\\Serializer\\Normalizer\\ArrayDenormalizer` -to the set of normalizers. By appending ``[]`` to the type parameter of the -:method:`Symfony\\Component\\Serializer\\Serializer::deserialize` method, -you indicate that you're expecting an array instead of a single object:: - - use Symfony\Component\Serializer\Encoder\JsonEncoder; - use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer; - use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer; - use Symfony\Component\Serializer\Serializer; - - $serializer = new Serializer( - [new GetSetMethodNormalizer(), new ArrayDenormalizer()], - [new JsonEncoder()] - ); - - $data = ...; // The serialized data from the previous example - $persons = $serializer->deserialize($data, 'Acme\Person[]', 'json'); - -Handling Constructor Arguments ------------------------------- - -If the class constructor defines arguments, as usually happens with -`Value Objects`_, the serializer won't be able to create the object if some -arguments are missing. In those cases, use the ``default_constructor_arguments`` -context option:: - - use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; - use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; - use Symfony\Component\Serializer\Serializer; - - class MyObj - { - public function __construct( - private string $foo, - private string $bar, - ) { - } - } - - $normalizer = new ObjectNormalizer(); - $serializer = new Serializer([$normalizer]); - - $data = $serializer->denormalize( - ['foo' => 'Hello'], - 'MyObj', - null, - [AbstractNormalizer::DEFAULT_CONSTRUCTOR_ARGUMENTS => [ - 'MyObj' => ['foo' => '', 'bar' => ''], - ]] - ); - // $data = new MyObj('Hello', ''); - -Recursive Denormalization and Type Safety ------------------------------------------ - -The Serializer component can use the :doc:`PropertyInfo Component ` to denormalize -complex types (objects). The type of the class' property will be guessed using the provided -extractor and used to recursively denormalize the inner data. - -When using this component in a Symfony application, all normalizers are automatically configured to use the registered extractors. -When using the component standalone, an implementation of :class:`Symfony\\Component\\PropertyInfo\\PropertyTypeExtractorInterface`, -(usually an instance of :class:`Symfony\\Component\\PropertyInfo\\PropertyInfoExtractor`) must be passed as the 4th -parameter of the ``ObjectNormalizer``:: - - namespace Acme; - - use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; - use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; - use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; - use Symfony\Component\Serializer\Serializer; - - class ObjectOuter - { - private ObjectInner $inner; - private \DateTimeInterface $date; - - public function getInner(): ObjectInner - { - return $this->inner; - } - - public function setInner(ObjectInner $inner): void - { - $this->inner = $inner; - } - - public function getDate(): \DateTimeInterface - { - return $this->date; - } - - public function setDate(\DateTimeInterface $date): void - { - $this->date = $date; - } - } - - class ObjectInner - { - public string $foo; - public string $bar; - } - - $normalizer = new ObjectNormalizer(null, null, null, new ReflectionExtractor()); - $serializer = new Serializer([new DateTimeNormalizer(), $normalizer]); - - $obj = $serializer->denormalize( - ['inner' => ['foo' => 'foo', 'bar' => 'bar'], 'date' => '1988/01/21'], - 'Acme\ObjectOuter' - ); - - dump($obj->getInner()->foo); // 'foo' - dump($obj->getInner()->bar); // 'bar' - dump($obj->getDate()->format('Y-m-d')); // '1988-01-21' - -When a ``PropertyTypeExtractor`` is available, the normalizer will also check that the data to denormalize -matches the type of the property (even for primitive types). For instance, if a ``string`` is provided, but -the type of the property is ``int``, an :class:`Symfony\\Component\\Serializer\\Exception\\UnexpectedValueException` -will be thrown. The type enforcement of the properties can be disabled by setting -the serializer context option ``ObjectNormalizer::DISABLE_TYPE_ENFORCEMENT`` -to ``true``. - -.. _serializer_interfaces-and-abstract-classes: - -Serializing Interfaces and Abstract Classes -------------------------------------------- - -When dealing with objects that are fairly similar or share properties, you may -use interfaces or abstract classes. The Serializer component allows you to -serialize and deserialize these objects using a *"discriminator class mapping"*. - -The discriminator is the field (in the serialized string) used to differentiate -between the possible objects. In practice, when using the Serializer component, -pass a :class:`Symfony\\Component\\Serializer\\Mapping\\ClassDiscriminatorResolverInterface` -implementation to the :class:`Symfony\\Component\\Serializer\\Normalizer\\ObjectNormalizer`. - -The Serializer component provides an implementation of ``ClassDiscriminatorResolverInterface`` -called :class:`Symfony\\Component\\Serializer\\Mapping\\ClassDiscriminatorFromClassMetadata` -which uses the class metadata factory and a mapping configuration to serialize -and deserialize objects of the correct class. - -When using this component inside a Symfony application and the class metadata factory is enabled -as explained in the :ref:`Attributes Groups section `, -this is already set up and you only need to provide the configuration. Otherwise:: - - // ... - use Symfony\Component\Serializer\Encoder\JsonEncoder; - use Symfony\Component\Serializer\Mapping\ClassDiscriminatorFromClassMetadata; - use Symfony\Component\Serializer\Mapping\ClassDiscriminatorMapping; - use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; - use Symfony\Component\Serializer\Serializer; - - $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); - - $discriminator = new ClassDiscriminatorFromClassMetadata($classMetadataFactory); - - $serializer = new Serializer( - [new ObjectNormalizer($classMetadataFactory, null, null, null, $discriminator)], - ['json' => new JsonEncoder()] - ); - -Now configure your discriminator class mapping. Consider an application that -defines an abstract ``CodeRepository`` class extended by ``GitHubCodeRepository`` -and ``BitBucketCodeRepository`` classes: - -.. configuration-block:: - - .. code-block:: php-attributes - - namespace App; - - use App\BitBucketCodeRepository; - use App\GitHubCodeRepository; - use Symfony\Component\Serializer\Annotation\DiscriminatorMap; - - #[DiscriminatorMap(typeProperty: 'type', mapping: [ - 'github' => GitHubCodeRepository::class, - 'bitbucket' => BitBucketCodeRepository::class, - ])] - abstract class CodeRepository - { - // ... - } - - .. code-block:: yaml - - App\CodeRepository: - discriminator_map: - type_property: type - mapping: - github: 'App\GitHubCodeRepository' - bitbucket: 'App\BitBucketCodeRepository' - - .. code-block:: xml - - - - - - - - - - - -.. note:: - - The values of the ``mapping`` array option must be strings. - Otherwise, they will be cast into strings automatically. - -Once configured, the serializer uses the mapping to pick the correct class:: - - $serialized = $serializer->serialize(new GitHubCodeRepository(), 'json'); - // {"type": "github"} - - $repository = $serializer->deserialize($serialized, CodeRepository::class, 'json'); - // instanceof GitHubCodeRepository - -Learn more ----------- - -.. toctree:: - :maxdepth: 1 - :glob: - - /serializer - -.. seealso:: - - Normalizers for the Symfony Serializer Component supporting popular web API formats - (JSON-LD, GraphQL, OpenAPI, HAL, JSON:API) are available as part of the `API Platform`_ project. - -.. seealso:: - - A popular alternative to the Symfony Serializer component is the third-party - library, `JMS serializer`_ (versions before ``v1.12.0`` were released under - the Apache license, so incompatible with GPLv2 projects). - -.. _`PSR-1 standard`: https://www.php-fig.org/psr/psr-1/ -.. _`JMS serializer`: https://github.com/schmittjoh/serializer -.. _RFC3339: https://tools.ietf.org/html/rfc3339#section-5.8 -.. _`options with libxml`: https://www.php.net/manual/en/libxml.constants.php -.. _`DOM XML_* constants`: https://www.php.net/manual/en/dom.constants.php -.. _JSON: https://www.json.org/json-en.html -.. _XML: https://www.w3.org/XML/ -.. _YAML: https://yaml.org/ -.. _CSV: https://tools.ietf.org/html/rfc4180 -.. _`RFC 7807`: https://tools.ietf.org/html/rfc7807 -.. _`UTF-8 BOM`: https://en.wikipedia.org/wiki/Byte_order_mark -.. _`Value Objects`: https://en.wikipedia.org/wiki/Value_object -.. _`API Platform`: https://api-platform.com -.. _`list of PHP timezones`: https://www.php.net/manual/en/timezones.php -.. _`RFC 4122`: https://tools.ietf.org/html/rfc4122 -.. _`PHP reflection`: https://php.net/manual/en/book.reflection.php -.. _`data URI`: https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs -.. _seld/jsonlint: https://github.com/Seldaek/jsonlint -.. _$flags: https://www.php.net/manual/en/json.constants.php -.. _`a CDATA section`: https://en.wikipedia.org/wiki/CDATA diff --git a/components/type_info.rst b/components/type_info.rst index 30ae11aa222..47fe9dfd9ba 100644 --- a/components/type_info.rst +++ b/components/type_info.rst @@ -11,11 +11,6 @@ This component provides: * A way to get types from PHP elements such as properties, method arguments, return types, and raw strings. -.. caution:: - - This component is :doc:`experimental ` and - could be changed at any time without prior notice. - Installation ------------ @@ -45,12 +40,24 @@ to the :class:`Symfony\\Component\\TypeInfo\\Type` static methods as following:: // Many others are available and can be // found in Symfony\Component\TypeInfo\TypeFactoryTrait -The second way of using the component is to use ``TypeInfo`` to resolve a type -based on reflection or a simple string:: +Resolvers +~~~~~~~~~ + +The second way to use the component is by using ``TypeInfo`` to resolve a type +based on reflection or a simple string. This approach is designed for libraries +that need a simple way to describe a class or anything with a type:: use Symfony\Component\TypeInfo\Type; use Symfony\Component\TypeInfo\TypeResolver\TypeResolver; + class Dummy + { + public function __construct( + public int $id, + ) { + } + } + // Instantiate a new resolver $typeResolver = TypeResolver::create(); @@ -63,12 +70,6 @@ based on reflection or a simple string:: // Type instances have several helper methods - // returns the main type (e.g. in this example it returns an "array" Type instance); - // for nullable types (e.g. string|null) it returns the non-null type (e.g. string) - // and for compound types (e.g. int|string) it throws an exception because both types - // can be considered the main one, so there's no way to pick one - $baseType = $type->getBaseType(); - // for collections, it returns the type of the item used as the key; // in this example, the collection is a list, so it returns an "int" Type instance $keyType = $type->getCollectionKeyType(); @@ -81,6 +82,111 @@ Each of these calls will return you a ``Type`` instance that corresponds to the static method used. You can also resolve types from a string (as shown in the ``bool`` parameter of the previous example) -.. note:: +PHPDoc Parsing +~~~~~~~~~~~~~~ + +In many cases, you may not have cleanly typed properties or may need more precise +type definitions provided by advanced PHPDoc. To achieve this, you can use a string +resolver based on the PHPDoc annotations. + +First, run the command ``composer require phpstan/phpdoc-parser`` to install the +PHP package required for string resolving. Then, follow these steps:: + + use Symfony\Component\TypeInfo\TypeResolver\TypeResolver; + + class Dummy + { + public function __construct( + public int $id, + /** @var string[] $tags */ + public array $tags, + ) { + } + } + + $typeResolver = TypeResolver::create(); + $typeResolver->resolve(new \ReflectionProperty(Dummy::class, 'id')); // returns an "int" Type + $typeResolver->resolve(new \ReflectionProperty(Dummy::class, 'id')); // returns a collection with "int" as key and "string" as values Type + +Advanced Usages +~~~~~~~~~~~~~~~ + +The TypeInfo component provides various methods to manipulate and check types, +depending on your needs. + +**Identify** a type:: + + // define a simple integer type + $type = Type::int(); + // check if the type matches a specific identifier + $type->isIdentifiedBy(TypeIdentifier::INT); // true + $type->isIdentifiedBy(TypeIdentifier::STRING); // false + + // define a union type (equivalent to PHP's int|string) + $type = Type::union(Type::string(), Type::int()); + // now the second check is true because the union type contains the string type + $type->isIdentifiedBy(TypeIdentifier::INT); // true + $type->isIdentifiedBy(TypeIdentifier::STRING); // true + + class DummyParent {} + class Dummy extends DummyParent implements DummyInterface {} + + // define an object type + $type = Type::object(Dummy::class); + + // check if the type is an object or matches a specific class + $type->isIdentifiedBy(TypeIdentifier::OBJECT); // true + $type->isIdentifiedBy(Dummy::class); // true + // check if it inherits/implements something + $type->isIdentifiedBy(DummyParent::class); // true + $type->isIdentifiedBy(DummyInterface::class); // true + +Checking if a type **accepts a value**:: + + $type = Type::int(); + // check if the type accepts a given value + $type->accepts(123); // true + $type->accepts('z'); // false + + $type = Type::union(Type::string(), Type::int()); + // now the second check is true because the union type accepts either an int or a string value + $type->accepts(123); // true + $type->accepts('z'); // true + +.. versionadded:: 7.3 + + The :method:`Symfony\\Component\\TypeInfo\\Type::accepts` + method was introduced in Symfony 7.3. + +Using callables for **complex checks**:: + + class Foo + { + private int $integer; + private string $string; + private ?float $float; + } + + $reflClass = new \ReflectionClass(Foo::class); + + $resolver = TypeResolver::create(); + $integerType = $resolver->resolve($reflClass->getProperty('integer')); + $stringType = $resolver->resolve($reflClass->getProperty('string')); + $floatType = $resolver->resolve($reflClass->getProperty('float')); + + // define a callable to validate non-nullable number types + $isNonNullableNumber = function (Type $type): bool { + if ($type->isNullable()) { + return false; + } + + if ($type->isIdentifiedBy(TypeIdentifier::INT) || $type->isIdentifiedBy(TypeIdentifier::FLOAT)) { + return true; + } + + return false; + }; - To support raw string resolving, you need to install ``phpstan/phpdoc-parser`` package. + $integerType->isSatisfiedBy($isNonNullableNumber); // true + $stringType->isSatisfiedBy($isNonNullableNumber); // false + $floatType->isSatisfiedBy($isNonNullableNumber); // false diff --git a/components/uid.rst b/components/uid.rst index 73974ef8732..6c92fff0af9 100644 --- a/components/uid.rst +++ b/components/uid.rst @@ -386,7 +386,7 @@ entity primary keys:: // ... } -.. caution:: +.. warning:: Using UUIDs as primary keys is usually not recommended for performance reasons: indexes are slower and take more space (because UUIDs in binary format take @@ -574,7 +574,7 @@ entity primary keys:: // ... } -.. caution:: +.. warning:: Using ULIDs as primary keys is usually not recommended for performance reasons. Although ULIDs don't suffer from index fragmentation issues (because the values diff --git a/components/validator/resources.rst b/components/validator/resources.rst index c1474c1710d..5b1448dfba1 100644 --- a/components/validator/resources.rst +++ b/components/validator/resources.rst @@ -171,7 +171,7 @@ You can set this custom implementation using ->setMetadataFactory(new CustomMetadataFactory(...)) ->getValidator(); -.. caution:: +.. warning:: Since you are using a custom metadata factory, you can't configure loaders and caches using the ``add*Mapping()`` methods anymore. You now have to diff --git a/components/yaml.rst b/components/yaml.rst index 30f715a7a24..58436adffc2 100644 --- a/components/yaml.rst +++ b/components/yaml.rst @@ -428,6 +428,16 @@ you can dump them as ``~`` with the ``DUMP_NULL_AS_TILDE`` flag:: $dumped = Yaml::dump(['foo' => null], 2, 4, Yaml::DUMP_NULL_AS_TILDE); // foo: ~ +Another valid representation of the ``null`` value is an empty string. You can +use the ``DUMP_NULL_AS_EMPTY`` flag to dump null values as empty strings:: + + $dumped = Yaml::dump(['foo' => null], 2, 4, Yaml::DUMP_NULL_AS_EMPTY); + // foo: + +.. versionadded:: 7.3 + + The ``DUMP_NULL_AS_EMPTY`` flag was introduced in Symfony 7.3. + Dumping Numeric Keys as Strings ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/configuration.rst b/configuration.rst index 51ad9368098..35bc2fb7eec 100644 --- a/configuration.rst +++ b/configuration.rst @@ -267,7 +267,7 @@ reusable configuration value. By convention, parameters are defined under the // ... -.. caution:: +.. warning:: By default and when using XML configuration, the values between ```` tags are not trimmed. This means that the value of the following parameter will be @@ -379,7 +379,7 @@ a new ``locale`` parameter is added to the ``config/services.yaml`` file). By convention, parameters whose names start with a dot ``.`` (for example, ``.mailer.transport``), are available only during the container compilation. - They are useful when working with :ref:`Compiler Passes ` + They are useful when working with :doc:`Compiler Passes ` to declare some temporary parameters that won't be available later in the application. Configuration parameters are usually validation-free, but you can ensure that @@ -809,7 +809,7 @@ Use environment variables in values by prefixing variables with ``$``: DB_USER=root DB_PASS=${DB_USER}pass # include the user as a password prefix -.. caution:: +.. warning:: The order is important when some env var depends on the value of other env vars. In the above example, ``DB_PASS`` must be defined after ``DB_USER``. @@ -830,7 +830,7 @@ Embed commands via ``$()`` (not supported on Windows): START_TIME=$(date) -.. caution:: +.. warning:: Using ``$()`` might not work depending on your shell. diff --git a/configuration/env_var_processors.rst b/configuration/env_var_processors.rst index baf4037d05a..2e82104db66 100644 --- a/configuration/env_var_processors.rst +++ b/configuration/env_var_processors.rst @@ -687,7 +687,7 @@ Symfony provides the following env var processors: ], ]); - .. caution:: + .. warning:: In order to ease extraction of the resource from the URL, the leading ``/`` is trimmed from the ``path`` component. diff --git a/configuration/micro_kernel_trait.rst b/configuration/micro_kernel_trait.rst index c9739679f69..62e8c2d4128 100644 --- a/configuration/micro_kernel_trait.rst +++ b/configuration/micro_kernel_trait.rst @@ -16,7 +16,7 @@ via Composer: .. code-block:: terminal - $ composer symfony/framework-bundle symfony/runtime + $ composer require symfony/framework-bundle symfony/runtime Next, create an ``index.php`` file that defines the kernel class and runs it: diff --git a/configuration/multiple_kernels.rst b/configuration/multiple_kernels.rst index 512ea57f24d..ec8742213b5 100644 --- a/configuration/multiple_kernels.rst +++ b/configuration/multiple_kernels.rst @@ -229,7 +229,7 @@ but it should typically be added to your web server configuration. # .env APP_ID=api -.. caution:: +.. warning:: The value of this variable must match the application directory within ``apps/`` as it is used in the Kernel to load the specific application diff --git a/configuration/override_dir_structure.rst b/configuration/override_dir_structure.rst index d17b67aedba..e5dff35b6d0 100644 --- a/configuration/override_dir_structure.rst +++ b/configuration/override_dir_structure.rst @@ -111,7 +111,7 @@ In this case you have changed the location of the cache directory to You can also change the cache directory by defining an environment variable named ``APP_CACHE_DIR`` whose value is the full path of the cache folder. -.. caution:: +.. warning:: You should keep the cache directory different for each environment, otherwise some unexpected behavior may happen. Each environment generates diff --git a/console.rst b/console.rst index 57f322c983d..6a3ba70300f 100644 --- a/console.rst +++ b/console.rst @@ -366,7 +366,7 @@ Output sections let you manipulate the Console output in advanced ways, such as are updated independently and :ref:`appending rows to tables ` that have already been rendered. -.. caution:: +.. warning:: Terminals only allow overwriting the visible content, so you must take into account the console height when trying to write/overwrite section contents. @@ -531,13 +531,13 @@ call ``setAutoExit(false)`` on it to get the command result in ``CommandTester`` You can also test a whole console application by using :class:`Symfony\\Component\\Console\\Tester\\ApplicationTester`. -.. caution:: +.. warning:: When testing commands using the ``CommandTester`` class, console events are not dispatched. If you need to test those events, use the :class:`Symfony\\Component\\Console\\Tester\\ApplicationTester` instead. -.. caution:: +.. warning:: When testing commands using the :class:`Symfony\\Component\\Console\\Tester\\ApplicationTester` class, don't forget to disable the auto exit flag:: @@ -547,7 +547,7 @@ call ``setAutoExit(false)`` on it to get the command result in ``CommandTester`` $tester = new ApplicationTester($application); -.. caution:: +.. warning:: When testing ``InputOption::VALUE_NONE`` command options, you must pass ``true`` to them:: @@ -619,7 +619,7 @@ profile is accessible through the web page of the profiler. terminal supports links). If you run it in debug verbosity (``-vvv``) you'll also see the time and memory consumed by the command. -.. caution:: +.. warning:: When profiling the ``messenger:consume`` command from the :doc:`Messenger ` component, add the ``--no-reset`` option to the command or you won't get any diff --git a/console/calling_commands.rst b/console/calling_commands.rst index c5bfc6e5a72..349f1357682 100644 --- a/console/calling_commands.rst +++ b/console/calling_commands.rst @@ -36,6 +36,9 @@ method):: '--yell' => true, ]); + // disable interactive behavior for the greet command + $greetInput->setInteractive(false); + $returnCode = $this->getApplication()->doRun($greetInput, $output); // ... @@ -57,7 +60,7 @@ method):: ``$this->getApplication()->find('demo:greet')->run()`` will allow proper events to be dispatched for that inner command as well. -.. caution:: +.. warning:: Note that all the commands will run in the same process and some of Symfony's built-in commands may not work well this way. For instance, the ``cache:clear`` diff --git a/console/command_in_controller.rst b/console/command_in_controller.rst index 64475bff103..74af9e17c15 100644 --- a/console/command_in_controller.rst +++ b/console/command_in_controller.rst @@ -11,7 +11,7 @@ service that can be reused in the controller. However, when the command is part of a third-party library, you don't want to modify or duplicate their code. Instead, you can run the command directly from the controller. -.. caution:: +.. warning:: In comparison with a direct call from the console, calling a command from a controller has a slight performance impact because of the request stack diff --git a/console/commands_as_services.rst b/console/commands_as_services.rst index 75aa13d5be8..1393879a1df 100644 --- a/console/commands_as_services.rst +++ b/console/commands_as_services.rst @@ -51,7 +51,7 @@ argument (thanks to autowiring). In other words, you only need to create this class and everything works automatically! You can call the ``app:sunshine`` command and start logging. -.. caution:: +.. warning:: You *do* have access to services in ``configure()``. However, if your command is not :ref:`lazy `, try to avoid doing any @@ -130,7 +130,7 @@ only when the ``app:sunshine`` command is actually called. You don't need to call ``setName()`` for configuring the command when it is lazy. -.. caution:: +.. warning:: Calling the ``list`` command will instantiate all commands, including lazy commands. However, if the command is a ``Symfony\Component\Console\Command\LazyCommand``, then diff --git a/console/input.rst b/console/input.rst index c038ace56fc..baf47c6fd15 100644 --- a/console/input.rst +++ b/console/input.rst @@ -197,7 +197,7 @@ values after a whitespace or an ``=`` sign (e.g. ``--iterations 5`` or ``--iterations=5``), but short options can only use whitespaces or no separation at all (e.g. ``-i 5`` or ``-i5``). -.. caution:: +.. warning:: While it is possible to separate an option from its value with a whitespace, using this form leads to an ambiguity should the option appear before the diff --git a/contributing/code/bc.rst b/contributing/code/bc.rst index cff99a1554f..497c70fb01d 100644 --- a/contributing/code/bc.rst +++ b/contributing/code/bc.rst @@ -30,7 +30,7 @@ The second section, "Working on Symfony Code", is targeted at Symfony contributors. This section lists detailed rules that every contributor needs to follow to ensure smooth upgrades for our users. -.. caution:: +.. warning:: :doc:`Experimental Features ` and code marked with the ``@internal`` tags are excluded from our Backward @@ -53,7 +53,7 @@ All interfaces shipped with Symfony can be used in type hints. You can also call any of the methods that they declare. We guarantee that we won't break code that sticks to these rules. -.. caution:: +.. warning:: The exception to this rule are interfaces tagged with ``@internal``. Such interfaces should not be used or implemented. @@ -89,7 +89,7 @@ Using our Classes All classes provided by Symfony may be instantiated and accessed through their public methods and properties. -.. caution:: +.. warning:: Classes, properties and methods that bear the tag ``@internal`` as well as the classes located in the various ``*\Tests\`` namespaces are an @@ -146,7 +146,7 @@ Using our Traits All traits provided by Symfony may be used in your classes. -.. caution:: +.. warning:: The exception to this rule are traits tagged with ``@internal``. Such traits should not be used. diff --git a/contributing/code/bugs.rst b/contributing/code/bugs.rst index fba68617ee3..b0a46766026 100644 --- a/contributing/code/bugs.rst +++ b/contributing/code/bugs.rst @@ -4,7 +4,7 @@ Reporting a Bug Whenever you find a bug in Symfony, we kindly ask you to report it. It helps us make a better Symfony. -.. caution:: +.. warning:: If you think you've found a security issue, please use the special :doc:`procedure ` instead. diff --git a/contributing/code/maintenance.rst b/contributing/code/maintenance.rst index 04740ce8c6e..27e4fd73ea0 100644 --- a/contributing/code/maintenance.rst +++ b/contributing/code/maintenance.rst @@ -67,6 +67,9 @@ issue): * **Adding new deprecations**: After a version reaches stability, new deprecations cannot be added anymore. +* **Adding or updating annotations**: Adding or updating annotations (PHPDoc + annotations for instance) is not allowed; fixing them might be accepted. + Anything not explicitly listed above should be done on the next minor or major version instead. For instance, the following changes are never accepted in a patch version: diff --git a/contributing/documentation/format.rst b/contributing/documentation/format.rst index d933f3bcead..e81abe92b79 100644 --- a/contributing/documentation/format.rst +++ b/contributing/documentation/format.rst @@ -16,7 +16,7 @@ source code. If you want to learn more about this format, check out the `reStructuredText Primer`_ tutorial and the `reStructuredText Reference`_. -.. caution:: +.. warning:: If you are familiar with Markdown, be careful as things are sometimes very similar but different: diff --git a/contributing/documentation/standards.rst b/contributing/documentation/standards.rst index 420780d25f5..5e195d008fd 100644 --- a/contributing/documentation/standards.rst +++ b/contributing/documentation/standards.rst @@ -122,7 +122,7 @@ Example } } -.. caution:: +.. warning:: In YAML you should put a space after ``{`` and before ``}`` (e.g. ``{ _controller: ... }``), but this should not be done in Twig (e.g. ``{'hello' : 'value'}``). diff --git a/controller.rst b/controller.rst index 4fd03948ae2..026e76ed0a6 100644 --- a/controller.rst +++ b/controller.rst @@ -443,6 +443,26 @@ HTTP status to return if the validation fails:: The default status code returned if the validation fails is 404. +If you want to map your object to a nested array in your query using a specific key, +set the ``key`` option in the ``#[MapQueryString]`` attribute:: + + use App\Model\SearchDto; + use Symfony\Component\HttpFoundation\Response; + use Symfony\Component\HttpKernel\Attribute\MapQueryString; + + // ... + + public function dashboard( + #[MapQueryString(key: 'search')] SearchDto $searchDto + ): Response + { + // ... + } + +.. versionadded:: 7.3 + + The ``key`` option of ``#[MapQueryString]`` was introduced in Symfony 7.3. + If you need a valid DTO even when the request query string is empty, set a default value for your controller arguments:: @@ -788,6 +808,14 @@ response types. Some of these are mentioned below. To learn more about the ``Request`` and ``Response`` (and different ``Response`` classes), see the :ref:`HttpFoundation component documentation `. +.. note:: + + Technically, a controller can return a value other than a ``Response``. + However, your application is responsible for transforming that value into a + ``Response`` object. This is handled using :doc:`events ` + (specifically the :ref:`kernel.view event `), + an advanced feature you'll learn about later. + Accessing Configuration Values ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -890,7 +918,7 @@ method:: { $response = $this->sendEarlyHints([ new Link(rel: 'preconnect', href: 'https://fonts.google.com'), - (new Link(href: '/style.css'))->withAttribute('as', 'stylesheet'), + (new Link(href: '/style.css'))->withAttribute('as', 'style'), (new Link(href: '/script.js'))->withAttribute('as', 'script'), ]); @@ -946,6 +974,6 @@ Learn more about Controllers .. _`Early hints`: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/103 .. _`SAPI`: https://www.php.net/manual/en/function.php-sapi-name.php .. _`FrankenPHP`: https://frankenphp.dev -.. _`Validate Filters`: https://www.php.net/manual/en/filter.filters.validate.php +.. _`Validate Filters`: https://www.php.net/manual/en/filter.constants.php .. _`phpstan/phpdoc-parser`: https://packagist.org/packages/phpstan/phpdoc-parser .. _`phpdocumentor/type-resolver`: https://packagist.org/packages/phpdocumentor/type-resolver diff --git a/controller/error_pages.rst b/controller/error_pages.rst index 001e637c03e..fc36b88779a 100644 --- a/controller/error_pages.rst +++ b/controller/error_pages.rst @@ -319,7 +319,7 @@ error pages. .. note:: - If your listener calls ``setThrowable()`` on the + If your listener calls ``setResponse()`` on the :class:`Symfony\\Component\\HttpKernel\\Event\\ExceptionEvent` event, propagation will be stopped and the response will be sent to the client. diff --git a/deployment.rst b/deployment.rst index 3edbc34dd6b..864ebc7a963 100644 --- a/deployment.rst +++ b/deployment.rst @@ -184,7 +184,7 @@ as you normally do: significantly by building a "class map". The ``--no-dev`` flag ensures that development packages are not installed in the production environment. -.. caution:: +.. warning:: If you get a "class not found" error during this step, you may need to run ``export APP_ENV=prod`` (or ``export SYMFONY_ENV=prod`` if you're not diff --git a/deployment/proxies.rst b/deployment/proxies.rst index cd5649d979a..4dad6f95fb1 100644 --- a/deployment/proxies.rst +++ b/deployment/proxies.rst @@ -102,7 +102,7 @@ using the following configuration options: Support for the ``SYMFONY_TRUSTED_PROXIES`` and ``SYMFONY_TRUSTED_HEADERS`` environment variables was introduced in Symfony 7.2. -.. caution:: +.. danger:: Enabling the ``Request::HEADER_X_FORWARDED_HOST`` option exposes the application to `HTTP Host header attacks`_. Make sure the proxy really diff --git a/doctrine.rst b/doctrine.rst index dc42a5b9e73..171f8a3348a 100644 --- a/doctrine.rst +++ b/doctrine.rst @@ -58,7 +58,7 @@ The database connection information is stored as an environment variable called # to use oracle: # DATABASE_URL="oci8://db_user:db_password@127.0.0.1:1521/db_name" -.. caution:: +.. warning:: If the username, password, host or database name contain any character considered special in a URI (such as ``: / ? # [ ] @ ! $ & ' ( ) * + , ; =``), @@ -174,7 +174,7 @@ Whoa! You now have a new ``src/Entity/Product.php`` file:: Confused why the price is an integer? Don't worry: this is just an example. But, storing prices as integers (e.g. 100 = $1 USD) can avoid rounding issues. -.. caution:: +.. warning:: There is a `limit of 767 bytes for the index key prefix`_ when using InnoDB tables in MySQL 5.6 and earlier versions. String columns with 255 @@ -204,7 +204,7 @@ If you want to use XML instead of attributes, add ``type: xml`` and ``dir: '%kernel.project_dir%/config/doctrine'`` to the entity mappings in your ``config/packages/doctrine.yaml`` file. -.. caution:: +.. warning:: Be careful not to use reserved SQL keywords as your table or column names (e.g. ``GROUP`` or ``USER``). See Doctrine's `Reserved SQL keywords documentation`_ @@ -320,7 +320,7 @@ before, execute your migrations: $ php bin/console doctrine:migrations:migrate -.. caution:: +.. warning:: If you are using an SQLite database, you'll see the following error: *PDOException: SQLSTATE[HY000]: General error: 1 Cannot add a NOT NULL diff --git a/doctrine/custom_dql_functions.rst b/doctrine/custom_dql_functions.rst index 1b3aa4aa185..e5b21819f58 100644 --- a/doctrine/custom_dql_functions.rst +++ b/doctrine/custom_dql_functions.rst @@ -132,7 +132,7 @@ In Symfony, you can register your custom DQL functions as follows: ->datetimeFunction('test_datetime', DatetimeFunction::class); }; -.. caution:: +.. warning:: DQL functions are instantiated by Doctrine outside of the Symfony :doc:`service container ` so you can't inject services diff --git a/doctrine/multiple_entity_managers.rst b/doctrine/multiple_entity_managers.rst index 014d9e4dccb..1a56c55ddad 100644 --- a/doctrine/multiple_entity_managers.rst +++ b/doctrine/multiple_entity_managers.rst @@ -15,7 +15,7 @@ entities, each with their own database connection strings or separate cache conf advanced and not usually required. Be sure you actually need multiple entity managers before adding in this layer of complexity. -.. caution:: +.. warning:: Entities cannot define associations across different entity managers. If you need that, there are `several alternatives`_ that require some custom setup. @@ -142,7 +142,7 @@ and ``customer``. The ``default`` entity manager manages entities in the entities in ``src/Entity/Customer``. You've also defined two connections, one for each entity manager, but you are free to define the same connection for both. -.. caution:: +.. warning:: When working with multiple connections and entity managers, you should be explicit about which configuration you want. If you *do* omit the name of @@ -251,7 +251,7 @@ The same applies to repository calls:: } } -.. caution:: +.. warning:: One entity can be managed by more than one entity manager. This however results in unexpected behavior when extending from ``ServiceEntityRepository`` diff --git a/event_dispatcher.rst b/event_dispatcher.rst index 6787cba2d83..27885af267b 100644 --- a/event_dispatcher.rst +++ b/event_dispatcher.rst @@ -41,6 +41,9 @@ The most common way to listen to an event is to register an **event listener**:: // Customize your response object to display the exception details $response = new Response(); $response->setContent($message); + // the exception message can contain unfiltered user input; + // set the content-type to text to avoid XSS issues + $response->headers->set('Content-Type', 'text/plain; charset=utf-8'); // HttpExceptionInterface is a special type of exception that // holds status code and header details @@ -798,3 +801,11 @@ could listen to the ``mailer.post_send`` event and change the method's return va That's it! Your subscriber should be called automatically (or read more about :ref:`event subscriber configuration `). + +Learn More +---------- + +- :ref:`The Request-Response Lifecycle ` +- :doc:`/reference/events` +- :ref:`Security-related Events ` +- :doc:`/components/event_dispatcher` diff --git a/form/bootstrap5.rst b/form/bootstrap5.rst index 400747bba12..db098a1ba09 100644 --- a/form/bootstrap5.rst +++ b/form/bootstrap5.rst @@ -171,7 +171,7 @@ class to the label: ], // ... -.. caution:: +.. warning:: Switches only work with **checkbox**. @@ -201,7 +201,7 @@ class to the ``row_attr`` option. } }) }} -.. caution:: +.. warning:: If you fill the ``help`` option of your form, it will also be rendered as part of the group. @@ -239,7 +239,7 @@ of your form type. } }) }} -.. caution:: +.. warning:: You **must** provide a ``label`` and a ``placeholder`` to make floating labels work properly. diff --git a/form/create_custom_field_type.rst b/form/create_custom_field_type.rst index 709f3321544..0d92a967fa0 100644 --- a/form/create_custom_field_type.rst +++ b/form/create_custom_field_type.rst @@ -449,7 +449,7 @@ are some examples of Twig block names for the postal address type: ``postal_address_zipCode_label`` The label block of the ZIP Code field. -.. caution:: +.. warning:: When the name of your form class matches any of the built-in field types, your form might not be rendered correctly. A form type named diff --git a/form/data_mappers.rst b/form/data_mappers.rst index cb5c7936701..38c92ce35ae 100644 --- a/form/data_mappers.rst +++ b/form/data_mappers.rst @@ -126,7 +126,7 @@ in your form type:: } } -.. caution:: +.. warning:: The data passed to the mapper is *not yet validated*. This means that your objects should allow being created in an invalid state in order to produce @@ -215,7 +215,7 @@ If available, these options have priority over the property path accessor and the default data mapper will still use the :doc:`PropertyAccess component ` for the other form fields. -.. caution:: +.. warning:: When a form has the ``inherit_data`` option set to ``true``, it does not use the data mapper and lets its parent map inner values. diff --git a/form/data_transformers.rst b/form/data_transformers.rst index 4e81fc3e930..db051a04bbc 100644 --- a/form/data_transformers.rst +++ b/form/data_transformers.rst @@ -8,7 +8,7 @@ can be rendered as a ``yyyy-MM-dd``-formatted input text box. Internally, a data converts the ``DateTime`` value of the field to a ``yyyy-MM-dd`` formatted string when rendering the form, and then back to a ``DateTime`` object on submit. -.. caution:: +.. warning:: When a form field has the ``inherit_data`` option set to ``true``, data transformers are not applied to that field. @@ -340,7 +340,7 @@ that, after a successful submission, the Form component will pass a real If the issue isn't found, a form error will be created for that field and its error message can be controlled with the ``invalid_message`` field option. -.. caution:: +.. warning:: Be careful when adding your transformers. For example, the following is **wrong**, as the transformer would be applied to the entire form, instead of just this @@ -472,7 +472,7 @@ Which transformer you need depends on your situation. To use the view transformer, call ``addViewTransformer()``. -.. caution:: +.. warning:: Be careful with model transformers and :doc:`Collection ` field types. diff --git a/form/direct_submit.rst b/form/direct_submit.rst index 7b98134af18..7a08fb6978a 100644 --- a/form/direct_submit.rst +++ b/form/direct_submit.rst @@ -65,7 +65,7 @@ the fields defined by the form class. Otherwise, you'll see a form validation er argument to ``submit()``. Passing ``false`` will remove any missing fields within the form object. Otherwise, the missing fields will be set to ``null``. -.. caution:: +.. warning:: When the second parameter ``$clearMissing`` is ``false``, like with the "PATCH" method, the validation will only apply to the submitted fields. If diff --git a/form/events.rst b/form/events.rst index 745df2df453..dad6c242ddd 100644 --- a/form/events.rst +++ b/form/events.rst @@ -192,7 +192,7 @@ Form view data Same as in ``FormEvents::POST_SET_DATA`` See all form events at a glance in the :ref:`Form Events Information Table `. -.. caution:: +.. warning:: At this point, you cannot add or remove fields to the form. @@ -225,7 +225,7 @@ Form view data Normalized data transformed using a view transformer See all form events at a glance in the :ref:`Form Events Information Table `. -.. caution:: +.. warning:: At this point, you cannot add or remove fields to the current form and its children. diff --git a/form/form_collections.rst b/form/form_collections.rst index f0ad76a8a61..2a0ba99657f 100644 --- a/form/form_collections.rst +++ b/form/form_collections.rst @@ -195,7 +195,7 @@ then set on the ``tag`` field of the ``Task`` and can be accessed via ``$task->g So far, this works great, but only to edit *existing* tags. It doesn't allow us yet to add new tags or delete existing ones. -.. caution:: +.. warning:: You can embed nested collections as many levels down as you like. However, if you use Xdebug, you may receive a ``Maximum function nesting level of '100' @@ -427,13 +427,13 @@ That was fine, but forcing the use of the "adder" method makes handling these new ``Tag`` objects easier (especially if you're using Doctrine, which you will learn about next!). -.. caution:: +.. warning:: You have to create **both** ``addTag()`` and ``removeTag()`` methods, otherwise the form will still use ``setTag()`` even if ``by_reference`` is ``false``. You'll learn more about the ``removeTag()`` method later in this article. -.. caution:: +.. warning:: Symfony can only make the plural-to-singular conversion (e.g. from the ``tags`` property to the ``addTag()`` method) for English words. Code diff --git a/form/form_customization.rst b/form/form_customization.rst index 3f3cd0bbc89..1c23601a883 100644 --- a/form/form_customization.rst +++ b/form/form_customization.rst @@ -74,7 +74,7 @@ control over how each form field is rendered, so you can fully customize them: -.. caution:: +.. warning:: If you're rendering each field manually, make sure you don't forget the ``_token`` field that is automatically added for CSRF protection. @@ -305,7 +305,7 @@ Renders any errors for the given field. {# render any "global" errors not associated to any form field #} {{ form_errors(form) }} -.. caution:: +.. warning:: In the Bootstrap 4 form theme, ``form_errors()`` is already included in ``form_label()``. Read more about this in the diff --git a/form/form_themes.rst b/form/form_themes.rst index eb6f6f2ae22..8b82982edaa 100644 --- a/form/form_themes.rst +++ b/form/form_themes.rst @@ -177,7 +177,7 @@ of form themes: {# ... #} -.. caution:: +.. warning:: When using the ``only`` keyword, none of Symfony's built-in form themes (``form_div_layout.html.twig``, etc.) will be applied. In order to render diff --git a/form/inherit_data_option.rst b/form/inherit_data_option.rst index 19b14b27bcd..2caa0afcdbe 100644 --- a/form/inherit_data_option.rst +++ b/form/inherit_data_option.rst @@ -165,6 +165,6 @@ Finally, make this work by adding the location form to your two original forms:: That's it! You have extracted duplicated field definitions to a separate location form that you can reuse wherever you need it. -.. caution:: +.. warning:: Forms with the ``inherit_data`` option set cannot have ``*_SET_DATA`` event listeners. diff --git a/form/type_guesser.rst b/form/type_guesser.rst index 111f1b77986..106eb4e7742 100644 --- a/form/type_guesser.rst +++ b/form/type_guesser.rst @@ -162,7 +162,7 @@ instance with the value of the option. This constructor has 2 arguments: ``null`` is guessed when you believe the value of the option should not be set. -.. caution:: +.. warning:: You should be very careful using the ``guessMaxLength()`` method. When the type is a float, you cannot determine a length (e.g. you want a float to be diff --git a/form/unit_testing.rst b/form/unit_testing.rst index bf57e6d1afc..9603c5bc0d2 100644 --- a/form/unit_testing.rst +++ b/form/unit_testing.rst @@ -1,7 +1,7 @@ How to Unit Test your Forms =========================== -.. caution:: +.. warning:: This article is intended for developers who create :doc:`custom form types `. If you are using @@ -121,7 +121,7 @@ variable exists and will be available in your form themes:: Use `PHPUnit data providers`_ to test multiple form conditions using the same test code. -.. caution:: +.. warning:: When your type relies on the ``EntityType``, you should register the :class:`Symfony\\Bridge\\Doctrine\\Form\\DoctrineOrmExtension`, which will @@ -214,7 +214,7 @@ allows you to return a list of extensions to register:: { $validator = Validation::createValidator(); - // or if you also need to read constraints from annotations + // or if you also need to read constraints from attributes $validator = Validation::createValidatorBuilder() ->enableAttributeMapping() ->getValidator(); diff --git a/form/without_class.rst b/form/without_class.rst index 589f8a4739e..436976bdfcc 100644 --- a/form/without_class.rst +++ b/form/without_class.rst @@ -121,7 +121,7 @@ but here's a short example:: submitted data is validated using the ``Symfony\Component\Validator\Constraints\Valid`` constraint, unless you :doc:`disable validation `. -.. caution:: +.. warning:: When a form is only partially submitted (for example, in an HTTP PATCH request), only the constraints from the submitted form fields will be diff --git a/forms.rst b/forms.rst index a90e4ee1772..008c60a66c6 100644 --- a/forms.rst +++ b/forms.rst @@ -869,7 +869,7 @@ pass ``null`` to it:: } } -.. caution:: +.. warning:: When using a specific :doc:`form validation group `, the field type guesser will still consider *all* validation constraints when diff --git a/frontend.rst b/frontend.rst index f498dc737b5..c28e6fcf222 100644 --- a/frontend.rst +++ b/frontend.rst @@ -61,6 +61,10 @@ be executed by a browser. AssetMapper (Recommended) ~~~~~~~~~~~~~~~~~~~~~~~~~ +.. screencast:: + + Do you prefer video tutorials? Check out the `AssetMapper screencast series`_. + AssetMapper is the recommended system for handling your assets. It runs entirely in PHP with no complex build step or dependencies. It does this by leveraging the ``importmap`` feature of your browser, which is available in all browsers thanks @@ -118,6 +122,10 @@ the `StimulusBundle Documentation`_ Using a Front-end Framework (React, Vue, Svelte, etc) ----------------------------------------------------- +.. screencast:: + + Do you prefer video tutorials? Check out the `API Platform screencast series`_. + If you want to use a front-end framework (Next.js, React, Vue, Svelte, etc), we recommend using their native tools and using Symfony as a pure API. A wonderful tool to do that is `API Platform`_. Their standard distribution comes with a @@ -143,3 +151,5 @@ Other Front-End Articles .. _`Symfony UX`: https://ux.symfony.com .. _`API Platform`: https://api-platform.com/ .. _`SensioLabs Minify Bundle`: https://github.com/sensiolabs/minify-bundle +.. _`AssetMapper screencast series`: https://symfonycasts.com/screencast/asset-mapper +.. _`API Platform screencast series`: https://symfonycasts.com/screencast/api-platform diff --git a/frontend/asset_mapper.rst b/frontend/asset_mapper.rst index 685474e66d3..d68c77c0105 100644 --- a/frontend/asset_mapper.rst +++ b/frontend/asset_mapper.rst @@ -12,7 +12,7 @@ The component has two main features: * :ref:`Mapping & Versioning Assets `: All files inside of ``assets/`` are made available publicly and **versioned**. You can reference the file ``assets/images/product.jpg`` in a Twig template with ``{{ asset('images/product.jpg') }}``. - The final URL will include a version hash, like ``/assets/images/product-3c16d9220694c0e56d8648f25e6035e9.jpg``. + The final URL will include a version hash, like ``/assets/images/product-3c16d92m.jpg``. * :ref:`Importmaps `: A native browser feature that makes it easier to use the JavaScript ``import`` statement (e.g. ``import { Modal } from 'bootstrap'``) @@ -70,7 +70,7 @@ The path - ``images/duck.png`` - is relative to your mapped directory (``assets/ This is known as the **logical path** to your asset. If you look at the HTML in your page, the URL will be something -like: ``/assets/images/duck-3c16d9220694c0e56d8648f25e6035e9.png``. If you change +like: ``/assets/images/duck-3c16d92m.png``. If you change the file, the version part of the URL will also change automatically. .. _asset-mapper-compile-assets: @@ -78,7 +78,7 @@ the file, the version part of the URL will also change automatically. Serving Assets in dev vs prod ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -In the ``dev`` environment, the URL ``/assets/images/duck-3c16d9220694c0e56d8648f25e6035e9.png`` +In the ``dev`` environment, the URL ``/assets/images/duck-3c16d92m.png`` is handled and returned by your Symfony app. For the ``prod`` environment, before deploy, you should run: @@ -91,7 +91,7 @@ This will physically copy all the files from your mapped directories to ``public/assets/`` so that they're served directly by your web server. See :ref:`Deployment ` for more details. -.. caution:: +.. warning:: If you run the ``asset-map:compile`` command on your development machine, you won't see any changes made to your assets when reloading the page. @@ -283,9 +283,9 @@ outputs an `importmap`_: @@ -342,8 +342,8 @@ The ``importmap()`` function also outputs a set of "preloads": .. code-block:: html - - + + This is a performance optimization and you can learn more about below in :ref:`Performance: Add Preloading `. @@ -415,6 +415,8 @@ from inside ``app.js``: // things on "window" become global variables window.$ = $; +.. _asset-mapper-handling-css: + Handling CSS ------------ @@ -492,9 +494,9 @@ for ``duck.png``: .. code-block:: css - /* public/assets/styles/app-3c16d9220694c0e56d8648f25e6035e9.css */ + /* public/assets/styles/app-3c16d92m.css */ .quack { - background-image: url('../images/duck-3c16d9220694c0e56d8648f25e6035e9.png'); + background-image: url('../images/duck-3c16d92m.png'); } .. _asset-mapper-tailwind: @@ -571,7 +573,7 @@ Sometimes a JavaScript file you're importing (e.g. ``import './duck.js'``), or a CSS/image file you're referencing won't be found, and you'll see a 404 error in your browser's console. You'll also notice that the 404 URL is missing the version hash in the filename (e.g. a 404 to ``/assets/duck.js`` instead of -a path like ``/assets/duck.1b7a64b3b3d31219c262cf72521a5267.js``). +a path like ``/assets/duck-1b7a64b3.js``). This is usually because the path is wrong. If you're referencing the file directly in a Twig template: @@ -646,7 +648,7 @@ To make your AssetMapper-powered site fly, there are a few things you need to do. If you want to take a shortcut, you can use a service like `Cloudflare`_, which will automatically do most of these things for you: -- **Use HTTP/2**: Your web server should be running HTTP/2 (or HTTP/3) so the +- **Use HTTP/2**: Your web server should be running HTTP/2 or HTTP/3 so the browser can download assets in parallel. HTTP/2 is automatically enabled in Caddy and can be activated in Nginx and Apache. Or, proxy your site through a service like Cloudflare, which will automatically enable HTTP/2 for you. @@ -654,7 +656,9 @@ which will automatically do most of these things for you: - **Compress your assets**: Your web server should compress (e.g. using gzip) your assets (JavaScript, CSS, images) before sending them to the browser. This is automatically enabled in Caddy and can be activated in Nginx and Apache. - In Cloudflare, assets are compressed by default. + In Cloudflare, assets are compressed by default. AssetMapper also supports + :ref:`precompressing your web assets ` to further + improve performance. - **Set long-lived cache expiry**: Your web server should set a long-lived ``Cache-Control`` HTTP header on your assets. Because the AssetMapper component includes a version @@ -702,6 +706,76 @@ even though it hasn't yet seen the ``import`` statement for them. Additionally, if the :doc:`WebLink Component ` is available in your application, Symfony will add a ``Link`` header in the response to preload the CSS files. +.. _performance-precompressing: + +Pre-Compressing Assets +---------------------- + +Although most servers (Caddy, Nginx, Apache, FrankenPHP) and services like Cloudflare +provide asset compression features, AssetMapper also allows you to compress all +your assets before serving them. + +This improves performance because you can compress assets using the highest (and +slowest) compression ratios beforehand and provide those compressed assets to the +server, which then returns them to the client without wasting CPU resources on +compression. + +AssetMapper supports `Brotli`_, `Zstandard`_ and `gzip`_ compression formats. +Before using any of them, the server that pre-compresses assets must have +installed the following PHP extensions or CLI commands: + +* Brotli: ``brotli`` CLI command; `brotli PHP extension`_; +* Zstandard: ``zstd`` CLI command; `zstd PHP extension`_; +* gzip: ``zopfli`` (better) or ``gzip`` CLI command; `zlib PHP extension`_. + +Then, update your AssetMapper configuration to define which compression to use +and which file extensions should be compressed: + +.. code-block:: yaml + + # config/packages/asset_mapper.yaml + framework: + asset_mapper: + # ... + + precompress: + format: 'zstandard' + # if you don't define the following option, AssetMapper will compress all + # the extensions considered safe (css, js, json, svg, xml, ttf, otf, wasm, etc.) + extensions: ['css', 'js', 'json', 'svg', 'xml'] + +Now, when running the ``asset-map:compile`` command, all matching files will be +compressed in the configured format and at the highest compression level. The +compressed files are created with the same name as the original but with the +``.br``, ``.zst``, or ``.gz`` extension appended. + +Then, you need to configure your web server to serve the precompressed assets +instead of the original ones: + +.. configuration-block:: + + .. code-block:: caddy + + file_server { + precompressed br zstd gzip + } + + .. code-block:: nginx + + gzip_static on; + + # Requires https://github.com/google/ngx_brotli + brotli_static on; + + # Requires https://github.com/tokers/zstd-nginx-module + zstd_static on; + +.. tip:: + + AssetMapper provides an ``assets:compress`` CLI command and a service called + ``asset_mapper.compressor`` that you can use anywhere in your application to + compress any kind of files (e.g. files uploaded by users to your application). + Frequently Asked Questions -------------------------- @@ -846,7 +920,7 @@ be versioned! It will output something like: .. code-block:: html+twig - + Overriding 3rd-Party Assets ~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1093,6 +1167,24 @@ it in the CSP header, and then pass the same nonce to the Twig function: {# the csp_nonce() function is defined by the NelmioSecurityBundle #} {{ importmap('app', {'nonce': csp_nonce('script')}) }} +Content Security Policy and CSS Files +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If your importmap includes CSS files, AssetMapper uses a trick to load those by +adding ``data:application/javascript`` to the rendered importmap (see +:ref:`Handling CSS `). + +This can cause browsers to report CSP violations and block the CSS files from +being loaded. To prevent this, you can add `strict-dynamic`_ to the ``script-src`` +directive of your Content Security Policy, to tell the browser that the importmap +is allowed to load other resources. + +.. note:: + + When using ``strict-dynamic``, the browser will ignore any other sources in + ``script-src`` such as ``'self'`` or ``'unsafe-inline'``, so any other + ``