diff --git a/.doctor-rst.yaml b/.doctor-rst.yaml
index 2ecd4fc7d2d..03806a68585 100644
--- a/.doctor-rst.yaml
+++ b/.doctor-rst.yaml
@@ -8,6 +8,7 @@ rules:
correct_code_block_directive_based_on_the_content: ~
deprecated_directive_should_have_version: ~
ensure_bash_prompt_before_composer_command: ~
+ ensure_class_constant: ~
ensure_correct_format_for_phpfunction: ~
ensure_exactly_one_space_before_directive_type: ~
ensure_exactly_one_space_between_link_definition_and_link: ~
@@ -49,6 +50,7 @@ rules:
no_namespace_after_use_statements: ~
no_php_open_tag_in_code_block_php_directive: ~
no_space_before_self_xml_closing_tag: ~
+ no_typographic_quotes: ~
non_static_phpunit_assertions: ~
only_backslashes_in_namespace_in_php_code_block: ~
only_backslashes_in_use_statements_in_php_code_block: ~
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 497dfd9b430..3722546330d 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -72,7 +72,7 @@ jobs:
key: ${{ runner.os }}-doctor-rst-${{ steps.extract_base_branch.outputs.branch }}
- name: "Run DOCtor-RST"
- uses: docker://oskarstark/doctor-rst:1.67.0
+ uses: docker://oskarstark/doctor-rst:1.69.1
with:
args: --short --error-format=github --cache-file=/github/workspace/.cache/doctor-rst.cache
diff --git a/_build/build.php b/_build/build.php
index 5298abe779a..b684700a848 100755
--- a/_build/build.php
+++ b/_build/build.php
@@ -15,6 +15,13 @@
->addOption('generate-fjson-files', null, InputOption::VALUE_NONE, 'Use this option to generate docs both in HTML and JSON formats')
->addOption('disable-cache', null, InputOption::VALUE_NONE, 'Use this option to force a full regeneration of all doc contents')
->setCode(function(InputInterface $input, OutputInterface $output) {
+ // the doc building app doesn't work on Windows
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ $output->writeln('ERROR: The application that builds Symfony Docs does not support Windows. You can try using a Linux distribution via WSL (Windows Subsystem for Linux). ');
+
+ return 1;
+ }
+
$io = new SymfonyStyle($input, $output);
$io->text('Building all Symfony Docs...');
diff --git a/best_practices.rst b/best_practices.rst
index 2c393cae9c6..6211d042f0b 100644
--- a/best_practices.rst
+++ b/best_practices.rst
@@ -95,7 +95,7 @@ Use Secrets for Sensitive Information
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When your application has sensitive configuration, like an API key, you should
-store those securely via :doc:`Symfony’s secrets management system `.
+store those securely via :doc:`Symfony's secrets management system `.
Use Parameters for Application Configuration
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -362,10 +362,6 @@ Unless you have two legitimately different authentication systems and users
(e.g. form login for the main site and a token system for your API only), it's
recommended to have only one firewall to keep things simple.
-Additionally, you should use the ``anonymous`` key under your firewall. If you
-require users to be logged in for different sections of your site, use the
-:doc:`access_control ` option.
-
Use the ``auto`` Password Hasher
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/bundles/best_practices.rst b/bundles/best_practices.rst
index 37dc386b8e4..8049ebb9a1c 100644
--- a/bundles/best_practices.rst
+++ b/bundles/best_practices.rst
@@ -195,25 +195,24 @@ Continuous Integration
Testing bundle code continuously, including all its commits and pull requests,
is a good practice called Continuous Integration. There are several services
-providing this feature for free for open source projects, like `GitHub Actions`_
-and `Travis CI`_.
+providing this feature for free for open source projects, like `GitHub Actions`_.
A bundle should at least test:
* The lower bound of their dependencies (by running ``composer update --prefer-lowest``);
* The supported PHP versions;
-* All supported major Symfony versions (e.g. both ``4.x`` and ``5.x`` if
+* All supported major Symfony versions (e.g. both ``6.4`` and ``7.x`` if
support is claimed for both).
-Thus, a bundle supporting PHP 7.3, 7.4 and 8.0, and Symfony 4.4 and 5.x should
+Thus, a bundle supporting PHP 7.4, 8.3 and 8.4, and Symfony 6.4 and 7.x should
have at least this test matrix:
=========== =============== ===================
PHP version Symfony version Composer flags
=========== =============== ===================
-7.3 ``4.*`` ``--prefer-lowest``
-7.4 ``5.*``
-8.0 ``5.*``
+7.4 ``6.4`` ``--prefer-lowest``
+8.3 ``7.*``
+8.4 ``7.*``
=========== =============== ===================
.. tip::
@@ -233,10 +232,10 @@ with Symfony Flex to install a specific Symfony version:
.. code-block:: bash
- # this requires Symfony 5.x for all Symfony packages
- export SYMFONY_REQUIRE=5.*
+ # this requires Symfony 7.x for all Symfony packages
+ export SYMFONY_REQUIRE=7.*
# alternatively you can run this command to update composer.json config
- # composer config extra.symfony.require "5.*"
+ # composer config extra.symfony.require "7.*"
# install Symfony Flex in the CI environment
composer global config --no-plugins allow-plugins.symfony/flex true
@@ -560,6 +559,7 @@ Learn more
* :doc:`/bundles/extension`
* :doc:`/bundles/configuration`
+* :doc:`/frontend/create_ux_bundle`
.. _`PSR-4`: https://www.php-fig.org/psr/psr-4/
.. _`Symfony Flex recipe`: https://github.com/symfony/recipes
@@ -568,4 +568,3 @@ Learn more
.. _`choose any license`: https://choosealicense.com/
.. _`valid license identifier`: https://spdx.org/licenses/
.. _`GitHub Actions`: https://docs.github.com/en/free-pro-team@latest/actions
-.. _`Travis CI`: https://docs.travis-ci.com/
diff --git a/cache.rst b/cache.rst
index 83bb5b4cedc..5687d3544b6 100644
--- a/cache.rst
+++ b/cache.rst
@@ -539,6 +539,8 @@ Symfony stores the item automatically in all the missing pools.
;
};
+.. _cache-using-cache-tags:
+
Using Cache Tags
----------------
@@ -950,9 +952,9 @@ a message bus to compute values in a worker:
.. code-block:: php
// config/framework/framework.php
- use function Symfony\Component\DependencyInjection\Loader\Configurator\env;
use Symfony\Component\Cache\Messenger\EarlyExpirationMessage;
use Symfony\Config\FrameworkConfig;
+ use function Symfony\Component\DependencyInjection\Loader\Configurator\env;
return static function (FrameworkConfig $framework): void {
$framework->cache()
diff --git a/components/browser_kit.rst b/components/browser_kit.rst
index bcb8f7b3c8e..8cf0772298c 100644
--- a/components/browser_kit.rst
+++ b/components/browser_kit.rst
@@ -143,7 +143,7 @@ field values, etc.) before submitting it::
$crawler = $client->request('GET', 'https://github.com/login');
// find the form with the 'Log in' button and submit it
- // 'Log in' can be the text content, id, value or name of a or
+ // 'Log in' can be the text content, id or name of a or
$client->submitForm('Log in');
// the second optional argument lets you override the default form field values
diff --git a/components/cache.rst b/components/cache.rst
index 4873fb7abc7..44ba8b5c151 100644
--- a/components/cache.rst
+++ b/components/cache.rst
@@ -84,6 +84,69 @@ generate and return the value::
Use cache tags to delete more than one key at the time. Read more at
:doc:`/components/cache/cache_invalidation`.
+Creating Sub-Namespaces
+-----------------------
+
+.. versionadded:: 7.3
+
+ Cache sub-namespaces were introduced in Symfony 7.3.
+
+Sometimes you need to create context-dependent variations of data that should be
+cached. For example, the data used to render a dashboard page may be expensive
+to generate and unique per user, so you can't cache the same data for everyone.
+
+In such cases, Symfony allows you to create different cache contexts using
+namespaces. A cache namespace is an arbitrary string that identifies a set of
+related cache items. All cache adapters provided by the component implement the
+:class:`Symfony\\Contracts\\Cache\\NamespacedPoolInterface`, which provides the
+:method:`Symfony\\Contracts\\Cache\\NamespacedPoolInterface::withSubNamespace`
+method.
+
+This method allows you to namespace cached items by transparently prefixing their keys::
+
+ $userCache = $cache->withSubNamespace(sprintf('user-%d', $user->getId()));
+
+ $userCache->get('dashboard_data', function (ItemInterface $item): string {
+ $item->expiresAfter(3600);
+
+ return '...';
+ });
+
+In this example, the cache item uses the ``dashboard_data`` key, but it will be
+stored internally under a namespace based on the current user ID. This is handled
+automatically, so you **don't** need to manually prefix keys like ``user-27.dashboard_data``.
+
+There are no guidelines or restrictions on how to define cache namespaces.
+You can make them as granular or as generic as your application requires::
+
+ $localeCache = $cache->withSubNamespace($request->getLocale());
+
+ $flagCache = $cache->withSubNamespace(
+ $featureToggle->isEnabled('new_checkout') ? 'checkout-v2' : 'checkout-v1'
+ );
+
+ $channel = $request->attributes->get('_route')?->startsWith('api_') ? 'api' : 'web';
+ $channelCache = $cache->withSubNamespace($channel);
+
+.. tip::
+
+ You can combine cache namespaces with :ref:`cache tags `
+ for more advanced needs.
+
+There is no built-in way to invalidate caches by namespace. Instead, the recommended
+approach is to change the namespace itself. For this reason, it's common to include
+static or dynamic versioning data in the cache namespace::
+
+ // for simple applications, an incrementing static version number may be enough
+ $userCache = $cache->withSubNamespace(sprintf('v1-user-%d', $user->getId()));
+
+ // other applications may use dynamic versioning based on the date (e.g. monthly)
+ $userCache = $cache->withSubNamespace(sprintf('%s-user-%d', date('Ym'), $user->getId()));
+
+ // or even invalidate the cache when the user data changes
+ $checksum = hash('xxh128', $user->getUpdatedAt()->format(DATE_ATOM));
+ $userCache = $cache->withSubNamespace(sprintf('user-%d-%s', $user->getId(), $checksum));
+
.. _cache_stampede-prevention:
Stampede Prevention
diff --git a/components/cache/adapters/redis_adapter.rst b/components/cache/adapters/redis_adapter.rst
index 3362f4cc2db..c0295b487a0 100644
--- a/components/cache/adapters/redis_adapter.rst
+++ b/components/cache/adapters/redis_adapter.rst
@@ -8,7 +8,8 @@ Redis Cache Adapter
:ref:`Symfony Cache configuration `
article if you are using it in a Symfony application.
-This adapter stores the values in-memory using one (or more) `Redis server`_ instances.
+This adapter stores the values in-memory using one (or more) `Redis server`_
+of `Valkey`_ server instances.
Unlike the :doc:`APCu adapter `, and similarly to the
:doc:`Memcached adapter `, it is not limited to the current server's
@@ -19,9 +20,9 @@ to utilize a cluster of servers to provide redundancy and/or fail-over is also a
**Requirements:** At least one `Redis server`_ must be installed and running to use this
adapter. Additionally, this adapter requires a compatible extension or library that implements
- ``\Redis``, ``\RedisArray``, ``RedisCluster``, ``\Relay\Relay`` or ``\Predis``.
+ ``\Redis``, ``\RedisArray``, ``RedisCluster``, ``\Relay\Relay``, ``\Relay\Cluster`` or ``\Predis``.
-This adapter expects a `Redis`_, `RedisArray`_, `RedisCluster`_, `Relay`_ or `Predis`_ instance to be
+This adapter expects a `Redis`_, `RedisArray`_, `RedisCluster`_, `Relay`_, `RelayCluster`_ or `Predis`_ instance to be
passed as the first parameter. A namespace and default cache lifetime can optionally be passed
as the second and third parameters::
@@ -47,6 +48,10 @@ as the second and third parameters::
?MarshallerInterface $marshaller = null
);
+.. versionadded:: 7.3
+
+ Support for ``Relay\Cluster`` was introduced in Symfony 7.3.
+
Configure the Connection
------------------------
@@ -61,6 +66,11 @@ helper method allows creating and configuring the Redis client class instance us
'redis://localhost'
);
+.. versionadded:: 7.3
+
+ Starting in Symfony 7.3, when using Valkey servers you can use the
+ ``valkey[s]:`` scheme instead of the ``redis[s]:`` one in your DSNs.
+
The DSN can specify either an IP/host (and an optional port) or a socket path, as well as a
password and a database index. To enable TLS for connections, the scheme ``redis`` must be
replaced by ``rediss`` (the second ``s`` means "secure").
@@ -220,11 +230,34 @@ Available Options
``ssl`` (type: ``array``, default: ``null``)
SSL context options. See `php.net/context.ssl`_ for more information.
+``relay_cluster_context`` (type: ``array``, default: ``[]``)
+ Defines configuration options specific to ``\Relay\Cluster``. For example, to
+ user a self-signed certificate for testing in local environment::
+
+ $options = [
+ // ...
+ 'relay_cluster_context' => [
+ // ...
+ 'stream' => [
+ 'verify_peer' => false,
+ 'verify_peer_name' => false,
+ 'allow_self_signed' => true,
+ 'local_cert' => '/valkey.crt',
+ 'local_pk' => '/valkey.key',
+ 'cafile' => '/valkey.crt',
+ ],
+ ],
+ ];
+
.. versionadded:: 7.1
- The option `sentinel_master` as an alias for `redis_sentinel` was introduced
+ The option ``sentinel_master`` as an alias for ``redis_sentinel`` was introduced
in Symfony 7.1.
+.. versionadded:: 7.3
+
+ The ``relay_cluster_context`` option was introduced in Symfony 7.3.
+
.. note::
When using the `Predis`_ library some additional Predis-specific options are available.
@@ -348,10 +381,12 @@ Supports key rotation, ensuring secure decryption with both old and new keys::
.. _`Data Source Name (DSN)`: https://en.wikipedia.org/wiki/Data_source_name
.. _`Redis server`: https://redis.io/
+.. _`Valkey`: https://valkey.io/
.. _`Redis`: https://github.com/phpredis/phpredis
.. _`RedisArray`: https://github.com/phpredis/phpredis/blob/develop/arrays.md
.. _`RedisCluster`: https://github.com/phpredis/phpredis/blob/develop/cluster.md
.. _`Relay`: https://relay.so/
+.. _`RelayCluster`: https://relay.so/docs/1.x/connections#cluster
.. _`Predis`: https://packagist.org/packages/predis/predis
.. _`Predis Connection Parameters`: https://github.com/nrk/predis/wiki/Connection-Parameters#list-of-connection-parameters
.. _`TCP-keepalive`: https://redis.io/topics/clients#tcp-keepalive
diff --git a/components/config/caching.rst b/components/config/caching.rst
index 18620c0d8cf..80e23a4fdfb 100644
--- a/components/config/caching.rst
+++ b/components/config/caching.rst
@@ -3,7 +3,7 @@ Caching based on Resources
When all configuration resources are loaded, you may want to process the
configuration values and combine them all in one file. This file acts
-like a cache. Its contents don’t have to be regenerated every time the
+like a cache. Its contents don't have to be regenerated every time the
application runs – only when the configuration resources are modified.
For example, the Symfony Routing component allows you to load all routes,
diff --git a/components/config/definition.rst b/components/config/definition.rst
index 44189d9dd6f..4848af33ffe 100644
--- a/components/config/definition.rst
+++ b/components/config/definition.rst
@@ -546,6 +546,30 @@ and in XML:
+You can also provide a URL to a full documentation page::
+
+ $rootNode
+ ->docUrl('Full documentation is available at https://example.com/docs/{version:major}.{version:minor}/reference.html')
+ ->children()
+ ->integerNode('entries_per_page')
+ ->defaultValue(25)
+ ->end()
+ ->end()
+ ;
+
+A few placeholders are available to customize the URL:
+
+* ``{version:major}``: The major version of the package currently installed
+* ``{version:minor}``: The minor version of the package currently installed
+* ``{package}``: The name of the package
+
+The placeholders will be replaced when printing the configuration tree with the
+``config:dump-reference`` command.
+
+.. versionadded:: 7.3
+
+ The ``docUrl()`` method was introduced in Symfony 7.3.
+
Optional Sections
-----------------
diff --git a/components/console/helpers/map.rst.inc b/components/console/helpers/map.rst.inc
index b190d1dce44..73d5d4da7a0 100644
--- a/components/console/helpers/map.rst.inc
+++ b/components/console/helpers/map.rst.inc
@@ -1,6 +1,7 @@
* :doc:`/components/console/helpers/formatterhelper`
* :doc:`/components/console/helpers/processhelper`
* :doc:`/components/console/helpers/progressbar`
+* :doc:`/components/console/helpers/progressindicator`
* :doc:`/components/console/helpers/questionhelper`
* :doc:`/components/console/helpers/table`
* :doc:`/components/console/helpers/tree`
diff --git a/components/console/helpers/table.rst b/components/console/helpers/table.rst
index 9d6fdb0ee61..fe267599caf 100644
--- a/components/console/helpers/table.rst
+++ b/components/console/helpers/table.rst
@@ -1,23 +1,11 @@
-Table
-=====
+Table Helper
+============
-When building a console application it may be useful to display tabular data:
-
-.. code-block:: terminal
-
- +---------------+--------------------------+------------------+
- | ISBN | Title | Author |
- +---------------+--------------------------+------------------+
- | 99921-58-10-7 | Divine Comedy | Dante Alighieri |
- | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens |
- | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien |
- | 80-902734-1-6 | And Then There Were None | Agatha Christie |
- +---------------+--------------------------+------------------+
-
-.. note::
-
- As an alternative, consider using the
- :ref:`SymfonyStyle ` to display a table.
+When building console applications, Symfony provides several utilities for
+rendering tabular data. The simplest option is to use the table methods from
+:ref:`Symfony Style `. While convenient, this approach
+doesn't allow customization of the table's design. For more control and advanced
+features, use the ``Table`` console helper explained in this article.
To display a table, use :class:`Symfony\\Component\\Console\\Helper\\Table`,
set the headers, set the rows and then render the table::
@@ -48,6 +36,22 @@ set the headers, set the rows and then render the table::
}
}
+This outputs:
+
+.. code-block:: terminal
+
+ +---------------+--------------------------+------------------+
+ | ISBN | Title | Author |
+ +---------------+--------------------------+------------------+
+ | 99921-58-10-7 | Divine Comedy | Dante Alighieri |
+ | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens |
+ | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien |
+ | 80-902734-1-6 | And Then There Were None | Agatha Christie |
+ +---------------+--------------------------+------------------+
+
+Adding Table Separators
+-----------------------
+
You can add a table separator anywhere in the output by passing an instance of
:class:`Symfony\\Component\\Console\\Helper\\TableSeparator` as a row::
@@ -61,6 +65,8 @@ You can add a table separator anywhere in the output by passing an instance of
['80-902734-1-6', 'And Then There Were None', 'Agatha Christie'],
]);
+This outputs:
+
.. code-block:: terminal
+---------------+--------------------------+------------------+
@@ -73,6 +79,9 @@ You can add a table separator anywhere in the output by passing an instance of
| 80-902734-1-6 | And Then There Were None | Agatha Christie |
+---------------+--------------------------+------------------+
+Adding Table Titles
+-------------------
+
You can optionally display titles at the top and the bottom of the table::
// ...
@@ -80,6 +89,8 @@ You can optionally display titles at the top and the bottom of the table::
$table->setFooterTitle('Page 1/2');
$table->render();
+This outputs:
+
.. code-block:: terminal
+---------------+----------- Books --------+------------------+
@@ -92,6 +103,9 @@ You can optionally display titles at the top and the bottom of the table::
| 80-902734-1-6 | And Then There Were None | Agatha Christie |
+---------------+--------- Page 1/2 -------+------------------+
+Setting the Column Widths Explicitly
+------------------------------------
+
By default, the width of the columns is calculated automatically based on their
contents. Use the :method:`Symfony\\Component\\Console\\Helper\\Table::setColumnWidths`
method to set the column widths explicitly::
@@ -114,7 +128,7 @@ argument is the column width::
$table->setColumnWidth(2, 30);
$table->render();
-The output of this command will be:
+This outputs:
.. code-block:: terminal
@@ -141,7 +155,7 @@ If you prefer to wrap long contents in multiple rows, use the
$table->setColumnMaxWidth(1, 10);
$table->render();
-The output of this command will be:
+This outputs:
.. code-block:: terminal
@@ -154,6 +168,9 @@ The output of this command will be:
| (the rest of the rows...) |
+-------+------------+--------------------------------+
+Rendering Vertical Tables
+-------------------------
+
By default, table contents are displayed horizontally. You can change this behavior
via the :method:`Symfony\\Component\\Console\\Helper\\Table::setVertical` method::
@@ -161,7 +178,7 @@ via the :method:`Symfony\\Component\\Console\\Helper\\Table::setVertical` method
$table->setVertical();
$table->render();
-The output of this command will be:
+This outputs:
.. code-block:: terminal
@@ -175,37 +192,24 @@ The output of this command will be:
| Author: Charles Dickens |
+------------------------------+
+Customizing the Table Style
+---------------------------
+
The table style can be changed to any built-in styles via
:method:`Symfony\\Component\\Console\\Helper\\Table::setStyle`::
- // same as calling nothing
+ // this 'default' style is the one used when no style is specified
$table->setStyle('default');
- // changes the default style to markdown
- $table->setStyle('markdown');
- $table->render();
+Built-in Table Styles
+~~~~~~~~~~~~~~~~~~~~~
-This outputs the table in the Markdown format:
-
-.. code-block:: terminal
-
- | ISBN | Title | Author |
- |---------------|--------------------------|------------------|
- | 99921-58-10-7 | Divine Comedy | Dante Alighieri |
- | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens |
- | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien |
- | 80-902734-1-6 | And Then There Were None | Agatha Christie |
-
-.. versionadded:: 7.3
-
- The ``markdown`` style was introduced in Symfony 7.3.
-
-You can also set the style to ``compact``::
+**Compact**::
$table->setStyle('compact');
$table->render();
-The output of this command will be:
+This outputs:
.. code-block:: terminal
@@ -215,12 +219,12 @@ The output of this command will be:
960-425-059-0 The Lord of the Rings J. R. R. Tolkien
80-902734-1-6 And Then There Were None Agatha Christie
-You can also set the style to ``borderless``::
+**Borderless**::
$table->setStyle('borderless');
$table->render();
-which outputs:
+This outputs:
.. code-block:: terminal
@@ -233,12 +237,12 @@ which outputs:
80-902734-1-6 And Then There Were None Agatha Christie
=============== ========================== ==================
-You can also set the style to ``box``::
+**Box**::
$table->setStyle('box');
$table->render();
-which outputs:
+This outputs:
.. code-block:: terminal
@@ -251,12 +255,12 @@ which outputs:
│ 80-902734-1-6 │ And Then There Were None │ Agatha Christie │
└───────────────┴──────────────────────────┴──────────────────┘
-You can also set the style to ``box-double``::
+**Double box**::
$table->setStyle('box-double');
$table->render();
-which outputs:
+This outputs:
.. code-block:: terminal
@@ -269,7 +273,30 @@ which outputs:
║ 80-902734-1-6 │ And Then There Were None │ Agatha Christie ║
╚═══════════════╧══════════════════════════╧══════════════════╝
-If the built-in styles do not fit your need, define your own::
+**Markdown**::
+
+ $table->setStyle('markdown');
+ $table->render();
+
+This outputs:
+
+.. code-block:: terminal
+
+ | ISBN | Title | Author |
+ |---------------|--------------------------|------------------|
+ | 99921-58-10-7 | Divine Comedy | Dante Alighieri |
+ | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens |
+ | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien |
+ | 80-902734-1-6 | And Then There Were None | Agatha Christie |
+
+.. versionadded:: 7.3
+
+ The ``markdown`` style was introduced in Symfony 7.3.
+
+Making a Custom Table Style
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If the built-in styles do not fit your needs, define your own::
use Symfony\Component\Console\Helper\TableStyle;
@@ -359,7 +386,7 @@ To make a table cell that spans multiple columns you can use a :class:`Symfony\\
;
$table->render();
-This results in:
+This outputs:
.. code-block:: terminal
@@ -382,7 +409,7 @@ This results in:
]);
// ...
- This generates:
+ This outputs:
.. code-block:: terminal
@@ -461,7 +488,7 @@ The only requirement to append rows is that the table must be rendered inside a
}
}
-This will display the following table in the terminal:
+This outputs:
.. code-block:: terminal
@@ -482,7 +509,7 @@ This will display the following table in the terminal:
$table->render();
// ...
- This will display:
+ This outputs:
.. code-block:: terminal
diff --git a/components/console/helpers/tree.rst b/components/console/helpers/tree.rst
index 1161d00e942..b5839b74a26 100644
--- a/components/console/helpers/tree.rst
+++ b/components/console/helpers/tree.rst
@@ -96,25 +96,51 @@ The above code will output the following tree:
└── templates
└── base.html.twig
-Building and Rendering a Tree
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Building a Tree Programmatically
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-You can build a tree by creating a new instance of the
-:class:`Symfony\\Component\\Console\\Helper\\Tree` class and adding nodes to it::
+If you don't know the tree elements beforehand, you can build the tree programmatically
+by creating a new instance of the :class:`Symfony\\Component\\Console\\Helper\\Tree`
+class and adding nodes to it::
use Symfony\Component\Console\Helper\TreeHelper;
use Symfony\Component\Console\Helper\TreeNode;
- $node = TreeNode::fromValues([
- 'Command',
- 'Controller' => [
- 'DefaultController.php',
- ],
- 'Kernel.php',
- ]);
- $node->addChild('templates');
- $node->addChild('tests');
+ $root = new TreeNode('my-project/');
+ // you can pass a string directly or create a TreeNode object
+ $root->addChild('src/');
+ $root->addChild(new TreeNode('templates/'));
+
+ // create nested structures by adding child nodes to other nodes
+ $testsNode = new TreeNode('tests/');
+ $functionalTestsNode = new TreeNode('Functional/');
+ $testsNode->addChild($functionalTestsNode);
+ $root->addChild(testsNode);
+
+ $tree = TreeHelper::createTree($io, $root);
+ $tree->render();
+
+This example outputs:
+
+.. code-block:: terminal
+ my-project/
+ ├── src/
+ ├── templates/
+ └── tests/
+ └── Functional/
+
+If you prefer, you can build the array of elements programmatically and then
+create and render the tree like this::
+
+ $tree = TreeHelper::createTree($io, null, $array);
+ $tree->render();
+
+You can also build part of the tree from an array and then add other nodes::
+
+ $node = TreeNode::fromValues($array);
+ $node->addChild('templates');
+ // ...
$tree = TreeHelper::createTree($io, $node);
$tree->render();
@@ -125,15 +151,13 @@ Built-in Tree Styles
~~~~~~~~~~~~~~~~~~~~
The tree helper provides a few built-in styles that you can use to customize the
-output of the tree::
+output of the tree.
- use Symfony\Component\Console\Helper\TreeStyle;
- // ...
+**Default**::
- $tree = TreeHelper::createTree($io, $node, [], TreeStyle::compact());
- $tree->render();
+ TreeHelper::createTree($io, $node, [], TreeStyle::default());
-``TreeHelper::createTree($io, $node, [], TreeStyle::default())`` (`details`_)
+This outputs:
.. code-block:: terminal
@@ -150,7 +174,11 @@ output of the tree::
└── templates
└── base.html.twig
-``TreeHelper::createTree($io, $node, [], TreeStyle::box())`` (`details`_)
+**Box**::
+
+ TreeHelper::createTree($io, $node, [], TreeStyle::box());
+
+This outputs:
.. code-block:: terminal
@@ -167,7 +195,11 @@ output of the tree::
┗╸ templates
┗╸ base.html.twig
-``TreeHelper::createTree($io, $node, [], TreeStyle::doubleBox())`` (`details`_)
+**Double box**::
+
+ TreeHelper::createTree($io, $node, [], TreeStyle::doubleBox());
+
+This outputs:
.. code-block:: terminal
@@ -184,7 +216,11 @@ output of the tree::
╚═ templates
╚═ base.html.twig
-``TreeHelper::createTree($io, $node, [], TreeStyle::compact())`` (`details`_)
+**Compact**::
+
+ TreeHelper::createTree($io, $node, [], TreeStyle::compact());
+
+This outputs:
.. code-block:: terminal
@@ -201,7 +237,11 @@ output of the tree::
└ templates
└ base.html.twig
-``TreeHelper::createTree($io, $node, [], TreeStyle::light())`` (`details`_)
+**Light**::
+
+ TreeHelper::createTree($io, $node, [], TreeStyle::light());
+
+This outputs:
.. code-block:: terminal
@@ -218,7 +258,11 @@ output of the tree::
`-- templates
`-- base.html.twig
-``TreeHelper::createTree($io, $node, [], TreeStyle::minimal())`` (`details`_)
+**Minimal**::
+
+ TreeHelper::createTree($io, $node, [], TreeStyle::minimal());
+
+This outputs:
.. code-block:: terminal
@@ -235,7 +279,11 @@ output of the tree::
. templates
. base.html.twig
-``TreeHelper::createTree($io, $node, [], TreeStyle::rounded())`` (`details`_)
+**Rounded**::
+
+ TreeHelper::createTree($io, $node, [], TreeStyle::rounded());
+
+This outputs:
.. code-block:: terminal
@@ -291,5 +339,3 @@ The above code will output the following tree:
🔵 🟢 🟠 🟡 Kernel.php
🔵 🟠 🟡 templates
🔵 🔴 🟠 🟡 base.html.twig
-
-.. _`details`: https://github.com/symfony/symfony/blob/7.3/src/Symfony/Component/Console/Helper/TreeStyle.php
diff --git a/components/dependency_injection.rst b/components/dependency_injection.rst
index 93e8af711cf..d146f553a0c 100644
--- a/components/dependency_injection.rst
+++ b/components/dependency_injection.rst
@@ -180,16 +180,16 @@ You can override this behavior as follows::
These are all the possible behaviors:
- * ``ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE``: throws an exception
- at compile time (this is the **default** behavior);
- * ``ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE``: throws an
- exception at runtime, when trying to access the missing service;
- * ``ContainerInterface::NULL_ON_INVALID_REFERENCE``: returns ``null``;
- * ``ContainerInterface::IGNORE_ON_INVALID_REFERENCE``: ignores the wrapping
- command asking for the reference (for instance, ignore a setter if the service
- does not exist);
- * ``ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE``: ignores/returns
- ``null`` for uninitialized services or invalid references.
+* ``ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE``: throws an exception
+ at compile time (this is the **default** behavior);
+* ``ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE``: throws an
+ exception at runtime, when trying to access the missing service;
+* ``ContainerInterface::NULL_ON_INVALID_REFERENCE``: returns ``null``;
+* ``ContainerInterface::IGNORE_ON_INVALID_REFERENCE``: ignores the wrapping
+ command asking for the reference (for instance, ignore a setter if the service
+ does not exist);
+* ``ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE``: ignores/returns
+ ``null`` for uninitialized services or invalid references.
Avoiding your Code Becoming Dependent on the Container
------------------------------------------------------
diff --git a/components/event_dispatcher.rst b/components/event_dispatcher.rst
index 8cd676dd5fe..62a3707bb39 100644
--- a/components/event_dispatcher.rst
+++ b/components/event_dispatcher.rst
@@ -277,8 +277,9 @@ Dispatch the Event
The :method:`Symfony\\Component\\EventDispatcher\\EventDispatcher::dispatch`
method notifies all listeners of the given event. It takes two arguments:
-the ``Event`` instance to pass to each listener of that event and the name
-of the event to dispatch::
+the ``Event`` instance to pass to each listener of that event and optionally the
+name of the event to dispatch. If it's not defined, the class of the ``Event``
+instance will be used::
use Acme\Store\Event\OrderPlacedEvent;
use Acme\Store\Order;
diff --git a/components/http_foundation.rst b/components/http_foundation.rst
index 8db6cd27f68..1cb87aafb24 100644
--- a/components/http_foundation.rst
+++ b/components/http_foundation.rst
@@ -852,9 +852,10 @@ Alternatively, if you are serving a static file, you can use a
The ``BinaryFileResponse`` will automatically handle ``Range`` and
``If-Range`` headers from the request. It also supports ``X-Sendfile``
-(see for `nginx`_ and `Apache`_). To make use of it, you need to determine
-whether or not the ``X-Sendfile-Type`` header should be trusted and call
-:method:`Symfony\\Component\\HttpFoundation\\BinaryFileResponse::trustXSendfileTypeHeader`
+(see `FrankenPHP X-Sendfile and X-Accel-Redirect headers`_,
+`nginx X-Accel-Redirect header`_ and `Apache mod_xsendfile module`_). To make use
+of it, you need to determine whether or not the ``X-Sendfile-Type`` header should
+be trusted and call :method:`Symfony\\Component\\HttpFoundation\\BinaryFileResponse::trustXSendfileTypeHeader`
if it should::
BinaryFileResponse::trustXSendfileTypeHeader();
@@ -1076,8 +1077,9 @@ Learn More
/session
/http_cache/*
-.. _nginx: https://mattbrictson.com/blog/accelerated-rails-downloads
-.. _Apache: https://tn123.org/mod_xsendfile/
+.. _`FrankenPHP X-Sendfile and X-Accel-Redirect headers`: https://frankenphp.dev/docs/x-sendfile/
+.. _`nginx X-Accel-Redirect header`: https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_ignore_headers
+.. _`Apache mod_xsendfile module`: https://github.com/nmaier/mod_xsendfile
.. _`JSON Hijacking`: https://haacked.com/archive/2009/06/25/json-hijacking.aspx/
.. _`valid JSON top-level value`: https://www.json.org/json-en.html
.. _OWASP guidelines: https://cheatsheetseries.owasp.org/cheatsheets/AJAX_Security_Cheat_Sheet.html#always-return-json-with-an-object-on-the-outside
diff --git a/components/http_kernel.rst b/components/http_kernel.rst
index 02791b370bc..62d1e92d89b 100644
--- a/components/http_kernel.rst
+++ b/components/http_kernel.rst
@@ -474,8 +474,8 @@ as possible to the client (e.g. sending emails).
.. 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
- API is able to send a response to the client while the server's PHP process
+ PHP function. This means that at the moment, only the `PHP FPM`_ API and the
+ `FrankenPHP`_ server are able to send a response to the client while the server's PHP process
still performs some tasks. With all other server APIs, listeners to ``kernel.terminate``
are still executed, but the response is not sent to the client until they
are all completed.
@@ -758,3 +758,4 @@ Learn more
.. _FOSRestBundle: https://github.com/friendsofsymfony/FOSRestBundle
.. _`PHP FPM`: https://www.php.net/manual/en/install.fpm.php
.. _variadic: https://www.php.net/manual/en/functions.arguments.php#functions.variable-arg-list
+.. _`FrankenPHP`: https://frankenphp.dev
diff --git a/components/lock.rst b/components/lock.rst
index b8ba38c8fc7..2403763bd4a 100644
--- a/components/lock.rst
+++ b/components/lock.rst
@@ -612,9 +612,9 @@ RedisStore
~~~~~~~~~~
The RedisStore saves locks on a Redis server, it requires a Redis connection
-implementing the ``\Redis``, ``\RedisArray``, ``\RedisCluster``, ``\Relay\Relay`` or
-``\Predis`` classes. This store does not support blocking, and expects a TTL to
-avoid stalled locks::
+implementing the ``\Redis``, ``\RedisArray``, ``\RedisCluster``, ``\Relay\Relay``,
+``\Relay\Cluster`` or ``\Predis`` classes. This store does not support blocking,
+and expects a TTL to avoid stalled locks::
use Symfony\Component\Lock\Store\RedisStore;
@@ -623,6 +623,10 @@ avoid stalled locks::
$store = new RedisStore($redis);
+.. versionadded:: 7.3
+
+ Support for ``Relay\Cluster`` was introduced in Symfony 7.3.
+
.. _lock-store-semaphore:
SemaphoreStore
diff --git a/components/options_resolver.rst b/components/options_resolver.rst
index 6f3a6751f28..17ec46c2fc9 100644
--- a/components/options_resolver.rst
+++ b/components/options_resolver.rst
@@ -936,7 +936,7 @@ can change your code to do the configuration only once per class::
public function __construct(array $options = [])
{
// What type of Mailer is this, a Mailer, a GoogleMailer, ... ?
- $class = get_class($this);
+ $class = $this::class;
// Was configureOptions() executed before for this class?
if (!isset(self::$resolversByClass[$class])) {
diff --git a/components/property_info.rst b/components/property_info.rst
index 39019657ced..865a36c5941 100644
--- a/components/property_info.rst
+++ b/components/property_info.rst
@@ -131,7 +131,7 @@ class exposes public methods to extract several types of information:
$propertyInfo->getProperties($awesomeObject);
// Good!
- $propertyInfo->getProperties(get_class($awesomeObject));
+ $propertyInfo->getProperties($awesomeObject::class);
$propertyInfo->getProperties('Example\Namespace\YourAwesomeClass');
$propertyInfo->getProperties(YourAwesomeClass::class);
diff --git a/components/runtime.rst b/components/runtime.rst
index 4eb75de2a75..770ea102563 100644
--- a/components/runtime.rst
+++ b/components/runtime.rst
@@ -36,6 +36,9 @@ So how does this front-controller work? At first, the special
the component. This file runs the following logic:
#. It instantiates a :class:`Symfony\\Component\\Runtime\\RuntimeInterface`;
+#. The front-controller script (e.g. ``public/index.php``) is included by the
+ runtime, making it run again. Ensure this doesn't produce any side effects
+ in your code;
#. The callable (returned by ``public/index.php``) is passed to the Runtime, whose job
is to resolve the arguments (in this example: ``array $context``);
#. Then, this callable is called to get the application (``App\Kernel``);
diff --git a/components/uid.rst b/components/uid.rst
index 6c92fff0af9..b4083765436 100644
--- a/components/uid.rst
+++ b/components/uid.rst
@@ -32,13 +32,13 @@ to create each type of UUID:
**UUID v1** (time-based)
Generates the UUID using a timestamp and the MAC address of your device
-(`read UUIDv1 spec `__).
+(`read the UUIDv1 spec `__).
Both are obtained automatically, so you don't have to pass any constructor argument::
use Symfony\Component\Uid\Uuid;
- // $uuid is an instance of Symfony\Component\Uid\UuidV1
$uuid = Uuid::v1();
+ // $uuid is an instance of Symfony\Component\Uid\UuidV1
.. tip::
@@ -48,7 +48,7 @@ Both are obtained automatically, so you don't have to pass any constructor argum
**UUID v2** (DCE security)
Similar to UUIDv1 but with a very high likelihood of ID collision
-(`read UUIDv2 spec `__).
+(`read the UUIDv2 spec `__).
It's part of the authentication mechanism of DCE (Distributed Computing Environment)
and the UUID includes the POSIX UIDs (user/group ID) of the user who generated it.
This UUID variant is **not implemented** by the Uid component.
@@ -56,7 +56,7 @@ This UUID variant is **not implemented** by the Uid component.
**UUID v3** (name-based, MD5)
Generates UUIDs from names that belong, and are unique within, some given namespace
-(`read UUIDv3 spec `__).
+(`read the UUIDv3 spec `__).
This variant is useful to generate deterministic UUIDs from arbitrary strings.
It works by populating the UUID contents with the``md5`` hash of concatenating
the namespace and the name::
@@ -69,8 +69,8 @@ the namespace and the name::
// $namespace = Uuid::v4();
// $name can be any arbitrary string
- // $uuid is an instance of Symfony\Component\Uid\UuidV3
$uuid = Uuid::v3($namespace, $name);
+ // $uuid is an instance of Symfony\Component\Uid\UuidV3
These are the default namespaces defined by the standard:
@@ -81,20 +81,20 @@ These are the default namespaces defined by the standard:
**UUID v4** (random)
-Generates a random UUID (`read UUIDv4 spec `__).
+Generates a random UUID (`read the UUIDv4 spec `__).
Because of its randomness, it ensures uniqueness across distributed systems
without the need for a central coordinating entity. It's privacy-friendly
because it doesn't contain any information about where and when it was generated::
use Symfony\Component\Uid\Uuid;
- // $uuid is an instance of Symfony\Component\Uid\UuidV4
$uuid = Uuid::v4();
+ // $uuid is an instance of Symfony\Component\Uid\UuidV4
**UUID v5** (name-based, SHA-1)
It's the same as UUIDv3 (explained above) but it uses ``sha1`` instead of
-``md5`` to hash the given namespace and name (`read UUIDv5 spec `__).
+``md5`` to hash the given namespace and name (`read the UUIDv5 spec `__).
This makes it more secure and less prone to hash collisions.
.. _uid-uuid-v6:
@@ -103,12 +103,12 @@ This makes it more secure and less prone to hash collisions.
It rearranges the time-based fields of the UUIDv1 to make it lexicographically
sortable (like :ref:`ULIDs `). It's more efficient for database indexing
-(`read UUIDv6 spec `__)::
+(`read the UUIDv6 spec `__)::
use Symfony\Component\Uid\Uuid;
- // $uuid is an instance of Symfony\Component\Uid\UuidV6
$uuid = Uuid::v6();
+ // $uuid is an instance of Symfony\Component\Uid\UuidV6
.. tip::
@@ -121,26 +121,28 @@ sortable (like :ref:`ULIDs `). It's more efficient for database indexing
Generates time-ordered UUIDs based on a high-resolution Unix Epoch timestamp
source (the number of milliseconds since midnight 1 Jan 1970 UTC, leap seconds excluded)
-(`read UUIDv7 spec `__).
+(`read the UUIDv7 spec `__).
It's recommended to use this version over UUIDv1 and UUIDv6 because it provides
better entropy (and a more strict chronological order of UUID generation)::
use Symfony\Component\Uid\Uuid;
- // $uuid is an instance of Symfony\Component\Uid\UuidV7
$uuid = Uuid::v7();
+ // $uuid is an instance of Symfony\Component\Uid\UuidV7
**UUID v8** (custom)
-Provides an RFC-compatible format for experimental or vendor-specific use cases
-(`read UUIDv8 spec `__).
-The only requirement is to set the variant and version bits of the UUID. The rest
-of the UUID value is specific to each implementation and no format should be assumed::
+Provides an RFC-compatible format intended for experimental or vendor-specific use cases
+(`read the UUIDv8 spec `__).
+You must generate the UUID value yourself. The only requirement is to set the
+variant and version bits of the UUID correctly. The rest of the UUID content is
+implementation-specific, and no particular format should be assumed::
use Symfony\Component\Uid\Uuid;
+ // pass your custom UUID value as the argument
+ $uuid = Uuid::v8('d9e7a184-5d5b-11ea-a62a-3499710062d0');
// $uuid is an instance of Symfony\Component\Uid\UuidV8
- $uuid = Uuid::v8();
If your UUID value is already generated in another format, use any of the
following methods to create a ``Uuid`` object from it::
@@ -367,6 +369,7 @@ entity primary keys::
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
+ use Symfony\Bridge\Doctrine\IdGenerator\UuidGenerator;
use Symfony\Bridge\Doctrine\Types\UuidType;
use Symfony\Component\Uid\Uuid;
@@ -375,7 +378,7 @@ entity primary keys::
#[ORM\Id]
#[ORM\Column(type: UuidType::NAME, unique: true)]
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
- #[ORM\CustomIdGenerator(class: 'doctrine.uuid_generator')]
+ #[ORM\CustomIdGenerator(class: UuidGenerator::class)]
private ?Uuid $id;
public function getId(): ?Uuid
@@ -555,6 +558,7 @@ entity primary keys::
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
+ use Symfony\Bridge\Doctrine\IdGenerator\UlidGenerator;
use Symfony\Bridge\Doctrine\Types\UlidType;
use Symfony\Component\Uid\Ulid;
@@ -563,7 +567,7 @@ entity primary keys::
#[ORM\Id]
#[ORM\Column(type: UlidType::NAME, unique: true)]
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
- #[ORM\CustomIdGenerator(class: 'doctrine.ulid_generator')]
+ #[ORM\CustomIdGenerator(class: UlidGenerator::class)]
private ?Ulid $id;
public function getId(): ?Ulid
diff --git a/components/var_exporter.rst b/components/var_exporter.rst
index fc6b34868db..c7ec9cd90d0 100644
--- a/components/var_exporter.rst
+++ b/components/var_exporter.rst
@@ -180,18 +180,50 @@ populated by using the special ``"\0"`` property name to define their internal v
Creating Lazy Objects
---------------------
-Lazy-objects are objects instantiated empty and populated on-demand. This is
-particularly useful when you have for example properties in your classes that
-requires some heavy computation to determine their value. In this case, you
-may want to trigger the property's value processing only when you actually need
-its value. Thanks to this, the heavy computation won't be done if you never use
-this property. The VarExporter component is bundled with two traits helping
-you implement such mechanism easily in your classes.
+Lazy objects are objects instantiated empty and populated on demand. This is
+particularly useful when, for example, a class has properties that require
+heavy computation to determine their values. In such cases, you may want to
+trigger the computation only when the property is actually accessed. This way,
+the expensive processing is avoided entirely if the property is never used.
+
+Since version 8.4, PHP provides support for lazy objects via the reflection API.
+This native API works with concrete classes, but not with abstract or internal ones.
+This component provides helpers to generate lazy objects using the decorator
+pattern, which also works with abstract classes, internal classes, and interfaces::
+
+ $proxyCode = ProxyHelper::generateLazyProxy(new \ReflectionClass(SomeInterface::class));
+ // $proxyCode should be dumped into a file in production environments
+ eval('class ProxyDecorator'.$proxyCode);
+
+ $proxy = ProxyDecorator::createLazyProxy(initializer: function (): SomeInterface {
+ // use whatever heavy logic you need here
+ // to compute the $dependencies of the proxied class
+ $instance = new SomeHeavyClass(...$dependencies);
+ // call setters, etc. if needed
+
+ return $instance;
+ });
+
+Use this mechanism only when native lazy objects cannot be leveraged
+(otherwise you'll get a deprecation notice).
+
+Legacy Creation of Lazy Objects
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When using a PHP version earlier than 8.4, native lazy objects are not available.
+In these cases, the VarExporter component provides two traits that help you
+implement lazy-loading mechanisms in your classes.
.. _var-exporter_ghost-objects:
LazyGhostTrait
-~~~~~~~~~~~~~~
+..............
+
+.. deprecated:: 7.3
+
+ ``LazyGhostTrait`` is deprecated since Symfony 7.3. Use PHP 8.4's native lazy
+ objects instead. Note that using the trait with PHP versions earlier than 8.4
+ does not trigger a deprecation, to ease the transition.
Ghost objects are empty objects, which see their properties populated the first
time any method is called. Thanks to :class:`Symfony\\Component\\VarExporter\\LazyGhostTrait`,
@@ -271,7 +303,13 @@ of :ref:`Virtual Proxies `.
.. _var-exporter_virtual-proxies:
LazyProxyTrait
-~~~~~~~~~~~~~~
+..............
+
+.. deprecated:: 7.3
+
+ ``LazyProxyTrait`` is deprecated since Symfony 7.3. Use PHP 8.4's native lazy
+ objects instead. Note that using the trait with PHP versions earlier than 8.4
+ does not trigger a deprecation, to ease the transition.
The purpose of virtual proxies in the same one as
:ref:`ghost objects `, but their internal behavior is
diff --git a/components/yaml.rst b/components/yaml.rst
index 471b59dcf5c..efaf84f04e6 100644
--- a/components/yaml.rst
+++ b/components/yaml.rst
@@ -298,7 +298,7 @@ You can make it convert to a ``DateTime`` instance by using the ``PARSE_DATETIME
flag::
$date = Yaml::parse('2016-05-27', Yaml::PARSE_DATETIME);
- var_dump(get_class($date)); // DateTime
+ var_dump($date::class); // DateTime
Dumping Multi-line Literal Blocks
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/configuration/micro_kernel_trait.rst b/configuration/micro_kernel_trait.rst
index f919b1f7a74..c372d876651 100644
--- a/configuration/micro_kernel_trait.rst
+++ b/configuration/micro_kernel_trait.rst
@@ -297,8 +297,8 @@ Now it looks like this::
{
// import the WebProfilerRoutes, only if the bundle is enabled
if (isset($this->bundles['WebProfilerBundle'])) {
- $routes->import('@WebProfilerBundle/Resources/config/routing/wdt.xml')->prefix('/_wdt');
- $routes->import('@WebProfilerBundle/Resources/config/routing/profiler.xml')->prefix('/_profiler');
+ $routes->import('@WebProfilerBundle/Resources/config/routing/wdt.php', 'php')->prefix('/_wdt');
+ $routes->import('@WebProfilerBundle/Resources/config/routing/profiler.php', 'php')->prefix('/_profiler');
}
// load the routes defined as PHP attributes
@@ -310,6 +310,12 @@ Now it looks like this::
// to override the default locations for these directories
}
+
+.. versionadded:: 7.3
+
+ The ``wdt.php`` and ``profiler.php`` files were introduced in Symfony 7.3.
+ Previously, you had to import ``wdt.xml`` and ``profiler.xml``
+
Before continuing, run this command to add support for the new dependencies:
.. code-block:: terminal
diff --git a/configuration/secrets.rst b/configuration/secrets.rst
index f717456a22c..285b89d521e 100644
--- a/configuration/secrets.rst
+++ b/configuration/secrets.rst
@@ -311,7 +311,7 @@ The secrets system is enabled by default and some of its behavior can be configu
xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd
http://symfony.com/schema/dic/framework https://symfony.com/schema/dic/framework/framework-1.0.xsd"
>
-
+
isSilent(), ->isQuiet(), ->isVerbose(), ->isVeryVerbose(), ->isDebug()
if ($output->isVerbose()) {
- $output->writeln('User class: '.get_class($user));
+ $output->writeln('User class: '.$user::class);
}
// alternatively you can pass the verbosity level PHP constant to writeln()
diff --git a/contributing/code/bc.rst b/contributing/code/bc.rst
index 497c70fb01d..ad394d2720c 100644
--- a/contributing/code/bc.rst
+++ b/contributing/code/bc.rst
@@ -176,6 +176,13 @@ covered by our backward compatibility promise:
| Use a public, protected or private method | Yes |
+-----------------------------------------------+-----------------------------+
+Using our Translations
+~~~~~~~~~~~~~~~~~~~~~~
+
+All translations provided by Symfony for security and validation errors are
+intended for internal use only. They may be changed or removed at any time.
+Symfony's Backward Compatibility Promise does not apply to internal translations.
+
Working on Symfony Code
-----------------------
diff --git a/contributing/code/standards.rst b/contributing/code/standards.rst
index 3a00343faf1..ebfde7dfab4 100644
--- a/contributing/code/standards.rst
+++ b/contributing/code/standards.rst
@@ -211,7 +211,7 @@ Naming Conventions
* Use `camelCase`_ for PHP variables, function and method names, arguments
(e.g. ``$acceptableContentTypes``, ``hasSession()``);
-Use `snake_case`_ for configuration parameters, route names and Twig template
+* Use `snake_case`_ for configuration parameters, route names and Twig template
variables (e.g. ``framework.csrf_protection``, ``http_status_code``);
* Use SCREAMING_SNAKE_CASE for constants (e.g. ``InputArgument::IS_ARRAY``);
diff --git a/contributing/code_of_conduct/code_of_conduct.rst b/contributing/code_of_conduct/code_of_conduct.rst
index 6202fdad424..ce14dd5ad0e 100644
--- a/contributing/code_of_conduct/code_of_conduct.rst
+++ b/contributing/code_of_conduct/code_of_conduct.rst
@@ -34,7 +34,7 @@ Examples of unacceptable behavior include:
any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
-* Publishing others’ private information, such as a physical or email address,
+* Publishing others' private information, such as a physical or email address,
without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
@@ -128,7 +128,7 @@ Attribution
This Code of Conduct is adapted from the `Contributor Covenant`_, version 2.1,
available at https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
-Community Impact Guidelines were inspired by `Mozilla’s code of conduct enforcement ladder`_.
+Community Impact Guidelines were inspired by `Mozilla's code of conduct enforcement ladder`_.
Related Documents
-----------------
@@ -141,4 +141,4 @@ Related Documents
concrete_example_document
.. _Contributor Covenant: https://www.contributor-covenant.org
-.. _Mozilla’s code of conduct enforcement ladder: https://github.com/mozilla/diversity
+.. _Mozilla's code of conduct enforcement ladder: https://github.com/mozilla/diversity
diff --git a/contributing/code_of_conduct/concrete_example_document.rst b/contributing/code_of_conduct/concrete_example_document.rst
index 60ffe2527db..227a41df4a8 100644
--- a/contributing/code_of_conduct/concrete_example_document.rst
+++ b/contributing/code_of_conduct/concrete_example_document.rst
@@ -9,7 +9,7 @@ according to the Symfony code of conduct.
Concrete Examples
-----------------
-* Unwelcome comments regarding a person’s lifestyle choices and practices,
+* Unwelcome comments regarding a person's lifestyle choices and practices,
including those related to food, health, parenting, drugs, and employment;
* Deliberate misgendering or use of `dead names`_ (The birth name
of a person who has since changed their name, often a transgender person);
diff --git a/contributing/community/review-comments.rst b/contributing/community/review-comments.rst
index 5b9bc932205..331352bb5fd 100644
--- a/contributing/community/review-comments.rst
+++ b/contributing/community/review-comments.rst
@@ -28,7 +28,7 @@ constructive, respectful and helpful reviews and replies.
welcoming place for everyone. **You are free to disagree with
someone's opinions, but don't be disrespectful.**
-It’s important to accept that many programming decisions are opinions.
+It's important to accept that many programming decisions are opinions.
Discuss trade-offs, which you prefer, and reach a resolution quickly.
It's not about being right or wrong, but using what works.
diff --git a/contributing/core_team.rst b/contributing/core_team.rst
index 88c9edf45ab..d776cd4ed93 100644
--- a/contributing/core_team.rst
+++ b/contributing/core_team.rst
@@ -69,6 +69,7 @@ In addition, there are other groups created to manage specific topics:
* **Security Team**: manages the whole security process (triaging reported vulnerabilities,
fixing the reported issues, coordinating the release of security fixes, etc.);
* **Symfony UX Team**: manages the `UX repositories`_;
+* **Symfony CLI Team**: manages the `CLI repositories`_;
* **Documentation Team**: manages the whole `symfony-docs repository`_.
Active Core Members
@@ -100,7 +101,6 @@ Active Core Members
* **Berislav Balogović** (`hypemc`_);
* **Mathias Arlaud** (`mtarld`_);
* **Florent Morselli** (`spomky`_);
- * **Tugdual Saunier** (`tucksaun`_);
* **Alexandre Daubois** (`alexandre-daubois`_).
* **Security Team** (``@symfony/security`` on GitHub):
@@ -116,6 +116,11 @@ Active Core Members
* **Hugo Alliaume** (`kocal`_);
* **Matheo Daninos** (`webmamba`_).
+* **Symfony CLI Team** (``@symfony-cli/core`` on GitHub):
+
+ * **Fabien Potencier** (`fabpot`_);
+ * **Tugdual Saunier** (`tucksaun`_).
+
* **Documentation Team** (``@symfony/team-symfony-docs`` on GitHub):
* **Fabien Potencier** (`fabpot`_);
@@ -192,7 +197,7 @@ Pull Request Merging Policy
A pull request **can be merged** if:
-* It is a :ref:`unsubstantial change `;
+* It is an :ref:`unsubstantial change `;
* Enough time was given for peer reviews;
* It is a bug fix and at least two **Mergers Team** members voted ``+1``
(only one if the submitter is part of the Mergers team) and no Core
@@ -249,7 +254,7 @@ generate the CHANGELOG files when releasing new versions.
#. A clone for their own contributions, which they use to push to their
fork on GitHub. Clear out the push URL for the Symfony repository using
``git remote set-url --push origin dev://null`` (change ``origin``
- to the Git remote poiting to the Symfony repository);
+ to the Git remote pointing to the Symfony repository);
#. A clone for merging, which they use in combination with ``gh`` and
allows them to push to the main repository.
@@ -333,6 +338,7 @@ discretion of the **Project Leader**.
.. _`symfony-docs repository`: https://github.com/symfony/symfony-docs
.. _`UX repositories`: https://github.com/symfony/ux
+.. _`CLI repositories`: https://github.com/symfony-cli
.. _`fabpot`: https://github.com/fabpot/
.. _`webmozart`: https://github.com/webmozart/
.. _`Tobion`: https://github.com/Tobion/
diff --git a/contributing/diversity/further_reading.rst b/contributing/diversity/further_reading.rst
index 8bb07c39c97..b5f44047159 100644
--- a/contributing/diversity/further_reading.rst
+++ b/contributing/diversity/further_reading.rst
@@ -9,7 +9,7 @@ Diversity in Open Source
`Sage Sharp - What makes a good community? `_
`Ashe Dryden - The Ethics of Unpaid Labor and the OSS Community `_
`Model View Culture - The Dehumanizing Myth of the Meritocracy `_
-`Annalee - How “Good Intent” Undermines Diversity and Inclusion `_
+`Annalee - How "Good Intent" Undermines Diversity and Inclusion `_
`Karolina Szczur - Building Inclusive Communities `_
Code of Conduct
@@ -22,7 +22,7 @@ Code of Conduct
Inclusive language
------------------
-`Jenée Desmond-Harris - Why I’m finally convinced it's time to stop saying "you guys" `_
+`Jenée Desmond-Harris - Why I'm finally convinced it's time to stop saying "you guys" `_
`inclusive language presentations `_
Other talks and Blog Posts
@@ -30,7 +30,7 @@ Other talks and Blog Posts
`Lena Reinhard – A Talk About Nothing `_
`Lena Reinhard - A Talk about Everything `_
-`Sage Sharp - SCALE: Improving Diversity with Maslow’s hierarchy `_
+`Sage Sharp - SCALE: Improving Diversity with Maslow's hierarchy `_
`UCSF - Unconscious Bias `_
`Responding to harassment reports `_
`Unconscious bias at work `_
diff --git a/controller/error_pages.rst b/controller/error_pages.rst
index 96856764ece..06087837437 100644
--- a/controller/error_pages.rst
+++ b/controller/error_pages.rst
@@ -154,7 +154,8 @@ automatically when installing ``symfony/framework-bundle``):
# config/routes/framework.yaml
when@dev:
_errors:
- resource: '@FrameworkBundle/Resources/config/routing/errors.xml'
+ resource: '@FrameworkBundle/Resources/config/routing/errors.php'
+ type: php
prefix: /_error
.. code-block:: xml
@@ -167,7 +168,7 @@ automatically when installing ``symfony/framework-bundle``):
https://symfony.com/schema/routing/routing-1.0.xsd">
-
+
@@ -178,7 +179,7 @@ automatically when installing ``symfony/framework-bundle``):
return function (RoutingConfigurator $routes): void {
if ('dev' === $routes->env()) {
- $routes->import('@FrameworkBundle/Resources/config/routing/errors.xml')
+ $routes->import('@FrameworkBundle/Resources/config/routing/errors.php', 'php')
->prefix('/_error')
;
}
@@ -191,6 +192,11 @@ need to replace ``http://localhost/`` by the host used in your local setup):
* ``http://localhost/_error/{statusCode}`` for HTML
* ``http://localhost/_error/{statusCode}.{format}`` for any other format
+.. versionadded:: 7.3
+
+ The ``errors.php`` file was introduced in Symfony 7.3.
+ Previously, you had to import ``errors.xml``
+
.. _overriding-non-html-error-output:
Overriding Error output for non-HTML formats
diff --git a/controller/service.rst b/controller/service.rst
index 88af093ff29..cf83e066a19 100644
--- a/controller/service.rst
+++ b/controller/service.rst
@@ -7,11 +7,65 @@ and your controllers extend the `AbstractController`_ class, they *are* automati
registered as services. This means you can use dependency injection like any
other normal service.
-If your controllers don't extend the `AbstractController`_ class, you must
-explicitly mark your controller services as ``public``. Alternatively, you can
-apply the ``controller.service_arguments`` tag to your controller services. This
-will make the tagged services ``public`` and will allow you to inject services
-in method parameters:
+If you prefer to not extend the ``AbstractController`` class, you can register
+your controllers as services in several ways:
+
+#. Using the ``#[Route]`` attribute;
+#. Using the ``#[AsController]`` attribute;
+#. Using the ``controller.service_arguments`` service tag.
+
+Using the ``#[Route]`` Attribute
+--------------------------------
+
+When using :ref:`the #[Route] attribute ` to define
+routes on any PHP class, Symfony treats that class as a controller. It registers
+it as a public, non-lazy service and enables service argument injection in all
+its methods.
+
+This is the simplest and recommended way to register controllers as services
+when not extending the base controller class.
+
+.. versionadded:: 7.3
+
+ The feature to register controllers as services when using the ``#[Route]``
+ attribute was introduced in Symfony 7.3.
+
+Using the ``#[AsController]`` Attribute
+---------------------------------------
+
+If you prefer, you can use the ``#[AsController]`` PHP attribute to automatically
+apply the ``controller.service_arguments`` tag to your controller services::
+
+ // src/Controller/HelloController.php
+ namespace App\Controller;
+
+ use Symfony\Component\HttpFoundation\Response;
+ use Symfony\Component\HttpKernel\Attribute\AsController;
+ use Symfony\Component\Routing\Attribute\Route;
+
+ #[AsController]
+ class HelloController
+ {
+ #[Route('/hello', name: 'hello', methods: ['GET'])]
+ public function index(): Response
+ {
+ // ...
+ }
+ }
+
+.. tip::
+
+ When using the ``#[Route]`` attribute, Symfony already registers the controller
+ class as a service, so using the ``#[AsController]`` attribute is redundant.
+
+Using the ``controller.service_arguments`` Service Tag
+------------------------------------------------------
+
+If your controllers don't extend the `AbstractController`_ class and you don't
+use the ``#[AsController]`` or ``#[Route]`` attributes, you must register the
+controllers as public services manually and apply the ``controller.service_arguments``
+:doc:`service tag ` to enable service injection in
+controller actions:
.. configuration-block::
@@ -58,26 +112,6 @@ in method parameters:
calls:
- [setContainer, ['@abstract_controller.locator']]
-If you prefer, you can use the ``#[AsController]`` PHP attribute to automatically
-apply the ``controller.service_arguments`` tag to your controller services::
-
- // src/Controller/HelloController.php
- namespace App\Controller;
-
- use Symfony\Component\HttpFoundation\Response;
- use Symfony\Component\HttpKernel\Attribute\AsController;
- use Symfony\Component\Routing\Attribute\Route;
-
- #[AsController]
- class HelloController
- {
- #[Route('/hello', name: 'hello', methods: ['GET'])]
- public function index(): Response
- {
- // ...
- }
- }
-
Registering your controller as a service is the first step, but you also need to
update your routing config to reference the service properly, so that Symfony
knows to use it.
diff --git a/controller/upload_file.rst b/controller/upload_file.rst
index cff326a8e2b..793cd26dd65 100644
--- a/controller/upload_file.rst
+++ b/controller/upload_file.rst
@@ -77,11 +77,8 @@ so Symfony doesn't try to get/set its value from the related entity::
'constraints' => [
new File(
maxSize: '1024k',
- mimeTypes: [
- 'application/pdf',
- 'application/x-pdf',
- ],
- mimeTypesMessage: 'Please upload a valid PDF document',
+ extensions: ['pdf'],
+ extensionsMessage: 'Please upload a valid PDF document',
)
],
])
diff --git a/controller/value_resolver.rst b/controller/value_resolver.rst
index 1844ff0c9be..835edcfbff9 100644
--- a/controller/value_resolver.rst
+++ b/controller/value_resolver.rst
@@ -281,7 +281,7 @@ this argument) or an array with the resolved value(s). Usually arguments are
resolved as a single value, but variadic arguments require resolving multiple
values. That's why you must always return an array, even for single values::
- // src/ValueResolver/IdentifierValueResolver.php
+ // src/ValueResolver/BookingIdValueResolver.php
namespace App\ValueResolver;
use App\IdentifierInterface;
@@ -333,6 +333,20 @@ but you can set it yourself to change its ``priority`` or ``name`` attributes.
.. configuration-block::
+ .. code-block:: php-attributes
+
+ // src/ValueResolver/BookingIdValueResolver.php
+ namespace App\ValueResolver;
+
+ use Symfony\Component\DependencyInjection\Attribute\AsTaggedItem;
+ use Symfony\Component\HttpKernel\Controller\ValueResolverInterface;
+
+ #[AsTaggedItem(index: 'booking_id', priority: 150)]
+ class BookingIdValueResolver implements ValueResolverInterface
+ {
+ // ...
+ }
+
.. code-block:: yaml
# config/services.yaml
@@ -414,7 +428,7 @@ As an alternative, you can add the
:class:`Symfony\\Component\\HttpKernel\\Attribute\\AsTargetedValueResolver` attribute
to your resolver and pass your custom name as its first argument::
- // src/ValueResolver/IdentifierValueResolver.php
+ // src/ValueResolver/BookingIdValueResolver.php
namespace App\ValueResolver;
use Symfony\Component\HttpKernel\Attribute\AsTargetedValueResolver;
diff --git a/create_framework/dependency_injection.rst b/create_framework/dependency_injection.rst
index de3c4e11e4e..aa377a77b5a 100644
--- a/create_framework/dependency_injection.rst
+++ b/create_framework/dependency_injection.rst
@@ -10,7 +10,6 @@ to it::
namespace Simplex;
use Symfony\Component\EventDispatcher\EventDispatcher;
- use Symfony\Component\HttpFoundation;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel;
use Symfony\Component\Routing;
@@ -199,6 +198,7 @@ Now, here is how you can register a custom listener in the front controller::
// ...
use Simplex\StringResponseListener;
+ use Symfony\Component\DependencyInjection\Reference;
$container->register('listener.string_response', StringResponseListener::class);
$container->getDefinition('dispatcher')
@@ -227,16 +227,16 @@ object::
$container->setParameter('charset', 'UTF-8');
Instead of relying on the convention that the routes are defined by the
-``$routes`` variables, let's use a parameter again::
+``$routes`` variables, let's use a reference::
// ...
$container->register('matcher', Routing\Matcher\UrlMatcher::class)
- ->setArguments(['%routes%', new Reference('context')])
+ ->setArguments([new Reference('routes'), new Reference('context')])
;
And the related change in the front controller::
- $container->setParameter('routes', include __DIR__.'/../src/app.php');
+ $container->set('routes', $routes);
We have barely scratched the surface of what you can do with the
container: from class names as parameters, to overriding existing object
diff --git a/create_framework/event_dispatcher.rst b/create_framework/event_dispatcher.rst
index 650e4c7554e..9a3a48942ac 100644
--- a/create_framework/event_dispatcher.rst
+++ b/create_framework/event_dispatcher.rst
@@ -121,7 +121,7 @@ the registration of a listener for the ``response`` event::
$response = $event->getResponse();
if ($response->isRedirection()
- || ($response->headers->has('Content-Type') && false === strpos($response->headers->get('Content-Type'), 'html'))
+ || ($response->headers->has('Content-Type') && !str_contains($response->headers->get('Content-Type'), 'html'))
|| 'html' !== $event->getRequest()->getRequestFormat()
) {
return;
@@ -200,7 +200,7 @@ Let's refactor the code a bit by moving the Google listener to its own class::
$response = $event->getResponse();
if ($response->isRedirection()
- || ($response->headers->has('Content-Type') && false === strpos($response->headers->get('Content-Type'), 'html'))
+ || ($response->headers->has('Content-Type') && !str_contains($response->headers->get('Content-Type'), 'html'))
|| 'html' !== $event->getRequest()->getRequestFormat()
) {
return;
diff --git a/create_framework/http_foundation.rst b/create_framework/http_foundation.rst
index 219119164b4..71146b1785c 100644
--- a/create_framework/http_foundation.rst
+++ b/create_framework/http_foundation.rst
@@ -189,7 +189,7 @@ fingertips thanks to a nice and simple API::
// retrieves a COOKIE value
$request->cookies->get('PHPSESSID');
- // retrieves a HTTP request header, with normalized, lowercase keys
+ // retrieves an HTTP request header, with normalized, lowercase keys
$request->headers->get('host');
$request->headers->get('content-type');
diff --git a/create_framework/http_kernel_controller_resolver.rst b/create_framework/http_kernel_controller_resolver.rst
index 1c2857c9ed9..6c7e469da27 100644
--- a/create_framework/http_kernel_controller_resolver.rst
+++ b/create_framework/http_kernel_controller_resolver.rst
@@ -165,15 +165,6 @@ Let's conclude with the new version of our framework::
use Symfony\Component\HttpKernel;
use Symfony\Component\Routing;
- function render_template(Request $request): Response
- {
- extract($request->attributes->all(), EXTR_SKIP);
- ob_start();
- include sprintf(__DIR__.'/../src/pages/%s.php', $_route);
-
- return new Response(ob_get_clean());
- }
-
$request = Request::createFromGlobals();
$routes = include __DIR__.'/../src/app.php';
diff --git a/create_framework/http_kernel_httpkernel_class.rst b/create_framework/http_kernel_httpkernel_class.rst
index ecf9d4c7879..158de638f8a 100644
--- a/create_framework/http_kernel_httpkernel_class.rst
+++ b/create_framework/http_kernel_httpkernel_class.rst
@@ -39,7 +39,6 @@ And the new front controller::
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
- use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel;
use Symfony\Component\Routing;
diff --git a/create_framework/unit_testing.rst b/create_framework/unit_testing.rst
index 32c97a03846..9c179cd3152 100644
--- a/create_framework/unit_testing.rst
+++ b/create_framework/unit_testing.rst
@@ -12,7 +12,7 @@ using `PHPUnit`_. At first, install PHPUnit as a development dependency:
.. code-block:: terminal
- $ composer require --dev phpunit/phpunit:^9.6
+ $ composer require --dev phpunit/phpunit:^11.0
Then, create a PHPUnit configuration file in ``example.com/phpunit.xml.dist``:
@@ -21,16 +21,16 @@ Then, create a PHPUnit configuration file in ``example.com/phpunit.xml.dist``:
-
+
./src
-
+
@@ -103,12 +103,12 @@ We are now ready to write our first test::
$matcher
->expects($this->once())
->method('match')
- ->will($this->throwException($exception))
+ ->willThrowException($exception)
;
$matcher
->expects($this->once())
->method('getContext')
- ->will($this->returnValue($this->createMock(Routing\RequestContext::class)))
+ ->willReturn($this->createMock(Routing\RequestContext::class))
;
$controllerResolver = $this->createMock(ControllerResolverInterface::class);
$argumentResolver = $this->createMock(ArgumentResolverInterface::class);
@@ -161,16 +161,16 @@ Response::
$matcher
->expects($this->once())
->method('match')
- ->will($this->returnValue([
+ ->willReturn([
'_route' => 'is_leap_year/{year}',
'year' => '2000',
'_controller' => [new LeapYearController(), 'index'],
- ]))
+ ])
;
$matcher
->expects($this->once())
->method('getContext')
- ->will($this->returnValue($this->createMock(Routing\RequestContext::class)))
+ ->willReturn($this->createMock(Routing\RequestContext::class))
;
$controllerResolver = new ControllerResolver();
$argumentResolver = new ArgumentResolver();
@@ -194,7 +194,7 @@ coverage feature (you need to enable `XDebug`_ first):
$ ./vendor/bin/phpunit --coverage-html=cov/
-Open ``example.com/cov/src/Simplex/Framework.php.html`` in a browser and check
+Open ``example.com/cov/Simplex/Framework.php.html`` in a browser and check
that all the lines for the Framework class are green (it means that they have
been visited when the tests were executed).
@@ -212,6 +212,6 @@ Symfony code.
Now that we are confident (again) about the code we have written, we can
safely think about the next batch of features we want to add to our framework.
-.. _`PHPUnit`: https://docs.phpunit.de/en/9.6/
-.. _`test doubles`: https://docs.phpunit.de/en/9.6/test-doubles.html
+.. _`PHPUnit`: https://docs.phpunit.de/en/11.0/
+.. _`test doubles`: https://docs.phpunit.de/en/11.0/test-doubles.html
.. _`XDebug`: https://xdebug.org/
diff --git a/deployment.rst b/deployment.rst
index 864ebc7a963..07187f53cba 100644
--- a/deployment.rst
+++ b/deployment.rst
@@ -134,17 +134,18 @@ B) Configure your Environment Variables
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Most Symfony applications read their configuration from environment variables.
-While developing locally, you'll usually store these in ``.env`` and ``.env.local``
-(for local overrides). On production, you have two options:
+While developing locally, you'll usually store these in :ref:`.env files `.
+On production, you have two options:
1. Create "real" environment variables. How you set environment variables, depends
on your setup: they can be set at the command line, in your Nginx configuration,
or via other methods provided by your hosting service;
-2. Or, create a ``.env.local`` file like your local development.
+2. Or, create a ``.env.prod.local`` file that contains values specific to your
+ production environment.
-There is no significant advantage to either of the two options: use whatever is
-most natural in your hosting environment.
+There is no significant advantage to either option: use whichever is most natural
+for your hosting environment.
.. tip::
diff --git a/doctrine.rst b/doctrine.rst
index 171f8a3348a..e14e4b9e4b5 100644
--- a/doctrine.rst
+++ b/doctrine.rst
@@ -646,8 +646,22 @@ automatically! You can simplify the controller to::
}
}
-That's it! The bundle uses the ``{id}`` from the route to query for the ``Product``
-by the ``id`` column. If it's not found, a 404 page is generated.
+That's it! The attribute uses the ``{id}`` from the route to query for the ``Product``
+by the ``id`` column. If it's not found, a 404 error is thrown.
+
+You can change this behavior by making the controller argument optional. In that
+case, no 404 is thrown automatically and you're free to handle the missing entity
+yourself::
+
+ #[Route('/product/{id}')]
+ public function show(?Product $product): Response
+ {
+ if (null === $product) {
+ // run your own logic to return a custom response
+ }
+
+ // ...
+ }
.. tip::
@@ -680,7 +694,7 @@ will automatically fetch them::
/**
* Perform a findOneBy() where the slug property matches {slug}.
*/
- #[Route('/product/{slug}')]
+ #[Route('/product/{slug:product}')]
public function showBySlug(Product $product): Response
{
}
@@ -694,14 +708,17 @@ Automatic fetching works in these situations:
*all* of the wildcards in your route that are actually properties
on your entity (non-properties are ignored).
-This behavior is enabled by default on all controllers. If you prefer, you can
-restrict this feature to only work on route wildcards called ``id`` to look for
-entities by primary key. To do so, set the option
-``doctrine.orm.controller_resolver.auto_mapping`` to ``false``.
+The ``{slug:product}`` syntax maps the route parameter named ``slug`` to the
+controller argument named ``$product``. It also hints the resolver to look up
+the corresponding ``Product`` object from the database using the slug.
+
+.. versionadded:: 7.1
+
+ Route parameter mapping was introduced in Symfony 7.1.
-When ``auto_mapping`` is disabled, you can configure the mapping explicitly for
-any controller argument with the ``MapEntity`` attribute. You can even control
-the ``EntityValueResolver`` behavior by using the `MapEntity options`_ ::
+You can also configure the mapping explicitly for any controller argument
+using the ``MapEntity`` attribute. You can even control the behavior of the
+``EntityValueResolver`` by using the `MapEntity options`_ ::
// src/Controller/ProductController.php
namespace App\Controller;
@@ -780,6 +797,32 @@ variable. Let's say you want the first or the last comment of a product dependin
Comment $comment
): Response {
}
+
+.. _doctrine-entity-value-resolver-resolve-target-entities:
+
+Fetch via Interfaces
+~~~~~~~~~~~~~~~~~~~~
+
+Suppose your ``Product`` class implements an interface called ``ProductInterface``.
+If you want to decouple your controllers from the concrete entity implementation,
+you can reference the entity by its interface instead.
+
+To enable this, first configure the
+:doc:`resolve_target_entities option `.
+Then, your controller can type-hint the interface, and the entity will be
+resolved automatically::
+
+ public function show(
+ #[MapEntity]
+ ProductInterface $product
+ ): Response {
+ // ...
+ }
+
+.. versionadded:: 7.3
+
+ Support for target entity resolution in the ``EntityValueResolver`` was
+ introduced Symfony 7.3
MapEntity Options
~~~~~~~~~~~~~~~~~
@@ -812,18 +855,6 @@ control behavior:
): Response {
}
-``exclude``
- Configures the properties that should be used in the ``findOneBy()``
- method by *excluding* one or more properties so that not *all* are used::
-
- #[Route('/product/{slug}/{date}')]
- public function show(
- #[MapEntity(exclude: ['date'])]
- Product $product,
- \DateTime $date
- ): Response {
- }
-
``stripNull``
If true, then when ``findOneBy()`` is used, any values that are
``null`` will not be used for the query.
diff --git a/doctrine/associations.rst b/doctrine/associations.rst
index 8dd9aa7f36b..bb670eeee52 100644
--- a/doctrine/associations.rst
+++ b/doctrine/associations.rst
@@ -447,7 +447,7 @@ by adding JOINs.
$category = $product->getCategory();
// prints "Proxies\AppEntityCategoryProxy"
- dump(get_class($category));
+ dump($category::class);
die();
This proxy object extends the true ``Category`` object, and looks and
@@ -501,7 +501,7 @@ following method to the ``ProductRepository`` class::
}
}
-This will *still* return an array of ``Product`` objects. But now, when you call
+This will *still* return a ``Product`` object. But now, when you call
``$product->getCategory()`` and use that data, no second query is made.
Now, you can use this method in your controller to query for a ``Product``
diff --git a/doctrine/events.rst b/doctrine/events.rst
index 929f44b915e..accf424083a 100644
--- a/doctrine/events.rst
+++ b/doctrine/events.rst
@@ -304,23 +304,6 @@ listener in the Symfony application by creating a new service for it and
.. configuration-block::
- .. code-block:: php-attributes
-
- // src/EventListener/SearchIndexer.php
- namespace App\EventListener;
-
- use Doctrine\Bundle\DoctrineBundle\Attribute\AsDoctrineListener;
- use Doctrine\ORM\Event\PostPersistEventArgs;
-
- #[AsDoctrineListener('postPersist'/*, 500, 'default'*/)]
- class SearchIndexer
- {
- public function postPersist(PostPersistEventArgs $event): void
- {
- // ...
- }
- }
-
.. code-block:: yaml
# config/services.yaml
diff --git a/doctrine/resolve_target_entity.rst b/doctrine/resolve_target_entity.rst
index 5ae6475a957..1495f475628 100644
--- a/doctrine/resolve_target_entity.rst
+++ b/doctrine/resolve_target_entity.rst
@@ -1,39 +1,45 @@
-How to Define Relationships with Abstract Classes and Interfaces
-================================================================
+Referencing Entities with Abstract Classes and Interfaces
+=========================================================
-One of the goals of bundles is to create discrete bundles of functionality
-that do not have many (if any) dependencies, allowing you to use that
-functionality in other applications without including unnecessary items.
+In applications where functionality is organized in layers or modules with
+minimal concrete dependencies, such as monoliths split into multiple modules,
+it can be challenging to avoid tight coupling between entities.
-Doctrine 2.2 includes a new utility called the ``ResolveTargetEntityListener``,
-that functions by intercepting certain calls inside Doctrine and rewriting
-``targetEntity`` parameters in your metadata mapping at runtime. It means that
-in your bundle you are able to use an interface or abstract class in your
-mappings and expect correct mapping to a concrete entity at runtime.
+Doctrine provides a utility called the ``ResolveTargetEntityListener`` to solve
+this issue. It works by intercepting certain calls within Doctrine and rewriting
+``targetEntity`` parameters in your metadata mapping at runtime. This allows you
+to reference an interface or abstract class in your mappings and have it resolved
+to a concrete entity at runtime.
-This functionality allows you to define relationships between different entities
-without making them hard dependencies.
+This makes it possible to define relationships between entities without
+creating hard dependencies. This feature also works with the ``EntityValueResolver``
+:ref:`as explained in the main Doctrine article `.
+
+.. versionadded:: 7.3
+
+ Support for target entity resolution in the ``EntityValueResolver`` was
+ introduced Symfony 7.3
Background
----------
-Suppose you have an InvoiceBundle which provides invoicing functionality
-and a CustomerBundle that contains customer management tools. You want
-to keep these separated, because they can be used in other systems without
-each other, but for your application you want to use them together.
+Suppose you have an application with two modules: an Invoice module that
+provides invoicing functionality, and a Customer module that handles customer
+management. You want to keep these modules decoupled, so that neither is aware
+of the other's implementation details.
-In this case, you have an ``Invoice`` entity with a relationship to a
-non-existent object, an ``InvoiceSubjectInterface``. The goal is to get
-the ``ResolveTargetEntityListener`` to replace any mention of the interface
-with a real object that implements that interface.
+In this case, your ``Invoice`` entity has a relationship to the interface
+``InvoiceSubjectInterface``. Since interfaces are not valid Doctrine entities,
+the goal is to use the ``ResolveTargetEntityListener`` to replace all
+references to this interface with a concrete class that implements it.
Set up
------
-This article uses the following two basic entities (which are incomplete for
-brevity) to explain how to set up and use the ``ResolveTargetEntityListener``.
+This article uses two basic (incomplete) entities to demonstrate how to set up
+and use the ``ResolveTargetEntityListener``.
-A Customer entity::
+A ``Customer`` entity::
// src/Entity/Customer.php
namespace App\Entity;
@@ -50,7 +56,7 @@ A Customer entity::
// are already implemented in the BaseCustomer
}
-An Invoice entity::
+An ``Invoice`` entity::
// src/Entity/Invoice.php
namespace App\Entity;
@@ -58,9 +64,6 @@ An Invoice entity::
use App\Model\InvoiceSubjectInterface;
use Doctrine\ORM\Mapping as ORM;
- /**
- * Represents an Invoice.
- */
#[ORM\Entity]
#[ORM\Table(name: 'invoice')]
class Invoice
@@ -69,7 +72,7 @@ An Invoice entity::
protected InvoiceSubjectInterface $subject;
}
-An InvoiceSubjectInterface::
+The interface representing the subject used in the invoice::
// src/Model/InvoiceSubjectInterface.php
namespace App\Model;
@@ -89,8 +92,8 @@ An InvoiceSubjectInterface::
public function getName(): string;
}
-Next, you need to configure the listener, which tells the DoctrineBundle
-about the replacement:
+Now configure the ``resolve_target_entities`` option to tell Doctrine
+how to replace the interface with the concrete class:
.. configuration-block::
@@ -140,7 +143,6 @@ about the replacement:
Final Thoughts
--------------
-With the ``ResolveTargetEntityListener``, you are able to decouple your
-bundles, keeping them usable by themselves, but still being able to
-define relationships between different objects. By using this method,
-your bundles will end up being easier to maintain independently.
+Using ``ResolveTargetEntityListener`` allows you to decouple your modules
+while still defining relationships between their entities. This makes your
+codebase more modular and easier to maintain over time.
diff --git a/event_dispatcher.rst b/event_dispatcher.rst
index 7372d58b8d0..13ef1624370 100644
--- a/event_dispatcher.rst
+++ b/event_dispatcher.rst
@@ -149,7 +149,7 @@ Defining Event Listeners with PHP Attributes
An alternative way to define an event listener is to use the
:class:`Symfony\\Component\\EventDispatcher\\Attribute\\AsEventListener`
-PHP attribute. This allows to configure the listener inside its class, without
+PHP attribute. This allows you to configure the listener inside its class, without
having to add any configuration in external files::
namespace App\EventListener;
@@ -246,14 +246,14 @@ methods could be called before or after the methods defined in other listeners
and subscribers. To learn more about event subscribers, read :doc:`/components/event_dispatcher`.
The following example shows an event subscriber that defines several methods which
-listen to the same ``kernel.exception`` event::
+listen to the same :ref:`kernel.exception event `
+via its ``ExceptionEvent`` class::
// src/EventSubscriber/ExceptionSubscriber.php
namespace App\EventSubscriber;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
- use Symfony\Component\HttpKernel\KernelEvents;
class ExceptionSubscriber implements EventSubscriberInterface
{
@@ -261,7 +261,7 @@ listen to the same ``kernel.exception`` event::
{
// return the subscribed events, their methods and priorities
return [
- KernelEvents::EXCEPTION => [
+ ExceptionEvent::class => [
['processException', 10],
['logException', 0],
['notifyException', -10],
diff --git a/form/dynamic_form_modification.rst b/form/dynamic_form_modification.rst
index 09be80ebb5a..a1f32c7c16c 100644
--- a/form/dynamic_form_modification.rst
+++ b/form/dynamic_form_modification.rst
@@ -138,8 +138,8 @@ For better reusability or if there is some heavy logic in your event listener,
you can also move the logic for creating the ``name`` field to an
:ref:`event subscriber `::
- // src/Form/EventListener/AddNameFieldSubscriber.php
- namespace App\Form\EventListener;
+ // src/Form/EventSubscriber/AddNameFieldSubscriber.php
+ namespace App\Form\EventSubscriber;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Form\Extension\Core\Type\TextType;
@@ -172,7 +172,7 @@ Great! Now use that in your form class::
namespace App\Form\Type;
// ...
- use App\Form\EventListener\AddNameFieldSubscriber;
+ use App\Form\EventSubscriber\AddNameFieldSubscriber;
class ProductType extends AbstractType
{
diff --git a/form/embedded.rst b/form/embedded.rst
index dd163235f03..9e20164c3a4 100644
--- a/form/embedded.rst
+++ b/form/embedded.rst
@@ -87,6 +87,7 @@ inside the task form itself. To accomplish this, add a ``category`` field
to the ``TaskType`` object whose type is an instance of the new ``CategoryType``
class::
+ // src/Form/TaskType.php
use App\Form\CategoryType;
use Symfony\Component\Form\FormBuilderInterface;
diff --git a/form/form_collections.rst b/form/form_collections.rst
index 2a0ba99657f..3c8a2050690 100644
--- a/form/form_collections.rst
+++ b/form/form_collections.rst
@@ -699,13 +699,12 @@ the relationship between the removed ``Tag`` and ``Task`` object.
The Symfony community has created some JavaScript packages that provide the
functionality needed to add, edit and delete elements of the collection.
- Check out the `@a2lix/symfony-collection`_ package for modern browsers and
- the `symfony-collection`_ package based on jQuery for the rest of browsers.
+ Check out the `@a2lix/symfony-collection`_ or search on GitHub for other
+ recent packages.
.. _`Owning Side and Inverse Side`: https://www.doctrine-project.org/projects/doctrine-orm/en/current/reference/unitofwork-associations.html
.. _`JSFiddle`: https://jsfiddle.net/ey8ozh6n/
.. _`@a2lix/symfony-collection`: https://github.com/a2lix/symfony-collection
-.. _`symfony-collection`: https://github.com/ninsuo/symfony-collection
.. _`ArrayCollection`: https://www.doctrine-project.org/projects/doctrine-collections/en/1.6/index.html
.. _`Symfony UX Demo of Form Collections`: https://ux.symfony.com/live-component/demos/form-collection-type
.. _`Stimulus`: https://symfony.com/doc/current/frontend/encore/simple-example.html#stimulus-symfony-ux
diff --git a/frontend/asset_mapper.rst b/frontend/asset_mapper.rst
index 8b27cd8ba16..454e13a1d29 100644
--- a/frontend/asset_mapper.rst
+++ b/frontend/asset_mapper.rst
@@ -252,6 +252,26 @@ This adds the ``bootstrap`` package to your ``importmap.php`` file::
`package.json configuration file`_. Try to contact the package maintainer to
ask them to fix those issues.
+.. tip::
+
+ If you see a network error like *Connection was reset for "https://cdn.jsdelivr.net/npm/..."*,
+ it may be caused by a proxy or firewall restriction. In that case, you can
+ temporarily configure a proxy to connect to the ``jsDelivr`` CDN:
+
+ .. code-block:: yaml
+
+ # config/packages/framework.yaml
+ framework:
+ # ...
+ http_client:
+ default_options:
+ proxy: '185.250.180.238:8080'
+ # if you use CURL, add extra options:
+ extra:
+ curl:
+ # 61 is value of constant CURLOPT_HTTPPROXYTUNNEL
+ '61': true
+
Now you can import the ``bootstrap`` package like usual:
.. code-block:: javascript
@@ -720,7 +740,11 @@ Symfony will add a ``Link`` header in the response to preload the CSS files.
Pre-Compressing Assets
----------------------
-Although most servers (Caddy, Nginx, Apache, FrankenPHP) and services like Cloudflare
+.. versionadded:: 7.3
+
+ Support for pre-compressing assets was introduced in Symfony 7.3.
+
+Although most web 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.
@@ -730,7 +754,7 @@ 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
+Before using any of them, the machine that pre-compresses assets must have
installed the following PHP extensions or CLI commands:
* Brotli: ``brotli`` CLI command; `brotli PHP extension`_;
@@ -748,7 +772,12 @@ and which file extensions should be compressed:
# ...
precompress:
+ # possible values: 'brotli', 'zstandard', 'gzip'
format: 'zstandard'
+
+ # you can also pass multiple values to generate files in several formats
+ # format: ['brotli', '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']
@@ -1146,14 +1175,19 @@ both ``app`` and ``checkout``:
{{ importmap(['app', 'checkout']) }}
{% endblock %}
-By passing both ``app`` and ``checkout``, the ``importmap()`` function will
-output the ``importmap`` and also add a ``