diff --git a/components/clock.rst b/components/clock.rst
index 5b20e6000b9..c4ac88e9092 100644
--- a/components/clock.rst
+++ b/components/clock.rst
@@ -267,6 +267,36 @@ timestamps::
:method:`Symfony\\Component\\Clock\\DatePoint::getMicrosecond` methods were
introduced in Symfony 7.1.
+Storing DatePoints in the Database
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If you :doc:`use Doctrine ` to work with databases, consider using the
+``date_point`` Doctrine type, which converts to/from ``DatePoint`` objects automatically::
+
+ // src/Entity/Product.php
+ namespace App\Entity;
+
+ use Doctrine\ORM\Mapping as ORM;
+ use Symfony\Component\Clock\DatePoint;
+
+ #[ORM\Entity]
+ class Product
+ {
+ // if you don't define the Doctrine type explicitly, Symfony will autodetect it:
+ #[ORM\Column]
+ private DatePoint $createdAt;
+
+ // if you prefer to define the Doctrine type explicitly:
+ #[ORM\Column(type: 'date_point')]
+ private DatePoint $updatedAt;
+
+ // ...
+ }
+
+.. versionadded:: 7.3
+
+ The ``DatePointType`` was introduced in Symfony 7.3.
+
.. _clock_writing-tests:
Writing Time-Sensitive Tests
diff --git a/components/config/definition.rst b/components/config/definition.rst
index 0e626931568..4848af33ffe 100644
--- a/components/config/definition.rst
+++ b/components/config/definition.rst
@@ -186,6 +186,25 @@ The configuration can now be written like this::
->end()
;
+You can also use the ``enumClass()`` method to pass the FQCN of an enum
+class to the node. This will automatically set the values of the node to
+the cases of the enum::
+
+ $rootNode
+ ->children()
+ ->enumNode('delivery')
+ ->enumClass(Delivery::class)
+ ->end()
+ ->end()
+ ;
+
+When using a backed enum, the values provided to the node will be cast
+to one of the enum cases if possible.
+
+.. versionadded:: 7.3
+
+ The ``enumClass()`` method was introduced in Symfony 7.3.
+
Array Nodes
~~~~~~~~~~~
@@ -527,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
-----------------
@@ -815,6 +858,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 +877,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
-----------------------------------
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/map.rst.inc b/components/console/helpers/map.rst.inc
index 8f9ce0ca0f3..b190d1dce44 100644
--- a/components/console/helpers/map.rst.inc
+++ b/components/console/helpers/map.rst.inc
@@ -3,5 +3,6 @@
* :doc:`/components/console/helpers/progressbar`
* :doc:`/components/console/helpers/questionhelper`
* :doc:`/components/console/helpers/table`
+* :doc:`/components/console/helpers/tree`
* :doc:`/components/console/helpers/debug_formatter`
* :doc:`/components/console/helpers/cursor`
diff --git a/components/console/helpers/questionhelper.rst b/components/console/helpers/questionhelper.rst
index 3dc97d5c0d3..c7e064b16ca 100644
--- a/components/console/helpers/questionhelper.rst
+++ b/components/console/helpers/questionhelper.rst
@@ -480,10 +480,10 @@ invalid answer and will only be able to proceed if their input is valid.
use Symfony\Component\Validator\Validation;
$question = new Question('Please enter the name of the bundle', 'AcmeDemoBundle');
- $validation = Validation::createCallable(new Regex([
- 'pattern' => '/^[a-zA-Z]+Bundle$/',
- 'message' => 'The name of the bundle should be suffixed with \'Bundle\'',
- ]));
+ $validation = Validation::createCallable(new Regex(
+ pattern: '/^[a-zA-Z]+Bundle$/',
+ message: 'The name of the bundle should be suffixed with \'Bundle\'',
+ ));
$question->setValidator($validation);
Validating a Hidden Response
diff --git a/components/console/helpers/table.rst b/components/console/helpers/table.rst
index 13bdeb491f0..9d6fdb0ee61 100644
--- a/components/console/helpers/table.rst
+++ b/components/console/helpers/table.rst
@@ -181,11 +181,31 @@ The table style can be changed to any built-in styles via
// same as calling nothing
$table->setStyle('default');
- // changes the default style to compact
+ // changes the default style to markdown
+ $table->setStyle('markdown');
+ $table->render();
+
+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``::
+
$table->setStyle('compact');
$table->render();
-This code results in:
+The output of this command will be:
.. code-block:: terminal
diff --git a/components/console/helpers/tree.rst b/components/console/helpers/tree.rst
new file mode 100644
index 00000000000..1161d00e942
--- /dev/null
+++ b/components/console/helpers/tree.rst
@@ -0,0 +1,295 @@
+Tree Helper
+===========
+
+The Tree Helper allows you to build and display tree structures in the console.
+It's commonly used to render directory hierarchies, but you can also use it to render
+any tree-like content, such us organizational charts, product category trees, taxonomies, etc.
+
+.. versionadded:: 7.3
+
+ The ``TreeHelper`` class was introduced in Symfony 7.3.
+
+Rendering a Tree
+----------------
+
+The :method:`Symfony\\Component\\Console\\Helper\\TreeHelper::createTree` method
+creates a tree structure from an array and returns a :class:`Symfony\\Component\\Console\\Helper\\Tree`
+object that can be rendered in the console.
+
+Rendering a Tree from an Array
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You can build a tree from an array by passing the array to the
+:method:`Symfony\\Component\\Console\\Helper\\TreeHelper::createTree` method
+inside your console command::
+
+ namespace App\Command;
+
+ use Symfony\Component\Console\Attribute\AsCommand;
+ use Symfony\Component\Console\Command\Command;
+ use Symfony\Component\Console\Helper\TreeHelper;
+ use Symfony\Component\Console\Helper\TreeNode;
+ use Symfony\Component\Console\Input\InputInterface;
+ use Symfony\Component\Console\Output\OutputInterface;
+ use Symfony\Component\Console\Style\SymfonyStyle;
+
+ #[AsCommand(name: 'app:some-command', description: '...')]
+ class SomeCommand extends Command
+ {
+ // ...
+
+ protected function execute(InputInterface $input, OutputInterface $output): int
+ {
+ $io = new SymfonyStyle($input, $output);
+
+ $node = TreeNode::fromValues([
+ 'config/',
+ 'public/',
+ 'src/',
+ 'templates/',
+ 'tests/',
+ ]);
+
+ $tree = TreeHelper::createTree($io, $node);
+ $tree->render();
+
+ // ...
+ }
+ }
+
+This exampe would output the following:
+
+.. code-block:: terminal
+
+ ├── config/
+ ├── public/
+ ├── src/
+ ├── templates/
+ └── tests/
+
+The given contents can be defined in a multi-dimensional array::
+
+ $tree = TreeHelper::createTree($io, null, [
+ 'src' => [
+ 'Command',
+ 'Controller' => [
+ 'DefaultController.php',
+ ],
+ 'Kernel.php',
+ ],
+ 'templates' => [
+ 'base.html.twig',
+ ],
+ ]);
+
+ $tree->render();
+
+The above code will output the following tree:
+
+.. code-block:: terminal
+
+ ├── src
+ │ ├── Command
+ │ ├── Controller
+ │ │ └── DefaultController.php
+ │ └── Kernel.php
+ └── templates
+ └── base.html.twig
+
+Building and Rendering a Tree
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You can build a tree 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');
+
+ $tree = TreeHelper::createTree($io, $node);
+ $tree->render();
+
+Customizing the Tree Style
+--------------------------
+
+Built-in Tree Styles
+~~~~~~~~~~~~~~~~~~~~
+
+The tree helper provides a few built-in styles that you can use to customize the
+output of the tree::
+
+ use Symfony\Component\Console\Helper\TreeStyle;
+ // ...
+
+ $tree = TreeHelper::createTree($io, $node, [], TreeStyle::compact());
+ $tree->render();
+
+``TreeHelper::createTree($io, $node, [], TreeStyle::default())`` (`details`_)
+
+.. code-block:: terminal
+
+ ├── config
+ │ ├── packages
+ │ └── routes
+ │ ├── framework.yaml
+ │ └── web_profiler.yaml
+ ├── src
+ │ ├── Command
+ │ ├── Controller
+ │ │ └── DefaultController.php
+ │ └── Kernel.php
+ └── templates
+ └── base.html.twig
+
+``TreeHelper::createTree($io, $node, [], TreeStyle::box())`` (`details`_)
+
+.. code-block:: terminal
+
+ ┃╸ config
+ ┃ ┃╸ packages
+ ┃ ┗╸ routes
+ ┃ ┃╸ framework.yaml
+ ┃ ┗╸ web_profiler.yaml
+ ┃╸ src
+ ┃ ┃╸ Command
+ ┃ ┃╸ Controller
+ ┃ ┃ ┗╸ DefaultController.php
+ ┃ ┗╸ Kernel.php
+ ┗╸ templates
+ ┗╸ base.html.twig
+
+``TreeHelper::createTree($io, $node, [], TreeStyle::doubleBox())`` (`details`_)
+
+.. code-block:: terminal
+
+ ╠═ config
+ ║ ╠═ packages
+ ║ ╚═ routes
+ ║ ╠═ framework.yaml
+ ║ ╚═ web_profiler.yaml
+ ╠═ src
+ ║ ╠═ Command
+ ║ ╠═ Controller
+ ║ ║ ╚═ DefaultController.php
+ ║ ╚═ Kernel.php
+ ╚═ templates
+ ╚═ base.html.twig
+
+``TreeHelper::createTree($io, $node, [], TreeStyle::compact())`` (`details`_)
+
+.. code-block:: terminal
+
+ ├ config
+ │ ├ packages
+ │ └ routes
+ │ ├ framework.yaml
+ │ └ web_profiler.yaml
+ ├ src
+ │ ├ Command
+ │ ├ Controller
+ │ │ └ DefaultController.php
+ │ └ Kernel.php
+ └ templates
+ └ base.html.twig
+
+``TreeHelper::createTree($io, $node, [], TreeStyle::light())`` (`details`_)
+
+.. code-block:: terminal
+
+ |-- config
+ | |-- packages
+ | `-- routes
+ | |-- framework.yaml
+ | `-- web_profiler.yaml
+ |-- src
+ | |-- Command
+ | |-- Controller
+ | | `-- DefaultController.php
+ | `-- Kernel.php
+ `-- templates
+ `-- base.html.twig
+
+``TreeHelper::createTree($io, $node, [], TreeStyle::minimal())`` (`details`_)
+
+.. code-block:: terminal
+
+ . config
+ . . packages
+ . . routes
+ . . framework.yaml
+ . . web_profiler.yaml
+ . src
+ . . Command
+ . . Controller
+ . . . DefaultController.php
+ . . Kernel.php
+ . templates
+ . base.html.twig
+
+``TreeHelper::createTree($io, $node, [], TreeStyle::rounded())`` (`details`_)
+
+.. code-block:: terminal
+
+ ├─ config
+ │ ├─ packages
+ │ ╰─ routes
+ │ ├─ framework.yaml
+ │ ╰─ web_profiler.yaml
+ ├─ src
+ │ ├─ Command
+ │ ├─ Controller
+ │ │ ╰─ DefaultController.php
+ │ ╰─ Kernel.php
+ ╰─ templates
+ ╰─ base.html.twig
+
+Making a Custom Tree Style
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You can create your own tree style by passing the characters to the constructor
+of the :class:`Symfony\\Component\\Console\\Helper\\TreeStyle` class::
+
+ use Symfony\Component\Console\Helper\TreeHelper;
+ use Symfony\Component\Console\Helper\TreeStyle;
+
+ $customStyle = new TreeStyle('🟣 ', '🟠 ', '🔵 ', '🟢 ', '🔴 ', '🟡 ');
+
+ // Pass the custom style to the createTree method
+
+ $tree = TreeHelper::createTree($io, null, [
+ 'src' => [
+ 'Command',
+ 'Controller' => [
+ 'DefaultController.php',
+ ],
+ 'Kernel.php',
+ ],
+ 'templates' => [
+ 'base.html.twig',
+ ],
+ ], $customStyle);
+
+ $tree->render();
+
+The above code will output the following tree:
+
+.. code-block:: terminal
+
+ 🔵 🟣 🟡 src
+ 🔵 🟢 🟣 🟡 Command
+ 🔵 🟢 🟣 🟡 Controller
+ 🔵 🟢 🟢 🟠 🟡 DefaultController.php
+ 🔵 🟢 🟠 🟡 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/http_foundation.rst b/components/http_foundation.rst
index f35f2020535..1cb87aafb24 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
~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/components/options_resolver.rst b/components/options_resolver.rst
index ff25f9e0fc4..6f3a6751f28 100644
--- a/components/options_resolver.rst
+++ b/components/options_resolver.rst
@@ -305,13 +305,21 @@ correctly. To validate the types of the options, call
// specify multiple allowed types
$resolver->setAllowedTypes('port', ['null', 'int']);
+ // 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
+
+ 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
using ``instanceof``). Additionally, you can validate all items in an array
@@ -386,7 +394,7 @@ returns ``true`` for acceptable values and ``false`` for invalid values::
// ...
$resolver->setAllowedValues('transport', Validation::createIsValidCallable(
- new Length(['min' => 10 ])
+ new Length(min: 10)
));
In sub-classes, you can use :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::addAllowedValues`
@@ -654,7 +662,7 @@ default value::
public function configureOptions(OptionsResolver $resolver): void
{
- $resolver->setDefault('spool', function (OptionsResolver $spoolResolver): void {
+ $resolver->setOptions('spool', function (OptionsResolver $spoolResolver): void {
$spoolResolver->setDefaults([
'type' => 'file',
'path' => '/path/to/spool',
@@ -678,6 +686,16 @@ default value::
],
]);
+.. deprecated:: 7.3
+
+ Defining nested options via :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::setDefault`
+ is deprecated since Symfony 7.3. Use the :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::setOptions`
+ method instead, which also allows defining default values for prototyped options.
+
+.. versionadded:: 7.3
+
+ The ``setOptions()`` method was introduced in Symfony 7.3.
+
Nested options also support required options, validation (type, value) and
normalization of their values. If the default value of a nested option depends
on another option defined in the parent level, add a second ``Options`` argument
@@ -690,7 +708,7 @@ to the closure to access to them::
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefault('sandbox', false);
- $resolver->setDefault('spool', function (OptionsResolver $spoolResolver, Options $parent): void {
+ $resolver->setOptions('spool', function (OptionsResolver $spoolResolver, Options $parent): void {
$spoolResolver->setDefaults([
'type' => $parent['sandbox'] ? 'memory' : 'file',
// ...
@@ -713,13 +731,13 @@ In same way, parent options can access to the nested options as normal arrays::
public function configureOptions(OptionsResolver $resolver): void
{
- $resolver->setDefault('spool', function (OptionsResolver $spoolResolver): void {
+ $resolver->setOptions('spool', function (OptionsResolver $spoolResolver): void {
$spoolResolver->setDefaults([
'type' => 'file',
// ...
]);
});
- $resolver->setDefault('profiling', function (Options $options): void {
+ $resolver->setOptions('profiling', function (Options $options): void {
return 'file' === $options['spool']['type'];
});
}
@@ -740,7 +758,7 @@ with ``host``, ``database``, ``user`` and ``password`` each.
The best way to implement this is to define the ``connections`` option as prototype::
- $resolver->setDefault('connections', function (OptionsResolver $connResolver): void {
+ $resolver->setOptions('connections', function (OptionsResolver $connResolver): void {
$connResolver
->setPrototype(true)
->setRequired(['host', 'database'])
diff --git a/components/process.rst b/components/process.rst
index f6c8837d2c3..7552537e82e 100644
--- a/components/process.rst
+++ b/components/process.rst
@@ -114,6 +114,8 @@ You can configure the options passed to the ``other_options`` argument of
and ``suppress_errors``) are only supported on Windows operating systems.
Check out the `PHP documentation for proc_open()`_ before using them.
+.. _process-using-features-from-the-os-shell:
+
Using Features From the OS Shell
--------------------------------
diff --git a/components/property_info.rst b/components/property_info.rst
index 2e1ee42dd3f..39019657ced 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
~~~~~~~~~~~~~~~~~~~
@@ -527,6 +538,8 @@ with the ``property_info`` service in the Symfony Framework::
// Type information.
$doctrineExtractor->getTypes($class, $property);
+.. _components-property-information-constructor-extractor:
+
ConstructorExtractor
~~~~~~~~~~~~~~~~~~~~
@@ -559,6 +572,7 @@ Creating Your Own Extractors
You can create your own property information extractors by creating a
class that implements one or more of the following interfaces:
+:class:`Symfony\\Component\\PropertyInfo\\Extractor\\ConstructorArgumentTypeExtractorInterface`,
:class:`Symfony\\Component\\PropertyInfo\\PropertyAccessExtractorInterface`,
:class:`Symfony\\Component\\PropertyInfo\\PropertyDescriptionExtractorInterface`,
:class:`Symfony\\Component\\PropertyInfo\\PropertyListExtractorInterface`,
@@ -576,6 +590,11 @@ service by defining it as a service with one or more of the following
* ``property_info.access_extractor`` if it provides access information.
* ``property_info.initializable_extractor`` if it provides initializable information
(it checks if a property can be initialized through the constructor).
+* ``property_info.constructor_extractor`` if it provides type information from the constructor argument.
+
+ .. versionadded:: 7.3
+
+ The ``property_info.constructor_extractor`` tag was introduced in Symfony 7.3.
.. _`PSR-1`: https://www.php-fig.org/psr/psr-1/
.. _`phpDocumentor Reflection`: https://github.com/phpDocumentor/ReflectionDocBlock
diff --git a/components/runtime.rst b/components/runtime.rst
index 4eb75de2a75..336a8573227 100644
--- a/components/runtime.rst
+++ b/components/runtime.rst
@@ -36,6 +36,8 @@ So how does this front-controller work? At first, the special
the component. This file runs the following logic:
#. It instantiates a :class:`Symfony\\Component\\Runtime\\RuntimeInterface`;
+#. The runtime includes the front-controller script -- in this case
+ ``public/index.php`` -- making it run again. Make sure this doesn't cause problems.
#. The callable (returned by ``public/index.php``) is passed to the Runtime, whose job
is to resolve the arguments (in this example: ``array $context``);
#. Then, this callable is called to get the application (``App\Kernel``);
diff --git a/components/type_info.rst b/components/type_info.rst
index 3d1aa569fec..817c7f1d61a 100644
--- a/components/type_info.rst
+++ b/components/type_info.rst
@@ -40,6 +40,16 @@ to the :class:`Symfony\\Component\\TypeInfo\\Type` static methods as following::
Many others methods are available and can be found
in :class:`Symfony\\Component\\TypeInfo\\TypeFactoryTrait`.
+You can also use a generic method that detects the type automatically::
+
+ Type::fromValue(1.1); // same as Type::float()
+ Type::fromValue('...'); // same as Type::string()
+ Type::fromValue(false); // same as Type::false()
+
+.. versionadded:: 7.3
+
+ The ``fromValue()`` method was introduced in Symfony 7.3.
+
Resolvers
~~~~~~~~~
@@ -114,7 +124,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 +151,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 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
diff --git a/components/validator.rst b/components/validator.rst
index 085c77a7946..12c61507257 100644
--- a/components/validator.rst
+++ b/components/validator.rst
@@ -36,7 +36,7 @@ characters long::
$validator = Validation::createValidator();
$violations = $validator->validate('Bernhard', [
- new Length(['min' => 10]),
+ new Length(min: 10),
new NotBlank(),
]);
diff --git a/components/validator/metadata.rst b/components/validator/metadata.rst
index e7df42413bc..782e1ee216f 100755
--- a/components/validator/metadata.rst
+++ b/components/validator/metadata.rst
@@ -24,7 +24,7 @@ the ``Author`` class has at least 3 characters::
$metadata->addPropertyConstraint('firstName', new Assert\NotBlank());
$metadata->addPropertyConstraint(
'firstName',
- new Assert\Length(["min" => 3])
+ new Assert\Length(min: 3)
);
}
}
@@ -55,9 +55,9 @@ Then, add the Validator component configuration to the class::
{
public static function loadValidatorMetadata(ClassMetadata $metadata): void
{
- $metadata->addGetterConstraint('passwordSafe', new Assert\IsTrue([
- 'message' => 'The password cannot match your first name',
- ]));
+ $metadata->addGetterConstraint('passwordSafe', new Assert\IsTrue(
+ message: 'The password cannot match your first name',
+ ));
}
}
diff --git a/components/validator/resources.rst b/components/validator/resources.rst
index 4fc7aa2cbb0..7d6cd0e8e5d 100644
--- a/components/validator/resources.rst
+++ b/components/validator/resources.rst
@@ -42,10 +42,10 @@ In this example, the validation metadata is retrieved executing the
public static function loadValidatorMetadata(ClassMetadata $metadata): void
{
$metadata->addPropertyConstraint('name', new Assert\NotBlank());
- $metadata->addPropertyConstraint('name', new Assert\Length([
- 'min' => 5,
- 'max' => 20,
- ]));
+ $metadata->addPropertyConstraint('name', new Assert\Length(
+ min: 5,
+ max: 20,
+ ));
}
}
diff --git a/components/yaml.rst b/components/yaml.rst
index 30f715a7a24..471b59dcf5c 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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -440,6 +450,58 @@ By default, digit-only array keys are dumped as integers. You can use the
$dumped = Yaml::dump([200 => 'foo'], 2, 4, Yaml::DUMP_NUMERIC_KEY_AS_STRING);
// '200': foo
+Dumping Double Quotes on Values
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+By default, only unsafe string values are enclosed in double quotes (for example,
+if they are reserved words or contain newlines and spaces). Use the
+``DUMP_FORCE_DOUBLE_QUOTES_ON_VALUES`` flag to add double quotes to all string values::
+
+ $dumped = Yaml::dump([
+ 'foo' => 'bar', 'some foo' => 'some bar', 'x' => 3.14, 'y' => true, 'z' => null,
+ ]);
+ // foo: bar, 'some foo': 'some bar', x: 3.14, 'y': true, z: null
+
+ $dumped = Yaml::dump([
+ 'foo' => 'bar', 'some foo' => 'some bar', 'x' => 3.14, 'y' => true, 'z' => null,
+ ], 2, 4, Yaml::DUMP_FORCE_DOUBLE_QUOTES_ON_VALUES);
+ // "foo": "bar", "some foo": "some bar", "x": 3.14, "y": true, "z": null
+
+.. versionadded:: 7.3
+
+ The ``Yaml::DUMP_FORCE_DOUBLE_QUOTES_ON_VALUES`` flag was introduced in Symfony 7.3.
+
+Dumping Collection of Maps
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When the YAML component dumps collections of maps, it uses a hyphen on a separate
+line as a delimiter:
+
+.. code-block:: yaml
+
+ planets:
+ -
+ name: Mercury
+ distance: 57910000
+ -
+ name: Jupiter
+ distance: 778500000
+
+To produce a more compact output where the delimiter is included within the map,
+use the ``Yaml::DUMP_COMPACT_NESTED_MAPPING`` flag:
+
+.. code-block:: yaml
+
+ planets:
+ - name: Mercury
+ distance: 57910000
+ - name: Jupiter
+ distance: 778500000
+
+.. versionadded:: 7.3
+
+ The ``Yaml::DUMP_COMPACT_NESTED_MAPPING`` flag was introduced in Symfony 7.3.
+
Syntax Validation
~~~~~~~~~~~~~~~~~
diff --git a/console/style.rst b/console/style.rst
index 0aaaa3f675e..e1e5df38ffe 100644
--- a/console/style.rst
+++ b/console/style.rst
@@ -169,6 +169,32 @@ Content Methods
styled according to the Symfony Style Guide, which allows you to use
features such as dynamically appending rows.
+:method:`Symfony\\Component\\Console\\Style\\SymfonyStyle::tree`
+ It displays the given nested array as a formatted directory/file tree
+ structure in the console output::
+
+ $io->tree([
+ 'src' => [
+ 'Controller' => [
+ 'DefaultController.php',
+ ],
+ 'Kernel.php',
+ ],
+ 'templates' => [
+ 'base.html.twig',
+ ],
+ ]);
+
+.. versionadded:: 7.3
+
+ The ``SymfonyStyle::tree()`` and the ``SymfonyStyle::createTree()`` methods
+ were introduced in Symfony 7.3.
+
+:method:`Symfony\\Component\\Console\\Style\\SymfonyStyle::createTree`
+ Creates an instance of :class:`Symfony\\Component\\Console\\Helper\\TreeHelper`
+ styled according to the Symfony Style Guide, which allows you to use
+ features such as dynamically nesting nodes.
+
:method:`Symfony\\Component\\Console\\Style\\SymfonyStyle::newLine`
It displays a blank line in the command output. Although it may seem useful,
most of the times you won't need it at all. The reason is that every helper
diff --git a/controller.rst b/controller.rst
index 3ce0a33cae9..05abdaee4ea 100644
--- a/controller.rst
+++ b/controller.rst
@@ -371,6 +371,11 @@ The ``MapQueryParameter`` attribute supports the following argument types:
* ``float``
* ``int``
* ``string``
+* Objects that extend :class:`Symfony\\Component\\Uid\\AbstractUid`
+
+.. versionadded:: 7.3
+
+ Support for ``AbstractUid`` objects was introduced in Symfony 7.3.
``#[MapQueryParameter]`` can take an optional argument called ``filter``. You can use the
`Validate Filters`_ constants defined in PHP::
@@ -452,6 +457,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::
diff --git a/controller/error_pages.rst b/controller/error_pages.rst
index fc36b88779a..96856764ece 100644
--- a/controller/error_pages.rst
+++ b/controller/error_pages.rst
@@ -336,3 +336,50 @@ time and again, you can have just one (or several) listeners deal with them.
your application (like :class:`Symfony\\Component\\Security\\Core\\Exception\\AccessDeniedException`)
and takes measures like redirecting the user to the login page, logging them
out and other things.
+
+Dumping Error Pages as Static HTML Files
+----------------------------------------
+
+.. versionadded:: 7.3
+
+ The feature to dump error pages into static HTML files was introduced in Symfony 7.3.
+
+If an error occurs before reaching your Symfony application, web servers display
+their own default error pages instead of your custom ones. Dumping your application's
+error pages to static HTML ensures users always see your defined pages and improves
+performance by allowing the server to deliver errors instantly without calling
+your application.
+
+Symfony provides the following command to turn your error pages into static HTML files:
+
+.. code-block:: terminal
+
+ # the first argument is the path where the HTML files are stored
+ $ APP_ENV=prod php bin/console error:dump var/cache/prod/error_pages/
+
+ # by default, it generates the pages of all 4xx and 5xx errors, but you can
+ # pass a list of HTTP status codes to only generate those
+ $ APP_ENV=prod php bin/console error:dump var/cache/prod/error_pages/ 401 403 404 500
+
+You must also configure your web server to use these generated pages. For example,
+if you use Nginx:
+
+.. code-block:: nginx
+
+ # /etc/nginx/conf.d/example.com.conf
+ server {
+ # Existing server configuration
+ # ...
+
+ # Serve static error pages
+ error_page 400 /error_pages/400.html;
+ error_page 401 /error_pages/401.html;
+ # ...
+ error_page 510 /error_pages/510.html;
+ error_page 511 /error_pages/511.html;
+
+ location ^~ /error_pages/ {
+ root /path/to/your/symfony/var/cache/error_pages;
+ internal; # prevent direct URL access
+ }
+ }
diff --git a/controller/upload_file.rst b/controller/upload_file.rst
index b3dc2d6ffd0..cff326a8e2b 100644
--- a/controller/upload_file.rst
+++ b/controller/upload_file.rst
@@ -75,14 +75,14 @@ so Symfony doesn't try to get/set its value from the related entity::
// unmapped fields can't define their validation using attributes
// in the associated entity, so you can use the PHP constraint classes
'constraints' => [
- new File([
- 'maxSize' => '1024k',
- 'mimeTypes' => [
+ new File(
+ maxSize: '1024k',
+ mimeTypes: [
'application/pdf',
'application/x-pdf',
],
- 'mimeTypesMessage' => 'Please upload a valid PDF document',
- ])
+ mimeTypesMessage: 'Please upload a valid PDF document',
+ )
],
])
// ...
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/form/form_customization.rst b/form/form_customization.rst
index 1c23601a883..dc09aefe77d 100644
--- a/form/form_customization.rst
+++ b/form/form_customization.rst
@@ -103,6 +103,7 @@ That's why Symfony provides other Twig form helpers that render the value of
each form field part without adding any HTML around it:
* ``field_name()``
+* ``field_id()``
* ``field_value()``
* ``field_label()``
* ``field_help()``
@@ -116,6 +117,7 @@ fields, so you no longer have to deal with form themes:
+.. versionadded:: 7.3
+
+ The ``field_id()`` helper was introduced in Symfony 7.3.
+
Form Rendering Variables
------------------------
diff --git a/form/without_class.rst b/form/without_class.rst
index 8b0af7cf23f..5fec7f3a663 100644
--- a/form/without_class.rst
+++ b/form/without_class.rst
@@ -96,12 +96,12 @@ but here's a short example::
{
$builder
->add('firstName', TextType::class, [
- 'constraints' => new Length(['min' => 3]),
+ 'constraints' => new Length(min: 3),
])
->add('lastName', TextType::class, [
'constraints' => [
new NotBlank(),
- new Length(['min' => 3]),
+ new Length(min: 3),
],
])
;
@@ -153,10 +153,10 @@ This can be done by setting the ``constraints`` option in the
$resolver->setDefaults([
'data_class' => null,
'constraints' => new Collection([
- 'firstName' => new Length(['min' => 3]),
+ 'firstName' => new Length(min: 3),
'lastName' => [
new NotBlank(),
- new Length(['min' => 3]),
+ new Length(min: 3),
],
]),
]);
diff --git a/frontend/asset_mapper.rst b/frontend/asset_mapper.rst
index 95b656f043d..8b27cd8ba16 100644
--- a/frontend/asset_mapper.rst
+++ b/frontend/asset_mapper.rst
@@ -215,6 +215,15 @@ to add any `npm package`_:
$ php bin/console importmap:require bootstrap
+.. tip::
+
+ Add the ``--dry-run`` option to simulate package installation without actually
+ making any changes (e.g. ``php bin/console importmap:require bootstrap --dry-run``)
+
+ .. versionadded:: 7.3
+
+ The ``--dry-run`` option was introduced in Symfony 7.3.
+
This adds the ``bootstrap`` package to your ``importmap.php`` file::
// importmap.php
@@ -656,7 +665,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 +715,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
--------------------------
@@ -1195,3 +1276,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
diff --git a/mailer.rst b/mailer.rst
index 1e333a8a289..a6b5eb819d8 100644
--- a/mailer.rst
+++ b/mailer.rst
@@ -100,6 +100,7 @@ via a third-party provider:
===================== =============================================== ===============
Service Install with Webhook support
===================== =============================================== ===============
+`AhaSend`_ ``composer require symfony/aha-send-mailer`` yes
`Amazon SES`_ ``composer require symfony/amazon-mailer``
`Azure`_ ``composer require symfony/azure-mailer``
`Brevo`_ ``composer require symfony/brevo-mailer`` yes
@@ -127,6 +128,10 @@ Service Install with Webhook su
The Mailomat, Mailtrap, Postal and Sweego integrations were introduced in Symfony 7.2.
+.. versionadded:: 7.3
+
+ The AhaSend integration was introduced in Symfony 7.3.
+
.. note::
As a convenience, Symfony also provides support for Gmail (``composer
@@ -175,6 +180,10 @@ party provider:
+------------------------+---------------------------------------------------------+
| Provider | Formats |
+========================+=========================================================+
+| `AhaSend`_ | - API ``ahasend+api://KEY@default`` |
+| | - HTTP n/a |
+| | - SMTP ``ahasend+smtp://USERNAME:PASSWORD@default`` |
++------------------------+---------------------------------------------------------+
| `Amazon SES`_ | - SMTP ``ses+smtp://USERNAME:PASSWORD@default`` |
| | - HTTP ``ses+https://ACCESS_KEY:SECRET_KEY@default`` |
| | - API ``ses+api://ACCESS_KEY:SECRET_KEY@default`` |
@@ -259,12 +268,6 @@ party provider:
you need to add the ``ping_threshold`` parameter to your ``MAILER_DSN`` with
a value lower than ``10``: ``ses+smtp://USERNAME:PASSWORD@default?ping_threshold=9``
-.. warning::
-
- If you send custom headers when using the `Amazon SES`_ transport (to receive
- them later via a webhook), make sure to use the ``ses+https`` provider because
- it's the only one that supports them.
-
.. note::
When using SMTP, the default timeout for sending a message before throwing an
@@ -331,6 +334,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, 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
+
+ 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 +365,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, 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
+
+ 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
~~~~~~~~~~~~~~~~~~~~~
@@ -395,6 +420,27 @@ setting the ``auto_tls`` option to ``false`` in the DSN::
This setting only works when the ``smtp://`` protocol is used.
+Binding to IPv4 or IPv6
+~~~~~~~~~~~~~~~~~~~~~~~
+
+.. versionadded:: 7.3
+
+ The option to bind to IPv4, or IPv6, or a specific IP address was introduced in Symfony 7.3.
+
+By default, the underlying ``SocketStream`` will bind to IPv4 or IPv6 based on the
+available interfaces. You can enforce binding to a specific protocol or IP address
+by using the ``source_ip`` option. To bind to IPv4, use::
+
+ $dsn = 'smtp://smtp.example.com?source_ip=0.0.0.0';
+
+As per RFC2732, IPv6 addresses must be enclosed in square brackets. To bind to IPv6, use::
+
+ $dsn = 'smtp://smtp.example.com?source_ip=[::]';
+
+.. note::
+
+ This option only works when using the ``smtp://`` protocol.
+
Overriding default SMTP authenticators
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -1344,6 +1390,81 @@ key but not a certificate::
->toArray()
);
+Signing Messages Globally
+.........................
+
+Instead of creating a signer instance for each email, you can configure a global
+signer that automatically applies to all outgoing messages. This approach
+minimizes repetition and centralizes your configuration for DKIM and S/MIME signing.
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/mailer.yaml
+ framework:
+ mailer:
+ dkim_signer:
+ key: 'file://%kernel.project_dir%/var/certificates/dkim.pem'
+ domain: 'symfony.com'
+ select: 's1'
+ smime_signer:
+ key: '%kernel.project_dir%/var/certificates/smime.key'
+ certificate: '%kernel.project_dir%/var/certificates/smime.crt'
+ passphrase: ''
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+
+ file://%kernel.project_dir%/var/certificates/dkim.pem
+ symfony.com
+ s1
+
+
+ %kernel.project_dir%/var/certificates/smime.pem
+ %kernel.project_dir%/var/certificates/smime.crt
+
+
+
+
+
+
+ .. code-block:: php
+
+ // config/packages/mailer.php
+ use Symfony\Config\FrameworkConfig;
+
+ return static function (FrameworkConfig $framework): void {
+ $mailer = $framework->mailer();
+ $mailer->dsn('%env(MAILER_DSN)%');
+ $mailer->dkimSigner()
+ ->key('file://%kernel.project_dir%/var/certificates/dkim.pem')
+ ->domain('symfony.com')
+ ->select('s1');
+
+ $mailer->smimeSigner()
+ ->key('%kernel.project_dir%/var/certificates/smime.key')
+ ->certificate('%kernel.project_dir%/var/certificates/smime.crt')
+ ->passphrase('')
+ ;
+ };
+
+.. versionadded:: 7.3
+
+ Global message signing was introduced in Symfony 7.3.
+
Encrypting Messages
~~~~~~~~~~~~~~~~~~~
@@ -1385,6 +1506,59 @@ and it will select the appropriate certificate depending on the ``To`` option::
$firstEncryptedEmail = $encrypter->encrypt($firstEmail);
$secondEncryptedEmail = $encrypter->encrypt($secondEmail);
+Encrypting Messages Globally
+............................
+
+Instead of creating a new encrypter for each email, you can configure a global S/MIME
+encrypter that automatically applies to all outgoing messages:
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/mailer.yaml
+ framework:
+ mailer:
+ smime_encrypter:
+ certificate: '%kernel.project_dir%/var/certificates/smime.crt'
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+
+ %kernel.project_dir%/var/certificates/smime.crt
+
+
+
+
+
+ .. code-block:: php
+
+ // config/packages/mailer.php
+ use Symfony\Config\FrameworkConfig;
+
+ return static function (FrameworkConfig $framework): void {
+ $mailer = $framework->mailer();
+ $mailer->smimeEncrypter()
+ ->certificate('%kernel.project_dir%/var/certificates/smime.crt')
+ ;
+ };
+
+.. versionadded:: 7.3
+
+ Global message encryption configuration was introduced in Symfony 7.3.
+
.. _multiple-email-transports:
Multiple Email Transports
@@ -2033,6 +2207,7 @@ the :class:`Symfony\\Bundle\\FrameworkBundle\\Test\\MailerAssertionsTrait`::
following the redirection and the message will be lost from the mailer event
handler.
+.. _`AhaSend`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Mailer/Bridge/AhaSend/README.md
.. _`Amazon SES`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Mailer/Bridge/Amazon/README.md
.. _`Azure`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Mailer/Bridge/Azure/README.md
.. _`App Password`: https://support.google.com/accounts/answer/185833
diff --git a/messenger.rst b/messenger.rst
index 484a5cbdaff..9078a500d11 100644
--- a/messenger.rst
+++ b/messenger.rst
@@ -555,7 +555,7 @@ the message from being redelivered until the worker completes processing it:
.. note::
- This option is only available for the following transports: Beanstalkd and AmazonSQS.
+ This option is only available for the following transports: Beanstalkd, AmazonSQS, Doctrine and Redis.
.. versionadded:: 7.2
@@ -849,7 +849,56 @@ message before terminating.
However, you might prefer to use different POSIX signals for graceful shutdown.
You can override default ones by setting the ``framework.messenger.stop_worker_on_signals``
-configuration option.
+configuration option:
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/messenger.yaml
+ framework:
+ messenger:
+ stop_worker_on_signals:
+ - SIGTERM
+ - SIGINT
+ - SIGUSR1
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+ SIGTERM
+ SIGINT
+ SIGUSR1
+
+
+
+
+ .. code-block:: php
+
+ // config/packages/messenger.php
+ use Symfony\Config\FrameworkConfig;
+
+ return static function (FrameworkConfig $framework): void {
+ $framework->messenger()
+ ->stopWorkerOnSignals(['SIGTERM', 'SIGINT', 'SIGUSR1']);
+ };
+
+.. versionadded:: 7.3
+
+ Support for signals plain names in configuration was introduced in Symfony 7.3.
+ Previously, you had to use the numeric values of signals as defined by the
+ ``pcntl`` extension's `predefined constants`_.
In some cases the ``SIGTERM`` signal is sent by Supervisor itself (e.g. stopping
a Docker container having Supervisor as its entrypoint). In these cases you
@@ -1263,6 +1312,9 @@ to retry them:
# remove all messages in the failure transport
$ php bin/console messenger:failed:remove --all
+ # remove only App\Message\MyMessage messages
+ $ php bin/console messenger:failed:remove --class-filter='App\Message\MyMessage'
+
If the message fails again, it will be re-sent back to the failure transport
due to the normal :ref:`retry rules `. Once the max
retry has been hit, the message will be discarded permanently.
@@ -1272,6 +1324,11 @@ retry has been hit, the message will be discarded permanently.
The option to skip a message in the ``messenger:failed:retry`` command was
introduced in Symfony 7.2
+.. versionadded:: 7.3
+
+ The option to filter by a message class in the ``messenger:failed:remove`` command was
+ introduced in Symfony 7.3
+
Multiple Failed Transports
~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -1696,6 +1753,13 @@ in the table.
The length of time to wait for a response when calling
``PDO::pgsqlGetNotify``, in milliseconds.
+The Doctrine transport supports the ``--keepalive`` option by periodically updating
+the ``delivered_at`` timestamp to prevent the message from being redelivered.
+
+.. versionadded:: 7.3
+
+ Keepalive support was introduced in Symfony 7.3.
+
Beanstalkd Transport
~~~~~~~~~~~~~~~~~~~~
@@ -1718,8 +1782,13 @@ The Beanstalkd transport DSN may looks like this:
The transport has a number of options:
-``tube_name`` (default: ``default``)
- Name of the queue
+``bury_on_reject`` (default: ``false``)
+ When set to ``true``, rejected messages are placed into a "buried" state
+ in Beanstalkd instead of being deleted.
+
+ .. versionadded:: 7.3
+
+ The ``bury_on_reject`` option was introduced in Symfony 7.3.
``timeout`` (default: ``0``)
Message reservation timeout - in seconds. 0 will cause the server to
@@ -1729,6 +1798,9 @@ The transport has a number of options:
The message time to run before it is put back in the ready queue - in
seconds.
+``tube_name`` (default: ``default``)
+ Name of the queue
+
The Beanstalkd transport supports the ``--keepalive`` option by using Beanstalkd's
``touch`` command to periodically reset the job's ``ttr``.
@@ -1736,6 +1808,23 @@ The Beanstalkd transport supports the ``--keepalive`` option by using Beanstalkd
Keepalive support was introduced in Symfony 7.2.
+The Beanstalkd transport lets you set the priority of the messages being dispatched.
+Use the :class:`Symfony\\Component\\Messenger\\Bridge\\Beanstalkd\\Transport\\BeanstalkdPriorityStamp`
+and pass a number to specify the priority (default = ``1024``; lower numbers mean higher priority)::
+
+ use App\Message\SomeMessage;
+ use Symfony\Component\Messenger\Stamp\BeanstalkdPriorityStamp;
+
+ $this->bus->dispatch(new SomeMessage('some data'), [
+ // 0 = highest priority
+ // 2**32 - 1 = lowest priority
+ new BeanstalkdPriorityStamp(0),
+ ]);
+
+.. versionadded:: 7.3
+
+ ``BeanstalkdPriorityStamp`` support was introduced in Symfony 7.3.
+
.. _messenger-redis-transport:
Redis Transport
@@ -1882,6 +1971,13 @@ under the transport in ``messenger.yaml``:
in your case) to avoid memory leaks. Otherwise, all messages will remain
forever in Redis.
+The Redis transport supports the ``--keepalive`` option by using Redis's ``XCLAIM``
+command to periodically reset the message's idle time to zero.
+
+.. versionadded:: 7.3
+
+ Keepalive support was introduced in Symfony 7.3.
+
In Memory Transport
~~~~~~~~~~~~~~~~~~~
@@ -2024,6 +2120,12 @@ The transport has a number of options:
``queue_name`` (default: ``messages``)
Name of the queue
+``queue_attributes``
+ Attributes of a queue as per `SQS CreateQueue API`_. Array of strings indexed by keys of ``AsyncAws\Sqs\Enum\QueueAttributeName``.
+
+``queue_tags``
+ Cost allocation tags of a queue as per `SQS CreateQueue API`_. Array of strings indexed by strings.
+
``region`` (default: ``eu-west-1``)
Name of the AWS region
@@ -2039,6 +2141,10 @@ The transport has a number of options:
``wait_time`` (default: ``20``)
`Long polling`_ duration in seconds
+.. versionadded:: 7.3
+
+ The ``queue_attributes`` and ``queue_tags`` options were introduced in Symfony 7.3.
+
.. note::
The ``wait_time`` parameter defines the maximum duration Amazon SQS should
@@ -2161,6 +2267,22 @@ on a case-by-case basis via the :class:`Symfony\\Component\\Messenger\\Stamp\\Se
provides that control. See `SymfonyCasts' message serializer tutorial`_ for
details.
+Closing Connections
+~~~~~~~~~~~~~~~~~~~
+
+When using a transport that requires a connection, you can close it by calling the
+:method:`Symfony\\Component\\Messenger\\Transport\\CloseableTransportInterface::close`
+method to free up resources in long-running processes.
+
+This interface is implemented by the following transports: AmazonSqs, Amqp, and Redis.
+If you need to close a Doctrine connection, you can do so
+:ref:`using middleware `.
+
+.. versionadded:: 7.3
+
+ The ``CloseableTransportInterface`` and its ``close()`` method were introduced
+ in Symfony 7.3.
+
Running Commands And External Processes
---------------------------------------
@@ -2216,8 +2338,9 @@ will take care of creating a new process with the parameters you passed::
class CleanUpService
{
- public function __construct(private readonly MessageBusInterface $bus)
- {
+ public function __construct(
+ private readonly MessageBusInterface $bus,
+ ) {
}
public function cleanUp(): void
@@ -2228,6 +2351,34 @@ will take care of creating a new process with the parameters you passed::
}
}
+If you want to use shell features such as redirections or pipes, use the static
+factory :method:Symfony\\Component\\Process\\Messenger\\RunProcessMessage::fromShellCommandline::
+
+ use Symfony\Component\Messenger\MessageBusInterface;
+ use Symfony\Component\Process\Messenger\RunProcessMessage;
+
+ class CleanUpService
+ {
+ public function __construct(
+ private readonly MessageBusInterface $bus,
+ ) {
+ }
+
+ public function cleanUp(): void
+ {
+ $this->bus->dispatch(RunProcessMessage::fromShellCommandline('echo "Hello World" > var/log/hello.txt'));
+
+ // ...
+ }
+ }
+
+For more information, read the documentation about
+:ref:`using features from the OS shell `.
+
+.. versionadded:: 7.3
+
+ The ``RunProcessMessage::fromShellCommandline()`` method was introduced in Symfony 7.3.
+
Once handled, the handler will return a
:class:`Symfony\\Component\\Process\\Messenger\\RunProcessContext` which
contains many useful information such as the exit code or the output of the
@@ -2374,6 +2525,15 @@ wherever you need a query bus behavior instead of the ``MessageBusInterface``::
}
}
+You can also add new stamps when handling a message; they will be appended
+to the existing ones::
+
+ $this->handle(new SomeMessage($data), [new SomeStamp(), new AnotherStamp()]);
+
+.. versionadded:: 7.3
+
+ The ``$stamps`` parameter of the ``handle()`` method was introduced in Symfony 7.3.
+
Customizing Handlers
--------------------
@@ -3599,3 +3759,5 @@ Learn more
.. _`high connection churn`: https://www.rabbitmq.com/connections.html#high-connection-churn
.. _`article about CQRS`: https://martinfowler.com/bliki/CQRS.html
.. _`SSL context options`: https://php.net/context.ssl
+.. _`predefined constants`: https://www.php.net/pcntl.constants
+.. _`SQS CreateQueue API`: https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_CreateQueue.html
diff --git a/notifier.rst b/notifier.rst
index d2dc225f4f0..49a1c2d533b 100644
--- a/notifier.rst
+++ b/notifier.rst
@@ -68,6 +68,7 @@ Service
`AllMySms`_ **Install**: ``composer require symfony/all-my-sms-notifier`` \
**DSN**: ``allmysms://LOGIN:APIKEY@default?from=FROM`` \
**Webhook support**: No
+ **Extra properties in SentMessage**: ``nbSms``, ``balance``, ``cost``
`AmazonSns`_ **Install**: ``composer require symfony/amazon-sns-notifier`` \
**DSN**: ``sns://ACCESS_KEY:SECRET_KEY@default?region=REGION`` \
**Webhook support**: No
@@ -76,7 +77,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
@@ -139,6 +140,7 @@ Service
`OvhCloud`_ **Install**: ``composer require symfony/ovh-cloud-notifier`` \
**DSN**: ``ovhcloud://APPLICATION_KEY:APPLICATION_SECRET@default?consumer_key=CONSUMER_KEY&service_name=SERVICE_NAME`` \
**Webhook support**: No
+ **Extra properties in SentMessage**:: ``totalCreditsRemoved``
`Plivo`_ **Install**: ``composer require symfony/plivo-notifier`` \
**DSN**: ``plivo://AUTH_ID:AUTH_TOKEN@default?from=FROM`` \
**Webhook support**: No
@@ -177,7 +179,7 @@ Service
**Webhook support**: No
`Smsbox`_ **Install**: ``composer require symfony/smsbox-notifier`` \
**DSN**: ``smsbox://APIKEY@default?mode=MODE&strategy=STRATEGY&sender=SENDER`` \
- **Webhook support**: No
+ **Webhook support**: Yes
`SmsBiuras`_ **Install**: ``composer require symfony/sms-biuras-notifier`` \
**DSN**: ``smsbiuras://UID:API_KEY@default?from=FROM&test_mode=0`` \
**Webhook support**: No
@@ -236,6 +238,12 @@ 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.
+ The extra properties in ``SentMessage`` for ``AllMySms`` and ``OvhCloud``
+ providers were introduced in Symfony 7.3 too.
+
.. deprecated:: 7.1
The `Sms77`_ integration is deprecated since
@@ -357,6 +365,7 @@ Service
**DSN**: ``sns://ACCESS_KEY:SECRET_KEY@default?region=REGION``
`Bluesky`_ **Install**: ``composer require symfony/bluesky-notifier`` \
**DSN**: ``bluesky://USERNAME:PASSWORD@default``
+ **Extra properties in SentMessage**: ``cid``
`Chatwork`_ **Install**: ``composer require symfony/chatwork-notifier`` \
**DSN**: ``chatwork://API_TOKEN@default?room_id=ID``
`Discord`_ **Install**: ``composer require symfony/discord-notifier`` \
@@ -375,6 +384,8 @@ Service
**DSN**: ``linkedin://TOKEN:USER_ID@default``
`Mastodon`_ **Install**: ``composer require symfony/mastodon-notifier`` \
**DSN**: ``mastodon://ACCESS_TOKEN@HOST``
+`Matrix`_ **Install**: ``composer require symfony/matrix-notifier`` \
+ **DSN**: ``matrix://HOST:PORT/?accessToken=ACCESSTOKEN&ssl=SSL``
`Mattermost`_ **Install**: ``composer require symfony/mattermost-notifier`` \
**DSN**: ``mattermost://ACCESS_TOKEN@HOST/PATH?channel=CHANNEL``
`Mercure`_ **Install**: ``composer require symfony/mercure-notifier`` \
@@ -408,6 +419,10 @@ Service
The ``Gitter`` integration was removed in Symfony 7.2 because that service
no longer provides an API.
+.. versionadded:: 7.3
+
+ The ``Matrix`` integration was introduced in Symfony 7.3.
+
.. warning::
By default, if you have the :doc:`Messenger component ` installed,
@@ -1283,6 +1298,7 @@ is dispatched. Listeners receive a
.. _`LOX24`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/Lox24/README.md
.. _`Mailjet`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/Mailjet/README.md
.. _`Mastodon`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/Mastodon/README.md
+.. _`Matrix`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/Matrix/README.md
.. _`Mattermost`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/Mattermost/README.md
.. _`Mercure`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/Mercure/README.md
.. _`MessageBird`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/MessageBird/README.md
diff --git a/profiler.rst b/profiler.rst
index 57d412472ba..7fc97c8ee33 100644
--- a/profiler.rst
+++ b/profiler.rst
@@ -217,9 +217,48 @@ user by dynamically rewriting the current page rather than loading entire new
pages from a server.
By default, the debug toolbar displays the information of the initial page load
-and doesn't refresh after each AJAX request. However, you can set the
-``Symfony-Debug-Toolbar-Replace`` header to a value of ``'1'`` in the response to
-the AJAX request to force the refresh of the toolbar::
+and doesn't refresh after each AJAX request. However, you can configure the
+toolbar to be refreshed after each AJAX request by enabling ``ajax_replace`` in the
+``web_profiler`` configuration:
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/web_profiler.yaml
+ web_profiler:
+ toolbar:
+ ajax_replace: true
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+ .. code-block:: php
+
+ // config/packages/web_profiler.php
+ use Symfony\Config\WebProfilerConfig;
+
+ return static function (WebProfilerConfig $profiler): void {
+ $profiler->toolbar()
+ ->ajaxReplace(true);
+ };
+
+If you need a more sophisticated solution, you can set the
+``Symfony-Debug-Toolbar-Replace`` header to a value of ``'1'`` in the response
+yourself::
$response->headers->set('Symfony-Debug-Toolbar-Replace', '1');
@@ -228,31 +267,21 @@ production. To do that, create an :doc:`event subscriber `
and listen to the :ref:`kernel.response `
event::
+ use Symfony\Component\DependencyInjection\Attribute\When;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\KernelInterface;
// ...
+ #[When(env: 'dev')]
class MySubscriber implements EventSubscriberInterface
{
- public function __construct(
- private KernelInterface $kernel,
- ) {
- }
-
// ...
public function onKernelResponse(ResponseEvent $event): void
{
- if (!$this->kernel->isDebug()) {
- return;
- }
-
- $request = $event->getRequest();
- if (!$request->isXmlHttpRequest()) {
- return;
- }
+ // Your custom logic here
$response = $event->getResponse();
$response->headers->set('Symfony-Debug-Toolbar-Replace', '1');
diff --git a/rate_limiter.rst b/rate_limiter.rst
index 6c158ee52d0..3a517c37bd4 100644
--- a/rate_limiter.rst
+++ b/rate_limiter.rst
@@ -230,6 +230,12 @@ prevents that number from being higher than 5,000).
Rate Limiting in Action
-----------------------
+.. versionadded:: 7.3
+
+ :class:`Symfony\\Component\\RateLimiter\\RateLimiterFactoryInterface` was
+ added and should now be used for autowiring instead of
+ :class:`Symfony\\Component\\RateLimiter\\RateLimiterFactory`.
+
After having installed and configured the rate limiter, inject it in any service
or controller and call the ``consume()`` method to try to consume a given number
of tokens. For example, this controller uses the previous rate limiter to control
@@ -242,13 +248,13 @@ the number of requests to the API::
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException;
- use Symfony\Component\RateLimiter\RateLimiterFactory;
+ use Symfony\Component\RateLimiter\RateLimiterFactoryInterface;
class ApiController extends AbstractController
{
// if you're using service autowiring, the variable name must be:
// "rate limiter name" (in camelCase) + "Limiter" suffix
- public function index(Request $request, RateLimiterFactory $anonymousApiLimiter): Response
+ public function index(Request $request, RateLimiterFactoryInterface $anonymousApiLimiter): Response
{
// create a limiter based on a unique identifier of the client
// (e.g. the client's IP address, a username/email, an API key, etc.)
@@ -291,11 +297,11 @@ using the ``reserve()`` method::
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
- use Symfony\Component\RateLimiter\RateLimiterFactory;
+ use Symfony\Component\RateLimiter\RateLimiterFactoryInterface;
class ApiController extends AbstractController
{
- public function registerUser(Request $request, RateLimiterFactory $authenticatedApiLimiter): Response
+ public function registerUser(Request $request, RateLimiterFactoryInterface $authenticatedApiLimiter): Response
{
$apiKey = $request->headers->get('apikey');
$limiter = $authenticatedApiLimiter->create($apiKey);
@@ -350,11 +356,11 @@ the :class:`Symfony\\Component\\RateLimiter\\Reservation` object returned by the
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
- use Symfony\Component\RateLimiter\RateLimiterFactory;
+ use Symfony\Component\RateLimiter\RateLimiterFactoryInterface;
class ApiController extends AbstractController
{
- public function index(Request $request, RateLimiterFactory $anonymousApiLimiter): Response
+ public function index(Request $request, RateLimiterFactoryInterface $anonymousApiLimiter): Response
{
$limiter = $anonymousApiLimiter->create($request->getClientIp());
$limit = $limiter->consume();
@@ -461,9 +467,10 @@ simultaneous requests (e.g. three servers of a company hitting your API at the
same time). Rate limiters use :doc:`locks ` to protect their operations
against these race conditions.
-By default, Symfony uses the global lock configured by ``framework.lock``, but
-you can use a specific :ref:`named lock ` via the
-``lock_factory`` option (or none at all):
+By default, if the :doc:`lock ` component is installed, Symfony uses the
+global lock configured by ``framework.lock``, but you can use a specific
+:ref:`named lock ` via the ``lock_factory`` option (or none
+at all):
.. configuration-block::
@@ -534,6 +541,129 @@ you can use a specific :ref:`named lock ` via the
;
};
+.. versionadded:: 7.3
+
+ Before Symfony 7.3, configuring a rate limiter and using the default configured
+ lock factory (``lock.factory``) failed if the Symfony Lock component was not
+ installed in the application.
+
+Compound Rate Limiter
+---------------------
+
+.. versionadded:: 7.3
+
+ Support for configuring compound rate limiters was introduced in Symfony 7.3.
+
+You can configure multiple rate limiters to work together:
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/rate_limiter.yaml
+ framework:
+ rate_limiter:
+ two_per_minute:
+ policy: 'fixed_window'
+ limit: 2
+ interval: '1 minute'
+ five_per_hour:
+ policy: 'fixed_window'
+ limit: 5
+ interval: '1 hour'
+ contact_form:
+ policy: 'compound'
+ limiters: [two_per_minute, five_per_hour]
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+
+
+ two_per_minute
+ five_per_hour
+
+
+
+
+
+ .. code-block:: php
+
+ // config/packages/rate_limiter.php
+ use Symfony\Config\FrameworkConfig;
+
+ return static function (FrameworkConfig $framework): void {
+ $framework->rateLimiter()
+ ->limiter('two_per_minute')
+ ->policy('fixed_window')
+ ->limit(2)
+ ->interval('1 minute')
+ ;
+
+ $framework->rateLimiter()
+ ->limiter('two_per_minute')
+ ->policy('fixed_window')
+ ->limit(5)
+ ->interval('1 hour')
+ ;
+
+ $framework->rateLimiter()
+ ->limiter('contact_form')
+ ->policy('compound')
+ ->limiters(['two_per_minute', 'five_per_hour'])
+ ;
+ };
+
+Then, inject and use as normal::
+
+ // src/Controller/ContactController.php
+ namespace App\Controller;
+
+ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+ use Symfony\Component\HttpFoundation\Request;
+ use Symfony\Component\HttpFoundation\Response;
+ use Symfony\Component\RateLimiter\RateLimiterFactory;
+
+ class ContactController extends AbstractController
+ {
+ public function registerUser(Request $request, RateLimiterFactoryInterface $contactFormLimiter): Response
+ {
+ $limiter = $contactFormLimiter->create($request->getClientIp());
+
+ if (false === $limiter->consume(1)->isAccepted()) {
+ // either of the two limiters has been reached
+ }
+
+ // ...
+ }
+
+ // ...
+ }
+
.. _`DoS attacks`: https://cheatsheetseries.owasp.org/cheatsheets/Denial_of_Service_Cheat_Sheet.html
.. _`Apache mod_ratelimit`: https://httpd.apache.org/docs/current/mod/mod_ratelimit.html
.. _`NGINX rate limiting`: https://www.nginx.com/blog/rate-limiting-nginx/
diff --git a/reference/configuration/framework.rst b/reference/configuration/framework.rst
index ba1ac384bec..56a7dfe54b1 100644
--- a/reference/configuration/framework.rst
+++ b/reference/configuration/framework.rst
@@ -1025,8 +1025,8 @@ exceptions
**type**: ``array``
-Defines the :ref:`log level ` and HTTP status code applied to the
-exceptions that match the given exception class:
+Defines the :ref:`log level `, :ref:`log channel `
+and HTTP status code applied to the exceptions that match the given exception class:
.. configuration-block::
@@ -1038,6 +1038,7 @@ exceptions that match the given exception class:
Symfony\Component\HttpKernel\Exception\BadRequestHttpException:
log_level: 'debug'
status_code: 422
+ log_channel: 'custom_channel'
.. code-block:: xml
@@ -1055,6 +1056,7 @@ exceptions that match the given exception class:
class="Symfony\Component\HttpKernel\Exception\BadRequestHttpException"
log-level="debug"
status-code="422"
+ log-channel="custom_channel"
/>
@@ -1070,9 +1072,14 @@ exceptions that match the given exception class:
$framework->exception(BadRequestHttpException::class)
->logLevel('debug')
->statusCode(422)
+ ->logChannel('custom_channel')
;
};
+.. versionadded:: 7.3
+
+ The ``log_channel`` option was introduced in Symfony 7.3.
+
The order in which you configure exceptions is important because Symfony will
use the configuration of the first exception that matches ``instanceof``:
@@ -2347,12 +2354,16 @@ Combine it with the ``collect`` option to enable/disable the profiler on demand:
collect_serializer_data
.......................
-**type**: ``boolean`` **default**: ``false``
+**type**: ``boolean`` **default**: ``true``
-Set this option to ``true`` to enable the serializer data collector and its
-profiler panel. When this option is ``true``, all normalizers and encoders are
+When this option is ``true``, all normalizers and encoders are
decorated by traceable implementations that collect profiling information about them.
+.. deprecated:: 7.3
+
+ Setting the ``collect_serializer_data`` option to ``false`` is deprecated
+ since Symfony 7.3.
+
.. _profiler-dsn:
dsn
@@ -2453,6 +2464,18 @@ enabled
**type**: ``boolean`` **default**: ``true`` or ``false`` depending on your installation
+with_constructor_extractor
+..........................
+
+**type**: ``boolean`` **default**: ``false``
+
+Configures the ``property_info`` service to extract property information from the constructor arguments
+using the :ref:`ConstructorExtractor `.
+
+.. versionadded:: 7.3
+
+ The ``with_constructor_extractor`` option was introduced in Symfony 7.3.
+
rate_limiter
~~~~~~~~~~~~
diff --git a/reference/configuration/web_profiler.rst b/reference/configuration/web_profiler.rst
index de706c73fef..c3b57d37c55 100644
--- a/reference/configuration/web_profiler.rst
+++ b/reference/configuration/web_profiler.rst
@@ -53,8 +53,21 @@ on the given link to perform the redirect.
toolbar
~~~~~~~
+enabled
+.......
**type**: ``boolean`` **default**: ``false``
It enables and disables the toolbar entirely. Usually you set this to ``true``
in the ``dev`` and ``test`` environments and to ``false`` in the ``prod``
environment.
+
+ajax_replace
+............
+**type**: ``boolean`` **default**: ``false``
+
+If you set this option to ``true``, the toolbar is replaced on AJAX requests.
+This only works in combination with an enabled toolbar.
+
+.. versionadded:: 7.3
+
+ The ``ajax_replace`` configuration option was introduced in Symfony 7.3.
diff --git a/reference/constraints/All.rst b/reference/constraints/All.rst
index 3aa05b1d2d0..43ff4d6ac9d 100644
--- a/reference/constraints/All.rst
+++ b/reference/constraints/All.rst
@@ -79,12 +79,12 @@ entry in that array:
{
public static function loadValidatorMetadata(ClassMetadata $metadata): void
{
- $metadata->addPropertyConstraint('favoriteColors', new Assert\All([
- 'constraints' => [
+ $metadata->addPropertyConstraint('favoriteColors', new Assert\All(
+ constraints: [
new Assert\NotBlank(),
- new Assert\Length(['min' => 5]),
+ new Assert\Length(min: 5),
],
- ]));
+ ));
}
}
@@ -97,7 +97,7 @@ Options
``constraints``
~~~~~~~~~~~~~~~
-**type**: ``array`` [:ref:`default option `]
+**type**: ``array``
This required option is the array of validation constraints that you want
to apply to each element of the underlying array.
diff --git a/reference/constraints/AtLeastOneOf.rst b/reference/constraints/AtLeastOneOf.rst
index 0a6ab618aa5..fecbe617f5a 100644
--- a/reference/constraints/AtLeastOneOf.rst
+++ b/reference/constraints/AtLeastOneOf.rst
@@ -115,23 +115,23 @@ The following constraints ensure that:
{
public static function loadValidatorMetadata(ClassMetadata $metadata): void
{
- $metadata->addPropertyConstraint('password', new Assert\AtLeastOneOf([
- 'constraints' => [
- new Assert\Regex(['pattern' => '/#/']),
- new Assert\Length(['min' => 10]),
+ $metadata->addPropertyConstraint('password', new Assert\AtLeastOneOf(
+ constraints: [
+ new Assert\Regex(pattern: '/#/'),
+ new Assert\Length(min: 10),
],
- ]));
+ ));
- $metadata->addPropertyConstraint('grades', new Assert\AtLeastOneOf([
- 'constraints' => [
- new Assert\Count(['min' => 3]),
- new Assert\All([
- 'constraints' => [
+ $metadata->addPropertyConstraint('grades', new Assert\AtLeastOneOf(
+ constraints: [
+ new Assert\Count(min: 3),
+ new Assert\All(
+ constraints: [
new Assert\GreaterThanOrEqual(5),
],
- ]),
+ ),
],
- ]));
+ ));
}
}
@@ -141,7 +141,7 @@ Options
constraints
~~~~~~~~~~~
-**type**: ``array`` [:ref:`default option `]
+**type**: ``array``
This required option is the array of validation constraints from which at least one of
has to be satisfied in order for the validation to succeed.
diff --git a/reference/constraints/Callback.rst b/reference/constraints/Callback.rst
index f4c78a9642a..017b9435cff 100644
--- a/reference/constraints/Callback.rst
+++ b/reference/constraints/Callback.rst
@@ -259,7 +259,7 @@ Options
``callback``
~~~~~~~~~~~~
-**type**: ``string``, ``array`` or ``Closure`` [:ref:`default option `]
+**type**: ``string``, ``array`` or ``Closure``
The callback option accepts three different formats for specifying the
callback method:
diff --git a/reference/constraints/CardScheme.rst b/reference/constraints/CardScheme.rst
index 6e98e6fab98..a2ed9c568c3 100644
--- a/reference/constraints/CardScheme.rst
+++ b/reference/constraints/CardScheme.rst
@@ -77,12 +77,12 @@ on an object that will contain a credit card number.
{
public static function loadValidatorMetadata(ClassMetadata $metadata): void
{
- $metadata->addPropertyConstraint('cardNumber', new Assert\CardScheme([
- 'schemes' => [
+ $metadata->addPropertyConstraint('cardNumber', new Assert\CardScheme(
+ schemes: [
Assert\CardScheme::VISA,
],
- 'message' => 'Your credit card number is invalid.',
- ]));
+ message: 'Your credit card number is invalid.',
+ ));
}
}
@@ -114,7 +114,7 @@ Parameter Description
``schemes``
~~~~~~~~~~~
-**type**: ``mixed`` [:ref:`default option `]
+**type**: ``mixed``
This option is required and represents the name of the number scheme used
to validate the credit card number, it can either be a string or an array.
diff --git a/reference/constraints/Choice.rst b/reference/constraints/Choice.rst
index 5a9c365be37..cdf6b6e47fd 100644
--- a/reference/constraints/Choice.rst
+++ b/reference/constraints/Choice.rst
@@ -100,10 +100,10 @@ If your valid choice list is simple, you can pass them in directly via the
new Assert\Choice(['New York', 'Berlin', 'Tokyo'])
);
- $metadata->addPropertyConstraint('genre', new Assert\Choice([
- 'choices' => ['fiction', 'non-fiction'],
- 'message' => 'Choose a valid genre.',
- ]));
+ $metadata->addPropertyConstraint('genre', new Assert\Choice(
+ choices: ['fiction', 'non-fiction'],
+ message: 'Choose a valid genre.',
+ ));
}
}
@@ -182,9 +182,9 @@ constraint.
public static function loadValidatorMetadata(ClassMetadata $metadata): void
{
- $metadata->addPropertyConstraint('genre', new Assert\Choice([
- 'callback' => 'getGenres',
- ]));
+ $metadata->addPropertyConstraint('genre', new Assert\Choice(
+ callback: 'getGenres',
+ ));
}
}
@@ -250,9 +250,9 @@ you can pass the class name and the method as an array.
public static function loadValidatorMetadata(ClassMetadata $metadata): void
{
- $metadata->addPropertyConstraint('genre', new Assert\Choice([
- 'callback' => [Genre::class, 'getGenres'],
- ]));
+ $metadata->addPropertyConstraint('genre', new Assert\Choice(
+ callback: [Genre::class, 'getGenres'],
+ ));
}
}
@@ -271,7 +271,7 @@ to return the choices array. See
``choices``
~~~~~~~~~~~
-**type**: ``array`` [:ref:`default option `]
+**type**: ``array``
A required option (unless `callback`_ is specified) - this is the array
of options that should be considered in the valid set. The input value
diff --git a/reference/constraints/Collection.rst b/reference/constraints/Collection.rst
index 2d16d201b17..c35a0103581 100644
--- a/reference/constraints/Collection.rst
+++ b/reference/constraints/Collection.rst
@@ -139,8 +139,8 @@ following:
public static function loadValidatorMetadata(ClassMetadata $metadata): void
{
- $metadata->addPropertyConstraint('profileData', new Assert\Collection([
- 'fields' => [
+ $metadata->addPropertyConstraint('profileData', new Assert\Collection(
+ fields: [
'personal_email' => new Assert\Email(),
'short_bio' => [
new Assert\NotBlank(),
@@ -150,8 +150,8 @@ following:
]),
],
],
- 'allowMissingFields' => true,
- ]));
+ allowMissingFields: true,
+ ));
}
}
@@ -267,15 +267,15 @@ you can do the following:
public static function loadValidatorMetadata(ClassMetadata $metadata): void
{
- $metadata->addPropertyConstraint('profileData', new Assert\Collection([
- 'fields' => [
+ $metadata->addPropertyConstraint('profileData', new Assert\Collection(
+ fields: [
'personal_email' => new Assert\Required([
new Assert\NotBlank(),
new Assert\Email(),
]),
'alternate_email' => new Assert\Optional(new Assert\Email()),
],
- ]));
+ ));
}
}
@@ -291,28 +291,28 @@ groups. Take the following example::
use Symfony\Component\Validator\Constraints as Assert;
- $constraint = new Assert\Collection([
- 'fields' => [
+ $constraint = new Assert\Collection(
+ fields: [
'name' => new Assert\NotBlank(['groups' => 'basic']),
'email' => new Assert\NotBlank(['groups' => 'contact']),
],
- ]);
+ );
This will result in the following configuration::
- $constraint = new Assert\Collection([
- 'fields' => [
- 'name' => new Assert\Required([
- 'constraints' => new Assert\NotBlank(['groups' => 'basic']),
- 'groups' => ['basic', 'strict'],
- ]),
- 'email' => new Assert\Required([
- "constraints" => new Assert\NotBlank(['groups' => 'contact']),
- 'groups' => ['basic', 'strict'],
- ]),
+ $constraint = new Assert\Collection(
+ fields: [
+ 'name' => new Assert\Required(
+ constraints: new Assert\NotBlank(groups: ['basic']),
+ groups: ['basic', 'strict'],
+ ),
+ 'email' => new Assert\Required(
+ constraints: new Assert\NotBlank(groups: ['contact']),
+ groups: ['basic', 'strict'],
+ ),
],
- 'groups' => ['basic', 'strict'],
- ]);
+ groups: ['basic', 'strict'],
+ );
The default ``allowMissingFields`` option requires the fields in all groups.
So when validating in ``contact`` group, ``$name`` can be empty but the key is
@@ -360,7 +360,7 @@ Parameter Description
``fields``
~~~~~~~~~~
-**type**: ``array`` [:ref:`default option `]
+**type**: ``array``
This option is required and is an associative array defining all of the
keys in the collection and, for each key, exactly which validator(s) should
diff --git a/reference/constraints/Compound.rst b/reference/constraints/Compound.rst
index 0d0dc933ae0..4d2c7743176 100644
--- a/reference/constraints/Compound.rst
+++ b/reference/constraints/Compound.rst
@@ -35,9 +35,9 @@ you can create your own named set or requirements to be reused consistently ever
return [
new Assert\NotBlank(),
new Assert\Type('string'),
- new Assert\Length(['min' => 12]),
+ new Assert\Length(min: 12),
new Assert\NotCompromisedPassword(),
- new Assert\PasswordStrength(['minScore' => 4]),
+ new Assert\PasswordStrength(minScore: 4),
];
}
}
diff --git a/reference/constraints/Count.rst b/reference/constraints/Count.rst
index 0bf40aca8e9..d33c54c0812 100644
--- a/reference/constraints/Count.rst
+++ b/reference/constraints/Count.rst
@@ -82,12 +82,12 @@ you might add the following:
public static function loadValidatorMetadata(ClassMetadata $metadata): void
{
- $metadata->addPropertyConstraint('emails', new Assert\Count([
- 'min' => 1,
- 'max' => 5,
- 'minMessage' => 'You must specify at least one email',
- 'maxMessage' => 'You cannot specify more than {{ limit }} emails',
- ]));
+ $metadata->addPropertyConstraint('emails', new Assert\Count(
+ min: 1,
+ max: 5,
+ minMessage: 'You must specify at least one email',
+ maxMessage: 'You cannot specify more than {{ limit }} emails',
+ ));
}
}
diff --git a/reference/constraints/CssColor.rst b/reference/constraints/CssColor.rst
index fbbc982087d..b9c78ec25ac 100644
--- a/reference/constraints/CssColor.rst
+++ b/reference/constraints/CssColor.rst
@@ -110,15 +110,15 @@ the named CSS colors:
{
$metadata->addPropertyConstraint('defaultColor', new Assert\CssColor());
- $metadata->addPropertyConstraint('accentColor', new Assert\CssColor([
- 'formats' => Assert\CssColor::HEX_LONG,
- 'message' => 'The accent color must be a 6-character hexadecimal color.',
- ]));
-
- $metadata->addPropertyConstraint('currentColor', new Assert\CssColor([
- 'formats' => [Assert\CssColor::BASIC_NAMED_COLORS, Assert\CssColor::EXTENDED_NAMED_COLORS],
- 'message' => 'The color "{{ value }}" is not a valid CSS color name.',
- ]));
+ $metadata->addPropertyConstraint('accentColor', new Assert\CssColor(
+ formats: Assert\CssColor::HEX_LONG,
+ message: 'The accent color must be a 6-character hexadecimal color.',
+ ));
+
+ $metadata->addPropertyConstraint('currentColor', new Assert\CssColor(
+ formats: [Assert\CssColor::BASIC_NAMED_COLORS, Assert\CssColor::EXTENDED_NAMED_COLORS],
+ message: 'The color "{{ value }}" is not a valid CSS color name.',
+ ));
}
}
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
diff --git a/reference/constraints/DivisibleBy.rst b/reference/constraints/DivisibleBy.rst
index dd90ad9a0fd..23b36023cff 100644
--- a/reference/constraints/DivisibleBy.rst
+++ b/reference/constraints/DivisibleBy.rst
@@ -92,9 +92,9 @@ The following constraints ensure that:
{
$metadata->addPropertyConstraint('weight', new Assert\DivisibleBy(0.25));
- $metadata->addPropertyConstraint('quantity', new Assert\DivisibleBy([
- 'value' => 5,
- ]));
+ $metadata->addPropertyConstraint('quantity', new Assert\DivisibleBy(
+ value: 5,
+ ));
}
}
diff --git a/reference/constraints/Email.rst b/reference/constraints/Email.rst
index 516d6d07dca..41012e5e935 100644
--- a/reference/constraints/Email.rst
+++ b/reference/constraints/Email.rst
@@ -70,9 +70,9 @@ Basic Usage
public static function loadValidatorMetadata(ClassMetadata $metadata): void
{
- $metadata->addPropertyConstraint('email', new Assert\Email([
- 'message' => 'The email "{{ value }}" is not a valid email.',
- ]));
+ $metadata->addPropertyConstraint('email', new Assert\Email(
+ message: 'The email "{{ value }}" is not a valid email.',
+ ));
}
}
diff --git a/reference/constraints/EqualTo.rst b/reference/constraints/EqualTo.rst
index d5d78f60a0f..fdc402b1a97 100644
--- a/reference/constraints/EqualTo.rst
+++ b/reference/constraints/EqualTo.rst
@@ -91,9 +91,9 @@ and that the ``age`` is ``20``, you could do the following:
{
$metadata->addPropertyConstraint('firstName', new Assert\EqualTo('Mary'));
- $metadata->addPropertyConstraint('age', new Assert\EqualTo([
- 'value' => 20,
- ]));
+ $metadata->addPropertyConstraint('age', new Assert\EqualTo(
+ value: 20,
+ ));
}
}
diff --git a/reference/constraints/Expression.rst b/reference/constraints/Expression.rst
index bf015d17573..518c5c1f160 100644
--- a/reference/constraints/Expression.rst
+++ b/reference/constraints/Expression.rst
@@ -111,10 +111,10 @@ One way to accomplish this is with the Expression constraint:
{
public static function loadValidatorMetadata(ClassMetadata $metadata): void
{
- $metadata->addConstraint(new Assert\Expression([
- 'expression' => 'this.getCategory() in ["php", "symfony"] or !this.isTechnicalPost()',
- 'message' => 'If this is a tech post, the category should be either php or symfony!',
- ]));
+ $metadata->addConstraint(new Assert\Expression(
+ expression: 'this.getCategory() in ["php", "symfony"] or !this.isTechnicalPost()',
+ message: 'If this is a tech post, the category should be either php or symfony!',
+ ));
}
// ...
@@ -200,10 +200,10 @@ assert that the expression must return ``true`` for validation to fail.
{
public static function loadValidatorMetadata(ClassMetadata $metadata): void
{
- $metadata->addPropertyConstraint('isTechnicalPost', new Assert\Expression([
- 'expression' => 'this.getCategory() in ["php", "symfony"] or value == false',
- 'message' => 'If this is a tech post, the category should be either php or symfony!',
- ]));
+ $metadata->addPropertyConstraint('isTechnicalPost', new Assert\Expression(
+ expression: 'this.getCategory() in ["php", "symfony"] or value == false',
+ message: 'If this is a tech post, the category should be either php or symfony!',
+ ));
}
// ...
@@ -227,7 +227,7 @@ Options
``expression``
~~~~~~~~~~~~~~
-**type**: ``string`` [:ref:`default option `]
+**type**: ``string``
The expression that will be evaluated. If the expression evaluates to a false
value (using ``==``, not ``===``), validation will fail. Learn more about the
@@ -343,10 +343,10 @@ type (numeric, boolean, strings, null, etc.)
{
public static function loadValidatorMetadata(ClassMetadata $metadata): void
{
- $metadata->addPropertyConstraint('metric', new Assert\Expression([
- 'expression' => 'value + error_margin < threshold',
- 'values' => ['error_margin' => 0.25, 'threshold' => 1.5],
- ]));
+ $metadata->addPropertyConstraint('metric', new Assert\Expression(
+ expression: 'value + error_margin < threshold',
+ values: ['error_margin' => 0.25, 'threshold' => 1.5],
+ ));
}
// ...
diff --git a/reference/constraints/ExpressionSyntax.rst b/reference/constraints/ExpressionSyntax.rst
index c1d086790c1..37e0ad7de4a 100644
--- a/reference/constraints/ExpressionSyntax.rst
+++ b/reference/constraints/ExpressionSyntax.rst
@@ -90,9 +90,9 @@ The following constraints ensure that:
{
$metadata->addPropertyConstraint('promotion', new Assert\ExpressionSyntax());
- $metadata->addPropertyConstraint('shippingOptions', new Assert\ExpressionSyntax([
- 'allowedVariables' => ['user', 'shipping_centers'],
- ]));
+ $metadata->addPropertyConstraint('shippingOptions', new Assert\ExpressionSyntax(
+ allowedVariables: ['user', 'shipping_centers'],
+ ));
}
}
diff --git a/reference/constraints/File.rst b/reference/constraints/File.rst
index 6d9b72d17b8..62efa6cc08e 100644
--- a/reference/constraints/File.rst
+++ b/reference/constraints/File.rst
@@ -119,13 +119,13 @@ below a certain file size and a valid PDF, add the following:
public static function loadValidatorMetadata(ClassMetadata $metadata): void
{
- $metadata->addPropertyConstraint('bioFile', new Assert\File([
- 'maxSize' => '1024k',
- 'extensions' => [
+ $metadata->addPropertyConstraint('bioFile', new Assert\File(
+ maxSize: '1024k',
+ extensions: [
'pdf',
],
- 'extensionsMessage' => 'Please upload a valid PDF',
- ]));
+ extensionsMessage: 'Please upload a valid PDF',
+ ));
}
}
@@ -274,6 +274,31 @@ You can find a list of existing mime types on the `IANA website`_.
If set, the validator will check that the filename of the underlying file
doesn't exceed a certain length.
+``filenameCountUnit``
+~~~~~~~~~~~~~~~~~~~~~
+
+**type**: ``string`` **default**: ``File::FILENAME_COUNT_BYTES``
+
+The character count unit to use for the filename max length check.
+By default :phpfunction:`strlen` is used, which counts the length of the string in bytes.
+
+Can be one of the following constants of the
+:class:`Symfony\\Component\\Validator\\Constraints\\File` class:
+
+* ``FILENAME_COUNT_BYTES``: Uses :phpfunction:`strlen` counting the length of the
+ string in bytes.
+* ``FILENAME_COUNT_CODEPOINTS``: Uses :phpfunction:`mb_strlen` counting the length
+ of the string in Unicode code points. Simple (multibyte) Unicode characters count
+ as 1 character, while for example ZWJ sequences of composed emojis count as
+ multiple characters.
+* ``FILENAME_COUNT_GRAPHEMES``: Uses :phpfunction:`grapheme_strlen` counting the
+ length of the string in graphemes, i.e. even emojis and ZWJ sequences of composed
+ emojis count as 1 character.
+
+.. versionadded:: 7.3
+
+ The ``filenameCountUnit`` option was introduced in Symfony 7.3.
+
``filenameTooLongMessage``
~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -290,6 +315,35 @@ Parameter Description
``{{ filename_max_length }}`` Maximum number of characters allowed
============================== ==============================================================
+``filenameCharset``
+~~~~~~~~~~~~~~~~~~~
+
+**type**: ``string`` **default**: ``null``
+
+The charset to be used when computing value's filename max length with the
+:phpfunction:`mb_check_encoding` and :phpfunction:`mb_strlen`
+PHP functions.
+
+``filenameCharsetMessage``
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+**type**: ``string`` **default**: ``This filename does not match the expected charset.``
+
+The message that will be shown if the value is not using the given `filenameCharsetMessage`_.
+
+You can use the following parameters in this message:
+
+================= ============================================================
+Parameter Description
+================= ============================================================
+``{{ charset }}`` The expected charset
+``{{ name }}`` The current (invalid) value
+================= ============================================================
+
+.. versionadded:: 7.3
+
+ The ``filenameCharset`` and ``filenameCharsetMessage`` options were introduced in Symfony 7.3.
+
``extensionsMessage``
~~~~~~~~~~~~~~~~~~~~~
diff --git a/reference/constraints/GreaterThan.rst b/reference/constraints/GreaterThan.rst
index 4f2e34bcbfa..d1b79028acd 100644
--- a/reference/constraints/GreaterThan.rst
+++ b/reference/constraints/GreaterThan.rst
@@ -89,9 +89,9 @@ The following constraints ensure that:
{
$metadata->addPropertyConstraint('siblings', new Assert\GreaterThan(5));
- $metadata->addPropertyConstraint('age', new Assert\GreaterThan([
- 'value' => 18,
- ]));
+ $metadata->addPropertyConstraint('age', new Assert\GreaterThan(
+ value: 18,
+ ));
}
}
diff --git a/reference/constraints/GreaterThanOrEqual.rst b/reference/constraints/GreaterThanOrEqual.rst
index e5a71e5f788..63c2ade6197 100644
--- a/reference/constraints/GreaterThanOrEqual.rst
+++ b/reference/constraints/GreaterThanOrEqual.rst
@@ -88,9 +88,9 @@ The following constraints ensure that:
{
$metadata->addPropertyConstraint('siblings', new Assert\GreaterThanOrEqual(5));
- $metadata->addPropertyConstraint('age', new Assert\GreaterThanOrEqual([
- 'value' => 18,
- ]));
+ $metadata->addPropertyConstraint('age', new Assert\GreaterThanOrEqual(
+ value: 18,
+ ));
}
}
diff --git a/reference/constraints/Hostname.rst b/reference/constraints/Hostname.rst
index 95b10d1736e..58ac0364669 100644
--- a/reference/constraints/Hostname.rst
+++ b/reference/constraints/Hostname.rst
@@ -72,9 +72,9 @@ will contain a host name.
public static function loadValidatorMetadata(ClassMetadata $metadata): void
{
- $metadata->addPropertyConstraint('name', new Assert\Hostname([
- 'message' => 'The server name must be a valid hostname.',
- ]));
+ $metadata->addPropertyConstraint('name', new Assert\Hostname(
+ message: 'The server name must be a valid hostname.',
+ ));
}
}
diff --git a/reference/constraints/Iban.rst b/reference/constraints/Iban.rst
index 3cf800200e2..8d5982eea6d 100644
--- a/reference/constraints/Iban.rst
+++ b/reference/constraints/Iban.rst
@@ -77,9 +77,9 @@ will contain an International Bank Account Number.
public static function loadValidatorMetadata(ClassMetadata $metadata): void
{
- $metadata->addPropertyConstraint('bankAccountNumber', new Assert\Iban([
- 'message' => 'This is not a valid International Bank Account Number (IBAN).',
- ]));
+ $metadata->addPropertyConstraint('bankAccountNumber', new Assert\Iban(
+ message: 'This is not a valid International Bank Account Number (IBAN).',
+ ));
}
}
diff --git a/reference/constraints/IdenticalTo.rst b/reference/constraints/IdenticalTo.rst
index 5b6d853dc0b..f8844f90a72 100644
--- a/reference/constraints/IdenticalTo.rst
+++ b/reference/constraints/IdenticalTo.rst
@@ -94,9 +94,9 @@ The following constraints ensure that:
{
$metadata->addPropertyConstraint('firstName', new Assert\IdenticalTo('Mary'));
- $metadata->addPropertyConstraint('age', new Assert\IdenticalTo([
- 'value' => 20,
- ]));
+ $metadata->addPropertyConstraint('age', new Assert\IdenticalTo(
+ value: 20,
+ ));
}
}
diff --git a/reference/constraints/Image.rst b/reference/constraints/Image.rst
index 042c6041423..5dd270c44f8 100644
--- a/reference/constraints/Image.rst
+++ b/reference/constraints/Image.rst
@@ -116,12 +116,12 @@ that it is between a certain size, add the following:
public static function loadValidatorMetadata(ClassMetadata $metadata): void
{
- $metadata->addPropertyConstraint('headshot', new Assert\Image([
- 'minWidth' => 200,
- 'maxWidth' => 400,
- 'minHeight' => 200,
- 'maxHeight' => 400,
- ]));
+ $metadata->addPropertyConstraint('headshot', new Assert\Image(
+ minWidth: 200,
+ maxWidth: 400,
+ minHeight: 200,
+ maxHeight: 400,
+ ));
}
}
@@ -187,10 +187,10 @@ following code:
public static function loadValidatorMetadata(ClassMetadata $metadata): void
{
- $metadata->addPropertyConstraint('headshot', new Assert\Image([
- 'allowLandscape' => false,
- 'allowPortrait' => false,
- ]));
+ $metadata->addPropertyConstraint('headshot', new Assert\Image(
+ allowLandscape: false,
+ allowPortrait: false,
+ ));
}
}
@@ -210,10 +210,9 @@ add several other options.
If this option is false, the image cannot be landscape oriented.
-.. note::
+.. versionadded:: 7.3
- This option does not apply to SVG files. If you use it with SVG files,
- you'll see the error message defined in the ``sizeNotDetectedMessage`` option.
+ The ``allowLandscape`` option support for SVG files was introduced in Symfony 7.3.
``allowLandscapeMessage``
~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -239,10 +238,9 @@ Parameter Description
If this option is false, the image cannot be portrait oriented.
-.. note::
+.. versionadded:: 7.3
- This option does not apply to SVG files. If you use it with SVG files,
- you'll see the error message defined in the ``sizeNotDetectedMessage`` option.
+ The ``allowPortrait`` option support for SVG files was introduced in Symfony 7.3.
``allowPortraitMessage``
~~~~~~~~~~~~~~~~~~~~~~~~
@@ -270,10 +268,9 @@ 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``.
-.. note::
+.. versionadded:: 7.3
- This option does not apply to SVG files. If you use it with SVG files,
- you'll see the error message defined in the ``sizeNotDetectedMessage`` option.
+ The ``allowSquare`` option support for SVG files was introduced in Symfony 7.3.
``allowSquareMessage``
~~~~~~~~~~~~~~~~~~~~~~
@@ -373,10 +370,9 @@ Parameter Description
If set, the aspect ratio (``width / height``) of the image file must be less
than or equal to this value.
-.. note::
+.. versionadded:: 7.3
- This option does not apply to SVG files. If you use it with SVG files,
- you'll see the error message defined in the ``sizeNotDetectedMessage`` option.
+ The ``maxRatio`` option support for SVG files was introduced in Symfony 7.3.
``maxRatioMessage``
~~~~~~~~~~~~~~~~~~~
@@ -497,10 +493,9 @@ Parameter Description
If set, the aspect ratio (``width / height``) of the image file must be greater
than or equal to this value.
-.. note::
+.. versionadded:: 7.3
- This option does not apply to SVG files. If you use it with SVG files,
- you'll see the error message defined in the ``sizeNotDetectedMessage`` option.
+ The ``minRatio`` option support for SVG files was introduced in Symfony 7.3.
``minRatioMessage``
~~~~~~~~~~~~~~~~~~~
@@ -555,11 +550,5 @@ options has been set.
This message has no parameters.
-.. note::
-
- Detecting the size of SVG images is not supported. This error message will
- be displayed if you use any of the following options: ``allowLandscape``,
- ``allowPortrait``, ``allowSquare``, ``maxRatio``, and ``minRatio``.
-
.. _`IANA website`: https://www.iana.org/assignments/media-types/media-types.xhtml
.. _`PHP GD extension`: https://www.php.net/manual/en/book.image.php
diff --git a/reference/constraints/IsFalse.rst b/reference/constraints/IsFalse.rst
index 0b9ebe77491..3d0a1665944 100644
--- a/reference/constraints/IsFalse.rst
+++ b/reference/constraints/IsFalse.rst
@@ -93,9 +93,9 @@ method returns **false**:
public static function loadValidatorMetadata(ClassMetadata $metadata): void
{
- $metadata->addGetterConstraint('stateInvalid', new Assert\IsFalse([
- 'message' => "You've entered an invalid state.",
- ]));
+ $metadata->addGetterConstraint('stateInvalid', new Assert\IsFalse(
+ message: "You've entered an invalid state.",
+ ));
}
public function isStateInvalid(): bool
diff --git a/reference/constraints/IsTrue.rst b/reference/constraints/IsTrue.rst
index 678371f6e69..b50ba4f3e8b 100644
--- a/reference/constraints/IsTrue.rst
+++ b/reference/constraints/IsTrue.rst
@@ -97,9 +97,9 @@ Then you can validate this method with ``IsTrue`` as follows:
public static function loadValidatorMetadata(ClassMetadata $metadata): void
{
- $metadata->addGetterConstraint('tokenValid', new IsTrue([
- 'message' => 'The token is invalid.',
- ]));
+ $metadata->addGetterConstraint('tokenValid', new IsTrue(
+ message: 'The token is invalid.',
+ ));
}
public function isTokenValid(): bool
diff --git a/reference/constraints/Isbn.rst b/reference/constraints/Isbn.rst
index 954bff233d5..52d10565fe5 100644
--- a/reference/constraints/Isbn.rst
+++ b/reference/constraints/Isbn.rst
@@ -76,10 +76,10 @@ on an object that will contain an ISBN.
public static function loadValidatorMetadata(ClassMetadata $metadata): void
{
- $metadata->addPropertyConstraint('isbn', new Assert\Isbn([
- 'type' => Assert\Isbn::ISBN_10,
- 'message' => 'This value is not valid.',
- ]));
+ $metadata->addPropertyConstraint('isbn', new Assert\Isbn(
+ type: Assert\Isbn::ISBN_10,
+ message: 'This value is not valid.',
+ ));
}
}
diff --git a/reference/constraints/Json.rst b/reference/constraints/Json.rst
index 28e15976f3c..337b2dc6a1e 100644
--- a/reference/constraints/Json.rst
+++ b/reference/constraints/Json.rst
@@ -69,9 +69,9 @@ The ``Json`` constraint can be applied to a property or a "getter" method:
{
public static function loadValidatorMetadata(ClassMetadata $metadata): void
{
- $metadata->addPropertyConstraint('chapters', new Assert\Json([
- 'message' => 'You\'ve entered an invalid Json.',
- ]));
+ $metadata->addPropertyConstraint('chapters', new Assert\Json(
+ message: 'You\'ve entered an invalid Json.',
+ ));
}
}
diff --git a/reference/constraints/Length.rst b/reference/constraints/Length.rst
index 9a4478f509b..c1a8575070b 100644
--- a/reference/constraints/Length.rst
+++ b/reference/constraints/Length.rst
@@ -85,12 +85,12 @@ and ``50``, you might add the following:
public static function loadValidatorMetadata(ClassMetadata $metadata): void
{
- $metadata->addPropertyConstraint('firstName', new Assert\Length([
- 'min' => 2,
- 'max' => 50,
- 'minMessage' => 'Your first name must be at least {{ limit }} characters long',
- 'maxMessage' => 'Your first name cannot be longer than {{ limit }} characters',
- ]));
+ $metadata->addPropertyConstraint('firstName', new Assert\Length(
+ min: 2,
+ max: 50,
+ minMessage: 'Your first name must be at least {{ limit }} characters long',
+ maxMessage: 'Your first name cannot be longer than {{ limit }} characters',
+ ));
}
}
diff --git a/reference/constraints/LessThan.rst b/reference/constraints/LessThan.rst
index 964bfbb3527..3d23bcda445 100644
--- a/reference/constraints/LessThan.rst
+++ b/reference/constraints/LessThan.rst
@@ -89,9 +89,9 @@ The following constraints ensure that:
{
$metadata->addPropertyConstraint('siblings', new Assert\LessThan(5));
- $metadata->addPropertyConstraint('age', new Assert\LessThan([
- 'value' => 80,
- ]));
+ $metadata->addPropertyConstraint('age', new Assert\LessThan(
+ value: 80,
+ ));
}
}
diff --git a/reference/constraints/LessThanOrEqual.rst b/reference/constraints/LessThanOrEqual.rst
index 9420c3e4376..ac66c62d7d0 100644
--- a/reference/constraints/LessThanOrEqual.rst
+++ b/reference/constraints/LessThanOrEqual.rst
@@ -88,9 +88,9 @@ The following constraints ensure that:
{
$metadata->addPropertyConstraint('siblings', new Assert\LessThanOrEqual(5));
- $metadata->addPropertyConstraint('age', new Assert\LessThanOrEqual([
- 'value' => 80,
- ]));
+ $metadata->addPropertyConstraint('age', new Assert\LessThanOrEqual(
+ value: 80,
+ ));
}
}
diff --git a/reference/constraints/Locale.rst b/reference/constraints/Locale.rst
index 49edd473d05..4bba45ae12b 100644
--- a/reference/constraints/Locale.rst
+++ b/reference/constraints/Locale.rst
@@ -78,9 +78,9 @@ Basic Usage
public static function loadValidatorMetadata(ClassMetadata $metadata): void
{
- $metadata->addPropertyConstraint('locale', new Assert\Locale([
- 'canonicalize' => true,
- ]));
+ $metadata->addPropertyConstraint('locale', new Assert\Locale(
+ canonicalize: true,
+ ));
}
}
diff --git a/reference/constraints/Luhn.rst b/reference/constraints/Luhn.rst
index 8f5ef34c4ba..0c835204091 100644
--- a/reference/constraints/Luhn.rst
+++ b/reference/constraints/Luhn.rst
@@ -72,9 +72,9 @@ will contain a credit card number.
public static function loadValidatorMetadata(ClassMetadata $metadata): void
{
- $metadata->addPropertyConstraint('cardNumber', new Assert\Luhn([
- 'message' => 'Please check your credit card number',
- ]));
+ $metadata->addPropertyConstraint('cardNumber', new Assert\Luhn(
+ message: 'Please check your credit card number',
+ ));
}
}
diff --git a/reference/constraints/NotEqualTo.rst b/reference/constraints/NotEqualTo.rst
index b8ee4cac32f..dd3f633b4a1 100644
--- a/reference/constraints/NotEqualTo.rst
+++ b/reference/constraints/NotEqualTo.rst
@@ -93,9 +93,9 @@ the following:
{
$metadata->addPropertyConstraint('firstName', new Assert\NotEqualTo('Mary'));
- $metadata->addPropertyConstraint('age', new Assert\NotEqualTo([
- 'value' => 15,
- ]));
+ $metadata->addPropertyConstraint('age', new Assert\NotEqualTo(
+ value: 15,
+ ));
}
}
diff --git a/reference/constraints/NotIdenticalTo.rst b/reference/constraints/NotIdenticalTo.rst
index 9ea93dc4b86..b2c20027292 100644
--- a/reference/constraints/NotIdenticalTo.rst
+++ b/reference/constraints/NotIdenticalTo.rst
@@ -94,9 +94,9 @@ The following constraints ensure that:
{
$metadata->addPropertyConstraint('age', new Assert\NotIdenticalTo('Mary'));
- $metadata->addPropertyConstraint('age', new Assert\NotIdenticalTo([
- 'value' => 15,
- ]));
+ $metadata->addPropertyConstraint('age', new Assert\NotIdenticalTo(
+ value: 15,
+ ));
}
}
diff --git a/reference/constraints/PasswordStrength.rst b/reference/constraints/PasswordStrength.rst
index 60125a763a1..0b242cacf08 100644
--- a/reference/constraints/PasswordStrength.rst
+++ b/reference/constraints/PasswordStrength.rst
@@ -101,9 +101,9 @@ or by a custom password strength estimator.
class User
{
- #[Assert\PasswordStrength([
- 'minScore' => PasswordStrength::STRENGTH_VERY_STRONG, // Very strong password required
- ])]
+ #[Assert\PasswordStrength(
+ minScore: PasswordStrength::STRENGTH_VERY_STRONG, // Very strong password required
+ )]
protected $rawPassword;
}
@@ -123,9 +123,9 @@ The default message supplied when the password does not reach the minimum requir
class User
{
- #[Assert\PasswordStrength([
- 'message' => 'Your password is too easy to guess. Company\'s security policy requires to use a stronger password.'
- ])]
+ #[Assert\PasswordStrength(
+ message: 'Your password is too easy to guess. Company\'s security policy requires to use a stronger password.'
+ )]
protected $rawPassword;
}
diff --git a/reference/constraints/Range.rst b/reference/constraints/Range.rst
index edd199c48b9..46a9e3799b3 100644
--- a/reference/constraints/Range.rst
+++ b/reference/constraints/Range.rst
@@ -78,11 +78,11 @@ you might add the following:
public static function loadValidatorMetadata(ClassMetadata $metadata): void
{
- $metadata->addPropertyConstraint('height', new Assert\Range([
- 'min' => 120,
- 'max' => 180,
- 'notInRangeMessage' => 'You must be between {{ min }}cm and {{ max }}cm tall to enter',
- ]));
+ $metadata->addPropertyConstraint('height', new Assert\Range(
+ min: 120,
+ max: 180,
+ notInRangeMessage: 'You must be between {{ min }}cm and {{ max }}cm tall to enter',
+ ));
}
}
@@ -154,10 +154,10 @@ date must lie within the current year like this:
public static function loadValidatorMetadata(ClassMetadata $metadata): void
{
- $metadata->addPropertyConstraint('startDate', new Assert\Range([
- 'min' => 'first day of January',
- 'max' => 'first day of January next year',
- ]));
+ $metadata->addPropertyConstraint('startDate', new Assert\Range(
+ min: 'first day of January',
+ max: 'first day of January next year',
+ ));
}
}
@@ -224,10 +224,10 @@ dates. If you want to fix the timezone, append it to the date string:
public static function loadValidatorMetadata(ClassMetadata $metadata): void
{
- $metadata->addPropertyConstraint('startDate', new Assert\Range([
- 'min' => 'first day of January UTC',
- 'max' => 'first day of January next year UTC',
- ]));
+ $metadata->addPropertyConstraint('startDate', new Assert\Range(
+ min: 'first day of January UTC',
+ max: 'first day of January next year UTC',
+ ));
}
}
@@ -294,10 +294,10 @@ can check that a delivery date starts within the next five hours like this:
public static function loadValidatorMetadata(ClassMetadata $metadata): void
{
- $metadata->addPropertyConstraint('deliveryDate', new Assert\Range([
- 'min' => 'now',
- 'max' => '+5 hours',
- ]));
+ $metadata->addPropertyConstraint('deliveryDate', new Assert\Range(
+ min: 'now',
+ max: '+5 hours',
+ ));
}
}
diff --git a/reference/constraints/Regex.rst b/reference/constraints/Regex.rst
index 2e11a8d04fc..e3b4d4711b2 100644
--- a/reference/constraints/Regex.rst
+++ b/reference/constraints/Regex.rst
@@ -71,9 +71,9 @@ more word characters at the beginning of your string:
public static function loadValidatorMetadata(ClassMetadata $metadata): void
{
- $metadata->addPropertyConstraint('description', new Assert\Regex([
- 'pattern' => '/^\w+/',
- ]));
+ $metadata->addPropertyConstraint('description', new Assert\Regex(
+ pattern: '/^\w+/',
+ ));
}
}
@@ -145,11 +145,11 @@ it a custom message:
public static function loadValidatorMetadata(ClassMetadata $metadata): void
{
- $metadata->addPropertyConstraint('firstName', new Assert\Regex([
- 'pattern' => '/\d/',
- 'match' => false,
- 'message' => 'Your name cannot contain a number',
- ]));
+ $metadata->addPropertyConstraint('firstName', new Assert\Regex(
+ pattern: '/\d/',
+ match: false,
+ message: 'Your name cannot contain a number',
+ ));
}
}
@@ -236,10 +236,10 @@ need to specify the HTML5 compatible pattern in the ``htmlPattern`` option:
public static function loadValidatorMetadata(ClassMetadata $metadata): void
{
- $metadata->addPropertyConstraint('name', new Assert\Regex([
- 'pattern' => '/^[a-z]+$/i',
- 'htmlPattern' => '[a-zA-Z]+',
- ]));
+ $metadata->addPropertyConstraint('name', new Assert\Regex(
+ pattern: '/^[a-z]+$/i',
+ htmlPattern: '[a-zA-Z]+',
+ ));
}
}
@@ -275,7 +275,7 @@ Parameter Description
``pattern``
~~~~~~~~~~~
-**type**: ``string`` [:ref:`default option `]
+**type**: ``string``
This required option is the regular expression pattern that the input will
be matched against. By default, this validator will fail if the input string
diff --git a/reference/constraints/Sequentially.rst b/reference/constraints/Sequentially.rst
index 7620997f0a3..078be338cdf 100644
--- a/reference/constraints/Sequentially.rst
+++ b/reference/constraints/Sequentially.rst
@@ -110,7 +110,7 @@ You can validate each of these constraints sequentially to solve these issues:
$metadata->addPropertyConstraint('address', new Assert\Sequentially([
new Assert\NotNull(),
new Assert\Type('string'),
- new Assert\Length(['min' => 10]),
+ new Assert\Length(min: 10),
new Assert\Regex(self::ADDRESS_REGEX),
new AcmeAssert\Geolocalizable(),
]));
@@ -123,7 +123,7 @@ Options
``constraints``
~~~~~~~~~~~~~~~
-**type**: ``array`` [:ref:`default option `]
+**type**: ``array``
This required option is the array of validation constraints that you want
to apply sequentially.
diff --git a/reference/constraints/Slug.rst b/reference/constraints/Slug.rst
new file mode 100644
index 00000000000..2eb82cd9c10
--- /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. By default, a slug is a string that matches
+the following regular expression: ``/^[a-z0-9]+(?:-[a-z0-9]+)*$/``.
+
+.. include:: /reference/constraints/_empty-values-are-valid.rst.inc
+
+========== ===================================================================
+Applies to :ref:`property or method `
+Class :class:`Symfony\\Component\\Validator\\Constraints\\Slug`
+Validator :class:`Symfony\\Component\\Validator\\Constraints\\SlugValidator`
+========== ===================================================================
+
+Basic Usage
+-----------
+
+The ``Slug`` constraint can be applied to a property or a getter method:
+
+.. configuration-block::
+
+ .. code-block:: php-attributes
+
+ // src/Entity/Author.php
+ namespace App\Entity;
+
+ use Symfony\Component\Validator\Constraints as Assert;
+
+ class Author
+ {
+ #[Assert\Slug]
+ protected string $slug;
+ }
+
+ .. code-block:: yaml
+
+ # config/validator/validation.yaml
+ App\Entity\Author:
+ properties:
+ slug:
+ - Slug: ~
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+
+
+ .. code-block:: php
+
+ // src/Entity/Author.php
+ namespace App\Entity;
+
+ use Symfony\Component\Validator\Constraints as Assert;
+ use Symfony\Component\Validator\Mapping\ClassMetadata;
+
+ class Author
+ {
+ // ...
+
+ public static function loadValidatorMetadata(ClassMetadata $metadata): void
+ {
+ $metadata->addPropertyConstraint('slug', new Assert\Slug());
+ }
+ }
+
+Examples of valid values:
+
+* foobar
+* foo-bar
+* foo123
+* foo-123bar
+
+Uppercase characters would result in an violation of this constraint.
+
+Options
+-------
+
+``regex``
+~~~~~~~~~
+
+**type**: ``string`` default: ``/^[a-z0-9]+(?:-[a-z0-9]+)*$/``
+
+This option allows you to modify the regular expression pattern that the input
+will be matched against via the :phpfunction:`preg_match` PHP function.
+
+If you need to use it, you might also want to take a look at the :doc:`Regex constraint `.
+
+``message``
+~~~~~~~~~~~
+
+**type**: ``string`` **default**: ``This value is not a valid slug``
+
+This is the message that will be shown if this validator fails.
+
+You can use the following parameters in this message:
+
+================= ==============================================================
+Parameter Description
+================= ==============================================================
+``{{ value }}`` The current (invalid) value
+================= ==============================================================
+
+.. include:: /reference/constraints/_groups-option.rst.inc
+
+.. include:: /reference/constraints/_payload-option.rst.inc
diff --git a/reference/constraints/Twig.rst b/reference/constraints/Twig.rst
new file mode 100644
index 00000000000..8435c191855
--- /dev/null
+++ b/reference/constraints/Twig.rst
@@ -0,0 +1,130 @@
+Twig Constraint
+===============
+
+.. versionadded:: 7.3
+
+ The ``Twig`` constraint was introduced in Symfony 7.3.
+
+Validates that a given string contains valid :ref:`Twig syntax `.
+This is particularly useful when template content is user-generated or
+configurable, and you want to ensure it can be rendered by the Twig engine.
+
+.. note::
+
+ Using this constraint requires having the ``symfony/twig-bridge`` package
+ installed in your application (e.g. by running ``composer require symfony/twig-bridge``).
+
+========== ===================================================================
+Applies to :ref:`property or method `
+Class :class:`Symfony\\Bridge\\Twig\\Validator\\Constraints\\Twig`
+Validator :class:`Symfony\\Bridge\\Twig\\Validator\\Constraints\\TwigValidator`
+========== ===================================================================
+
+Basic Usage
+-----------
+
+Apply the ``Twig`` constraint to validate the contents of any property or the
+returned value of any method:
+
+ use Symfony\Bridge\Twig\Validator\Constraints\Twig;
+
+ class Template
+ {
+ #[Twig]
+ private string $templateCode;
+ }
+
+.. configuration-block::
+
+ .. code-block:: php-attributes
+
+ // src/Entity/Page.php
+ namespace App\Entity;
+
+ use Symfony\Bridge\Twig\Validator\Constraints\Twig;
+
+ class Page
+ {
+ #[Twig]
+ private string $templateCode;
+ }
+
+ .. code-block:: yaml
+
+ # config/validator/validation.yaml
+ App\Entity\Page:
+ properties:
+ templateCode:
+ - Symfony\Bridge\Twig\Validator\Constraints\Twig: ~
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+
+
+ .. code-block:: php
+
+ // src/Entity/Page.php
+ namespace App\Entity;
+
+ use Symfony\Bridge\Twig\Validator\Constraints\Twig;
+ use Symfony\Component\Validator\Mapping\ClassMetadata;
+
+ class Page
+ {
+ // ...
+
+ public static function loadValidatorMetadata(ClassMetadata $metadata): void
+ {
+ $metadata->addPropertyConstraint('templateCode', new Twig());
+ }
+ }
+
+Constraint Options
+------------------
+
+``message``
+~~~~~~~~~~~
+
+**type**: ``message`` **default**: ``This value is not a valid Twig template.``
+
+This is the message displayed when the given string does *not* contain valid Twig syntax::
+
+ // ...
+
+ class Page
+ {
+ #[Twig(message: 'Check this Twig code; it contains errors.')]
+ private string $templateCode;
+ }
+
+This message has no parameters.
+
+``skipDeprecations``
+~~~~~~~~~~~~~~~~~~~~
+
+**type**: ``boolean`` **default**: ``true``
+
+If ``true``, Twig deprecation warnings are ignored during validation. Set it to
+``false`` to trigger validation errors when the given Twig code contains any deprecations::
+
+ // ...
+
+ class Page
+ {
+ #[Twig(skipDeprecations: false)]
+ private string $templateCode;
+ }
+
+This can be helpful when enforcing stricter template rules or preparing for major
+Twig version upgrades.
diff --git a/reference/constraints/Type.rst b/reference/constraints/Type.rst
index b99e8ce1c54..b49536dff8b 100644
--- a/reference/constraints/Type.rst
+++ b/reference/constraints/Type.rst
@@ -127,14 +127,14 @@ The following example checks if ``emailAddress`` is an instance of ``Symfony\Com
$metadata->addPropertyConstraint('firstName', new Assert\Type('string'));
- $metadata->addPropertyConstraint('age', new Assert\Type([
- 'type' => 'integer',
- 'message' => 'The value {{ value }} is not a valid {{ type }}.',
- ]));
-
- $metadata->addPropertyConstraint('accessCode', new Assert\Type([
- 'type' => ['alpha', 'digit'],
- ]));
+ $metadata->addPropertyConstraint('age', new Assert\Type(
+ type: 'integer',
+ message: 'The value {{ value }} is not a valid {{ type }}.',
+ ));
+
+ $metadata->addPropertyConstraint('accessCode', new Assert\Type(
+ type: ['alpha', 'digit'],
+ ));
}
}
@@ -169,7 +169,7 @@ Parameter Description
``type``
~~~~~~~~
-**type**: ``string`` or ``array`` [:ref:`default option `]
+**type**: ``string`` or ``array``
This required option defines the type or collection of types allowed for the
given value. Each type is either the FQCN (fully qualified class name) of some
diff --git a/reference/constraints/Unique.rst b/reference/constraints/Unique.rst
index 68754738271..9ce84139cd5 100644
--- a/reference/constraints/Unique.rst
+++ b/reference/constraints/Unique.rst
@@ -162,9 +162,9 @@ collection::
public static function loadValidatorMetadata(ClassMetadata $metadata): void
{
- $metadata->addPropertyConstraint('coordinates', new Assert\Unique([
- 'fields' => ['latitude', 'longitude'],
- ]));
+ $metadata->addPropertyConstraint('coordinates', new Assert\Unique(
+ fields: ['latitude', 'longitude'],
+ ));
}
}
@@ -216,4 +216,17 @@ trailing whitespace during validation.
.. include:: /reference/constraints/_payload-option.rst.inc
+``stopOnFirstError``
+~~~~~~~~~~~~~~~~~~~~
+
+**type**: ``boolean`` **default**: ``true``
+
+By default, this constraint stops at the first violation. If this option is set
+to ``false``, validation continues on all elements and returns all detected
+:class:`Symfony\\Component\\Validator\\ConstraintViolation` objects.
+
+.. versionadded:: 7.3
+
+ The ``stopOnFirstError`` option was introduced in Symfony 7.3.
+
.. _`PHP callable`: https://www.php.net/callable
diff --git a/reference/constraints/UniqueEntity.rst b/reference/constraints/UniqueEntity.rst
index d4fbfeb8666..e819a8009dc 100644
--- a/reference/constraints/UniqueEntity.rst
+++ b/reference/constraints/UniqueEntity.rst
@@ -95,9 +95,9 @@ between all of the rows in your user table:
public static function loadValidatorMetadata(ClassMetadata $metadata): void
{
- $metadata->addConstraint(new UniqueEntity([
- 'fields' => 'email',
- ]));
+ $metadata->addConstraint(new UniqueEntity(
+ fields: 'email',
+ ));
$metadata->addPropertyConstraint('email', new Assert\Email());
}
@@ -260,7 +260,7 @@ Now, the message would be bound to the ``port`` field with this configuration.
``fields``
~~~~~~~~~~
-**type**: ``array`` | ``string`` [:ref:`default option `]
+**type**: ``array`` | ``string``
This required option is the field (or list of fields) on which this entity
should be unique. For example, if you specified both the ``email`` and ``name``
@@ -346,10 +346,10 @@ this option to specify one or more fields to only ignore ``null`` values on them
{
public static function loadValidatorMetadata(ClassMetadata $metadata)
{
- $metadata->addConstraint(new UniqueEntity([
- 'fields' => ['email', 'phoneNumber'],
- 'ignoreNull' => 'phoneNumber',
- ]));
+ $metadata->addConstraint(new UniqueEntity(
+ fields: ['email', 'phoneNumber'],
+ ignoreNull: 'phoneNumber',
+ ));
// ...
}
diff --git a/reference/constraints/Url.rst b/reference/constraints/Url.rst
index 74f0d750dfd..fbeaa6da522 100644
--- a/reference/constraints/Url.rst
+++ b/reference/constraints/Url.rst
@@ -152,9 +152,9 @@ Parameter Description
public static function loadValidatorMetadata(ClassMetadata $metadata): void
{
- $metadata->addPropertyConstraint('bioUrl', new Assert\Url([
- 'message' => 'The url "{{ value }}" is not a valid url.',
- ]));
+ $metadata->addPropertyConstraint('bioUrl', new Assert\Url(
+ message: 'The url "{{ value }}" is not a valid url.',
+ ));
}
}
@@ -231,9 +231,9 @@ the ``ftp://`` type URLs to be valid, redefine the ``protocols`` array, listing
public static function loadValidatorMetadata(ClassMetadata $metadata): void
{
- $metadata->addPropertyConstraint('bioUrl', new Assert\Url([
- 'protocols' => ['http', 'https', 'ftp'],
- ]));
+ $metadata->addPropertyConstraint('bioUrl', new Assert\Url(
+ protocols: ['http', 'https', 'ftp'],
+ ));
}
}
@@ -302,9 +302,9 @@ also relative URLs that contain no protocol (e.g. ``//example.com``).
public static function loadValidatorMetadata(ClassMetadata $metadata): void
{
- $metadata->addPropertyConstraint('bioUrl', new Assert\Url([
- 'relativeProtocol' => true,
- ]));
+ $metadata->addPropertyConstraint('bioUrl', new Assert\Url(
+ relativeProtocol: true,
+ ));
}
}
@@ -414,10 +414,10 @@ Parameter Description
public static function loadValidatorMetadata(ClassMetadata $metadata): void
{
- $metadata->addPropertyConstraint('homepageUrl', new Assert\Url([
- 'requireTld' => true,
- 'tldMessage' => 'Add at least one TLD to the {{ value }} URL.',
- ]));
+ $metadata->addPropertyConstraint('homepageUrl', new Assert\Url(
+ requireTld: true,
+ tldMessage: 'Add at least one TLD to the {{ value }} URL.',
+ ));
}
}
diff --git a/reference/constraints/Valid.rst b/reference/constraints/Valid.rst
index 1f99b666419..61a2c1d992c 100644
--- a/reference/constraints/Valid.rst
+++ b/reference/constraints/Valid.rst
@@ -149,7 +149,7 @@ stores an ``Address`` instance in the ``$address`` property::
{
$metadata->addPropertyConstraint('street', new Assert\NotBlank());
$metadata->addPropertyConstraint('zipCode', new Assert\NotBlank());
- $metadata->addPropertyConstraint('zipCode', new Assert\Length(['max' => 5]));
+ $metadata->addPropertyConstraint('zipCode', new Assert\Length(max: 5));
}
}
@@ -166,7 +166,7 @@ stores an ``Address`` instance in the ``$address`` property::
public static function loadValidatorMetadata(ClassMetadata $metadata): void
{
$metadata->addPropertyConstraint('firstName', new Assert\NotBlank());
- $metadata->addPropertyConstraint('firstName', new Assert\Length(['min' => 4]));
+ $metadata->addPropertyConstraint('firstName', new Assert\Length(min: 4));
$metadata->addPropertyConstraint('lastName', new Assert\NotBlank());
}
}
diff --git a/reference/constraints/Week.rst b/reference/constraints/Week.rst
index f107d8b4192..b3c1b0ca122 100644
--- a/reference/constraints/Week.rst
+++ b/reference/constraints/Week.rst
@@ -79,10 +79,10 @@ the following:
public static function loadValidatorMetadata(ClassMetadata $metadata): void
{
- $metadata->addPropertyConstraint('startWeek', new Assert\Week([
- 'min' => '2022-W01',
- 'max' => '2022-W20',
- ]));
+ $metadata->addPropertyConstraint('startWeek', new Assert\Week(
+ min: '2022-W01',
+ max: '2022-W20',
+ ));
}
}
diff --git a/reference/constraints/When.rst b/reference/constraints/When.rst
index 2a05e58ee9c..6eca8b4895f 100644
--- a/reference/constraints/When.rst
+++ b/reference/constraints/When.rst
@@ -9,6 +9,7 @@ Applies to :ref:`class `
or :ref:`property/method `
Options - `expression`_
- `constraints`_
+ _ `otherwise`_
- `groups`_
- `payload`_
- `values`_
@@ -47,7 +48,7 @@ properties::
To validate the object, you have some requirements:
A) If ``type`` is ``percent``, then ``value`` must be less than or equal 100;
-B) If ``type`` is ``absolute``, then ``value`` can be anything;
+B) If ``type`` is not ``percent``, then ``value`` must be less than 9999;
C) No matter the value of ``type``, the ``value`` must be greater than 0.
One way to accomplish this is with the When constraint:
@@ -69,6 +70,9 @@ One way to accomplish this is with the When constraint:
constraints: [
new Assert\LessThanOrEqual(100, message: 'The value should be between 1 and 100!')
],
+ otherwise: [
+ new Assert\LessThan(9999, message: 'The value should be less than 9999!')
+ ],
)]
private ?int $value;
@@ -88,6 +92,10 @@ One way to accomplish this is with the When constraint:
- LessThanOrEqual:
value: 100
message: "The value should be between 1 and 100!"
+ otherwise:
+ - LessThan:
+ value: 9999
+ message: "The value should be less than 9999!"
.. code-block:: xml
@@ -109,6 +117,12 @@ One way to accomplish this is with the When constraint:
+
@@ -127,15 +141,21 @@ One way to accomplish this is with the When constraint:
public static function loadValidatorMetadata(ClassMetadata $metadata): void
{
$metadata->addPropertyConstraint('value', new Assert\GreaterThan(0));
- $metadata->addPropertyConstraint('value', new Assert\When([
- 'expression' => 'this.getType() == "percent"',
- 'constraints' => [
- new Assert\LessThanOrEqual([
- 'value' => 100,
- 'message' => 'The value should be between 1 and 100!',
- ]),
+ $metadata->addPropertyConstraint('value', new Assert\When(
+ expression: 'this.getType() == "percent"',
+ constraints: [
+ new Assert\LessThanOrEqual(
+ value: 100,
+ message: 'The value should be between 1 and 100!',
+ ),
],
- ]));
+ otherwise: [
+ new Assert\LessThan(
+ value: 9999,
+ message: 'The value should be less than 9999!',
+ ),
+ ],
+ ));
}
// ...
@@ -154,17 +174,15 @@ Options
``expression``
~~~~~~~~~~~~~~
-**type**: ``string``
-
-The condition written with the expression language syntax that will be evaluated.
-If the expression evaluates to a falsey value (i.e. using ``==``, not ``===``),
-validation of constraints won't be triggered.
+**type**: ``string|Closure``
-To learn more about the expression language syntax, see
-:doc:`/reference/formats/expression_language`.
+The condition evaluated to decide if the constraint is applied or not. It can be
+defined as a closure or a string using the :doc:`expression language syntax `.
+If the result is a falsey value (``false``, ``null``, ``0``, an empty string or
+an empty array) the constraints defined in the ``constraints`` option won't be
+applied but the constraints defined in ``otherwise`` option (if provided) will be applied.
-Depending on how you use the constraint, you have access to different variables
-in your expression:
+**When using an expression**, you access to the following variables:
``this``
The object being validated (e.g. an instance of Discount).
@@ -180,8 +198,12 @@ in your expression:
The ``context`` variable in expressions was introduced in Symfony 7.2.
-The ``value`` variable can be used when you want to execute more complex
-validation based on its value:
+**When using a closure**, the first argument is the object being validated.
+
+.. versionadded:: 7.3
+
+ The support for closures in the ``expression`` option was introduced in Symfony 7.3
+ and requires PHP 8.5.
.. configuration-block::
@@ -195,11 +217,21 @@ validation based on its value:
class Discount
{
+ // either using an expression...
#[Assert\When(
expression: 'value == "percent"',
constraints: [new Assert\Callback('doComplexValidation')],
)]
+
+ // ... or using a closure
+ #[Assert\When(
+ expression: static function (Discount $discount) {
+ return $discount->getType() === 'percent';
+ },
+ constraints: [new Assert\Callback('doComplexValidation')],
+ )]
private ?string $type;
+
// ...
public function doComplexValidation(ExecutionContextInterface $context, $payload): void
@@ -256,12 +288,12 @@ validation based on its value:
public static function loadValidatorMetadata(ClassMetadata $metadata): void
{
- $metadata->addPropertyConstraint('type', new Assert\When([
- 'expression' => 'value == "percent"',
- 'constraints' => [
+ $metadata->addPropertyConstraint('type', new Assert\When(
+ expression: 'value == "percent"',
+ constraints: [
new Assert\Callback('doComplexValidation'),
],
- ]));
+ ));
}
public function doComplexValidation(ExecutionContextInterface $context, $payload): void
@@ -279,6 +311,17 @@ You can also pass custom variables using the `values`_ option.
One or multiple constraints that are applied if the expression returns true.
+``otherwise``
+~~~~~~~~~~~~~
+
+**type**: ``array|Constraint``
+
+One or multiple constraints that are applied if the expression returns false.
+
+.. versionadded:: 7.3
+
+ The ``otherwise`` option was introduced in Symfony 7.3.
+
.. include:: /reference/constraints/_groups-option.rst.inc
.. include:: /reference/constraints/_payload-option.rst.inc
diff --git a/reference/constraints/WordCount.rst b/reference/constraints/WordCount.rst
index 74c79216898..392f8a5bcb7 100644
--- a/reference/constraints/WordCount.rst
+++ b/reference/constraints/WordCount.rst
@@ -78,10 +78,10 @@ class contains between 100 and 200 words, you could do the following:
public static function loadValidatorMetadata(ClassMetadata $metadata): void
{
- $metadata->addPropertyConstraint('content', new Assert\WordCount([
- 'min' => 100,
- 'max' => 200,
- ]));
+ $metadata->addPropertyConstraint('content', new Assert\WordCount(
+ min: 100,
+ max: 200,
+ ));
}
}
diff --git a/reference/constraints/Yaml.rst b/reference/constraints/Yaml.rst
index 49b65f251e8..0d1564f4f8a 100644
--- a/reference/constraints/Yaml.rst
+++ b/reference/constraints/Yaml.rst
@@ -73,9 +73,9 @@ The ``Yaml`` constraint can be applied to a property or a "getter" method:
{
public static function loadValidatorMetadata(ClassMetadata $metadata): void
{
- $metadata->addPropertyConstraint('customConfiguration', new Assert\Yaml([
- 'message' => 'Your configuration doesn\'t have valid YAML syntax.',
- ]));
+ $metadata->addPropertyConstraint('customConfiguration', new Assert\Yaml(
+ message: 'Your configuration doesn\'t have valid YAML syntax.',
+ ));
}
}
@@ -122,10 +122,10 @@ Its value is a combination of one or more of the :ref:`flags defined by the Yaml
{
public static function loadValidatorMetadata(ClassMetadata $metadata): void
{
- $metadata->addPropertyConstraint('customConfiguration', new Assert\Yaml([
- 'message' => 'Your configuration doesn\'t have valid YAML syntax.',
- 'flags' => Yaml::PARSE_CONSTANT | Yaml::PARSE_CUSTOM_TAGS | Yaml::PARSE_DATETIME,
- ]));
+ $metadata->addPropertyConstraint('customConfiguration', new Assert\Yaml(
+ message: 'Your configuration doesn\'t have valid YAML syntax.',
+ flags: Yaml::PARSE_CONSTANT | Yaml::PARSE_CUSTOM_TAGS | Yaml::PARSE_DATETIME,
+ ));
}
}
diff --git a/reference/constraints/_comparison-value-option.rst.inc b/reference/constraints/_comparison-value-option.rst.inc
index c8abdfb5af0..91ab28a2e94 100644
--- a/reference/constraints/_comparison-value-option.rst.inc
+++ b/reference/constraints/_comparison-value-option.rst.inc
@@ -1,7 +1,7 @@
``value``
~~~~~~~~~
-**type**: ``mixed`` [:ref:`default option `]
+**type**: ``mixed``
This option is required. It defines the comparison value. It can be a
string, number or object.
diff --git a/reference/constraints/map.rst.inc b/reference/constraints/map.rst.inc
index f23f5b71aa3..c2396ae3af7 100644
--- a/reference/constraints/map.rst.inc
+++ b/reference/constraints/map.rst.inc
@@ -33,6 +33,8 @@ String Constraints
* :doc:`NotCompromisedPassword `
* :doc:`PasswordStrength `
* :doc:`Regex `
+* :doc:`Slug `
+* :doc:`Twig `
* :doc:`Ulid `
* :doc:`Url `
* :doc:`UserPassword `
diff --git a/reference/forms/types/money.rst b/reference/forms/types/money.rst
index a02b695abd4..967fe9e4ce4 100644
--- a/reference/forms/types/money.rst
+++ b/reference/forms/types/money.rst
@@ -83,6 +83,9 @@ input
By default, the money value is converted to a ``float`` PHP type. If you need the
value to be converted into an integer (e.g. because some library needs money
values stored in cents as integers) set this option to ``integer``.
+You can also set this option to ``string``, it can be useful if the underlying
+data is a string for precision reasons (for example, Doctrine uses strings for
+the decimal type).
.. versionadded:: 7.1
diff --git a/reference/twig_reference.rst b/reference/twig_reference.rst
index f2735ec758a..633d4c7f0c6 100644
--- a/reference/twig_reference.rst
+++ b/reference/twig_reference.rst
@@ -187,20 +187,41 @@ 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.
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
+~~~~~~~~~~~~~~~~~~~
+
+.. 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) }}
+
+``user``
+ **type**: ``object``
+``attribute``
+ **type**: ``string``
+``subject`` *(optional)*
+ **type**: ``object``
+
+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
~~~~~~~~~~~
@@ -523,6 +544,7 @@ explained in the article about :doc:`customizing form rendering `
* :ref:`form_rest() `
* :ref:`field_name() `
+* :ref:`field_id() `
* :ref:`field_value() `
* :ref:`field_label() `
* :ref:`field_help() `
diff --git a/routing.rst b/routing.rst
index 24218eaf74c..19c2ae1a5a6 100644
--- a/routing.rst
+++ b/routing.rst
@@ -434,6 +434,18 @@ evaluates them:
blog_show ANY ANY ANY /blog/{slug}
---------------- ------- ------- ----- --------------------------------------------
+ # pass this option to also display all the defined route aliases
+ $ php bin/console debug:router --show-aliases
+
+ # pass this option to only display routes that match the given HTTP method
+ # (you can use the special value ANY to see routes that match any method)
+ $ php bin/console debug:router --method=GET
+ $ php bin/console debug:router --method=ANY
+
+.. versionadded:: 7.3
+
+ The ``--method`` option was introduced in Symfony 7.3.
+
Pass the name (or part of the name) of some route to this argument to print the
route details:
@@ -451,11 +463,6 @@ route details:
| | utf8: true |
+-------------+---------------------------------------------------------+
-.. tip::
-
- Use the ``--show-aliases`` option to show all available aliases for a given
- route.
-
The other command is called ``router:match`` and it shows which route will match
the given URL. It's useful to find out why some URL is not executing the
controller action that you expect:
@@ -2803,11 +2810,30 @@ 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.
-.. note::
+If you need to know the reason why a signed URI is invalid, you can use the
+``verify()`` method which throws exceptions on failure::
+
+ use Symfony\Component\HttpFoundation\Exception\ExpiredSignedUriException;
+ use Symfony\Component\HttpFoundation\Exception\UnsignedUriException;
+ use Symfony\Component\HttpFoundation\Exception\UnverifiedSignedUriException;
+
+ // ...
+
+ try {
+ $uriSigner->verify($uri); // $uri can be a string or Request object
+
+ // the URI is valid
+ } catch (UnsignedUriException) {
+ // the URI isn't signed
+ } catch (UnverifiedSignedUriException) {
+ // the URI is signed but the signature is invalid
+ } catch (ExpiredSignedUriException) {
+ // the URI is signed but expired
+ }
+
+.. versionadded:: 7.3
- The generated URI hashes may include the ``/`` and ``+`` characters, which
- can cause issues with certain clients. If you encounter this problem, replace
- them using the following: ``strtr($hash, ['/' => '_', '+' => '-'])``.
+ The ``verify()`` method was introduced in Symfony 7.3.
Troubleshooting
---------------
diff --git a/scheduler.rst b/scheduler.rst
index 1630fd8dada..e1c5b655bf3 100644
--- a/scheduler.rst
+++ b/scheduler.rst
@@ -439,10 +439,10 @@ by adding one of these attributes to a service or a command:
:class:`Symfony\\Component\\Scheduler\\Attribute\\AsCronTask`.
For both of these attributes, you have the ability to define the schedule to
-use via the ``schedule``option. By default, the ``default`` named schedule will
+use via the ``schedule`` option. By default, the ``default`` named schedule will
be used. Also, by default, the ``__invoke`` method of your service will be called
-but, it's also possible to specify the method to call via the ``method``option
-and you can define arguments via ``arguments``option if necessary.
+but, it's also possible to specify the method to call via the ``method`` option
+and you can define arguments via ``arguments`` option if necessary.
.. _scheduler-attributes-cron-task:
@@ -744,10 +744,15 @@ after a message is consumed::
$schedule = $event->getSchedule();
$context = $event->getMessageContext();
$message = $event->getMessage();
+ $result = $event->getResult();
- // do something with the schedule, context or message
+ // do something with the schedule, context, message or result
}
+.. versionadded:: 7.3
+
+ The ``getResult()`` method was introduced in Symfony 7.3.
+
Execute this command to find out which listeners are registered for this event
and their priorities:
diff --git a/security.rst b/security.rst
index a05bce6b72d..847f90a1e2c 100644
--- a/security.rst
+++ b/security.rst
@@ -2546,6 +2546,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
.......................
@@ -2582,6 +2593,19 @@ want to include extra details only for users that have a ``ROLE_SALES_ADMIN`` ro
// ...
}
+
+.. tip::
+
+ 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
+
+ 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.
diff --git a/security/csrf.rst b/security/csrf.rst
index be8348597c7..cc9b15253bc 100644
--- a/security/csrf.rst
+++ b/security/csrf.rst
@@ -288,11 +288,26 @@ object evaluated to the id::
// ... do something, like deleting an object
}
+By default, the ``IsCsrfTokenValid`` attribute performs the CSRF token check for
+all HTTP methods. You can restrict this validation to specific methods using the
+``methods`` parameter. If the request uses a method not listed in the ``methods``
+array, the attribute is ignored for that request, and no CSRF validation occurs::
+
+ #[IsCsrfTokenValid('delete-item', tokenKey: 'token', methods: ['DELETE'])]
+ public function delete(Post $post): Response
+ {
+ // ... delete the object
+ }
+
.. versionadded:: 7.1
The :class:`Symfony\\Component\\Security\\Http\\Attribute\\IsCsrfTokenValid`
attribute was introduced in Symfony 7.1.
+.. versionadded:: 7.3
+
+ The ``methods`` parameter was introduced in Symfony 7.3.
+
CSRF Tokens and Compression Side-Channel Attacks
------------------------------------------------
diff --git a/security/expressions.rst b/security/expressions.rst
index 569c7f093bf..a4ec02c7b84 100644
--- a/security/expressions.rst
+++ b/security/expressions.rst
@@ -201,6 +201,38 @@ Inside the subject's expression, you have access to two variables:
``args``
An array of controller arguments that are passed to the controller.
+Additionally to expressions, the ``#[IsGranted]`` attribute also accepts
+closures that return a boolean value. The subject can also be a closure that
+returns an array of values that will be injected into the closure::
+
+ // src/Controller/MyController.php
+ namespace App\Controller;
+
+ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+ use Symfony\Component\HttpFoundation\Response;
+ use Symfony\Component\Security\Http\Attribute\IsGranted;
+ use Symfony\Component\Security\Http\Attribute\IsGrantedContext;
+
+ class MyController extends AbstractController
+ {
+ #[IsGranted(static function (IsGrantedContext $context, mixed $subject) {
+ return $context->user === $subject['post']->getAuthor();
+ }, subject: static function (array $args) {
+ return [
+ 'post' => $args['post'],
+ ];
+ })]
+ public function index($post): Response
+ {
+ // ...
+ }
+ }
+
+.. versionadded:: 7.3
+
+ The support for closures in the ``#[IsGranted]`` attribute was introduced
+ in Symfony 7.3 and requires PHP 8.5.
+
Learn more
----------
diff --git a/security/ldap.rst b/security/ldap.rst
index 081be764290..c4c3646122b 100644
--- a/security/ldap.rst
+++ b/security/ldap.rst
@@ -256,6 +256,24 @@ This is the default role you wish to give to a user fetched from the LDAP
server. If you do not configure this key, your users won't have any roles,
and will not be considered as authenticated fully.
+role_fetcher
+............
+
+**Type**: ``string`` **Default**: ``null``
+
+When your LDAP service provides user roles, this option allows you to define
+the service that retrieves these roles. The role fetcher service must implement
+the ``Symfony\Component\Ldap\Security\RoleFetcherInterface``. When this option
+is set, the ``default_roles`` option is ignored.
+
+Symfony provides ``Symfony\Component\Ldap\Security\MemberOfRoles``, a concrete
+implementation of the interface that fetches roles from the ``ismemberof``
+attribute.
+
+.. versionadded:: 7.3
+
+ The ``role_fetcher`` configuration option was introduced in Symfony 7.3.
+
uid_key
.......
diff --git a/serializer.rst b/serializer.rst
index 8b7b05d9e74..4dd689a5ab5 100644
--- a/serializer.rst
+++ b/serializer.rst
@@ -1387,6 +1387,14 @@ normalizers (in order of priority):
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\\NumberNormalizer`
+ This normalizer converts between :phpclass:`BcMath\\Number` or :phpclass:`GMP` objects and
+ strings or integers.
+
+.. versionadded:: 7.2
+
+ The ``NumberNormalizer`` was introduced in Symfony 7.2.
+
:class:`Symfony\\Component\\Serializer\\Normalizer\\DataUriNormalizer`
This normalizer converts between :phpclass:`SplFileInfo` objects and a
`data URI`_ string (``data:...``) such that files can be embedded into
@@ -2376,6 +2384,70 @@ correct class for properties typed as ``InvoiceItemInterface``::
$invoiceLine = $serializer->deserialize($jsonString, InvoiceLine::class, 'json');
// $invoiceLine contains new InvoiceLine(new Product(...))
+You can add a default type to avoid the need to add the type property
+when deserializing:
+
+.. configuration-block::
+
+ .. code-block:: php-attributes
+
+ namespace App\Model;
+
+ use Symfony\Component\Serializer\Attribute\DiscriminatorMap;
+
+ #[DiscriminatorMap(
+ typeProperty: 'type',
+ mapping: [
+ 'product' => Product::class,
+ 'shipping' => Shipping::class,
+ ],
+ defaultType: 'product',
+ )]
+ interface InvoiceItemInterface
+ {
+ // ...
+ }
+
+ .. code-block:: yaml
+
+ App\Model\InvoiceItemInterface:
+ discriminator_map:
+ type_property: type
+ mapping:
+ product: 'App\Model\Product'
+ shipping: 'App\Model\Shipping'
+ default_type: product
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+
+Now it deserializes like this:
+
+.. configuration-block::
+
+ .. code-block:: php
+
+ // $jsonString does NOT contain "type" in "invoiceItem"
+ $invoiceLine = $serializer->deserialize('{"invoiceItem":{...},...}', InvoiceLine::class, 'json');
+ // $invoiceLine contains new InvoiceLine(new Product(...))
+
+.. versionadded:: 7.3
+
+ The ``defaultType`` parameter was added in Symfony 7.3.
+
.. _serializer-unwrapping-denormalizer:
Deserializing Input Partially (Unwrapping)
diff --git a/serializer/encoders.rst b/serializer/encoders.rst
index a11daaa7bad..d2cf1f9cab8 100644
--- a/serializer/encoders.rst
+++ b/serializer/encoders.rst
@@ -205,11 +205,16 @@ These are the options available on the :ref:`serializer context &]/``)
A regular expression pattern to determine if a value should be wrapped
in a CDATA section.
+``ignore_empty_attributes`` (default: ``false``)
+ If set to true, ignores all attributes with empty values in the generated XML
.. versionadded:: 7.1
The ``cdata_wrapping_pattern`` option was introduced in Symfony 7.1.
+.. versionadded:: 7.3
+
+ The ``ignore_empty_attributes`` option was introduced in Symfony 7.3.
Example with a custom ``context``::
diff --git a/service_container.rst b/service_container.rst
index 30b69b8aa14..6086ae1d946 100644
--- a/service_container.rst
+++ b/service_container.rst
@@ -162,10 +162,6 @@ each time you ask for it.
# this creates a service per class whose id is the fully-qualified class name
App\:
resource: '../src/'
- exclude:
- - '../src/DependencyInjection/'
- - '../src/Entity/'
- - '../src/Kernel.php'
# order is important in this file because service definitions
# always *replace* previous ones; add your own service configuration below
@@ -187,7 +183,7 @@ each time you ask for it.
-
+
@@ -212,8 +208,7 @@ each time you ask for it.
// makes classes in src/ available to be used as services
// this creates a service per class whose id is the fully-qualified class name
- $services->load('App\\', '../src/')
- ->exclude('../src/{DependencyInjection,Entity,Kernel.php}');
+ $services->load('App\\', '../src/');
// order is important in this file because service definitions
// always *replace* previous ones; add your own service configuration below
@@ -221,15 +216,57 @@ each time you ask for it.
.. tip::
- The value of the ``resource`` and ``exclude`` options can be any valid
- `glob pattern`_. The value of the ``exclude`` option can also be an
- array of glob patterns.
+ The value of the ``resource`` option can be any valid `glob pattern`_.
Thanks to this configuration, you can automatically use any classes from the
``src/`` directory as a service, without needing to manually configure
it. Later, you'll learn how to :ref:`import many services at once
` with resource.
+ If some files or directories in your project should not become services, you
+ can exclude them using the ``exclude`` option:
+
+ .. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/services.yaml
+ services:
+ # ...
+ App\:
+ resource: '../src/'
+ exclude:
+ - '../src/SomeDirectory/'
+ - '../src/AnotherDirectory/'
+ - '../src/SomeFile.php'
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+
+ .. code-block:: php
+
+ // config/services.php
+ namespace Symfony\Component\DependencyInjection\Loader\Configurator;
+
+ return function(ContainerConfigurator $container): void {
+ // ...
+
+ $services->load('App\\', '../src/')
+ ->exclude('../src/{SomeDirectory,AnotherDirectory,Kernel.php}');
+ };
+
If you'd prefer to manually wire your service, you can
:ref:`use explicit configuration `.
diff --git a/service_container/debug.rst b/service_container/debug.rst
index c09413e7213..9e3e28a5343 100644
--- a/service_container/debug.rst
+++ b/service_container/debug.rst
@@ -52,5 +52,8 @@ its id:
$ php bin/console debug:container App\Service\Mailer
- # to show the service arguments:
- $ php bin/console debug:container App\Service\Mailer --show-arguments
+.. deprecated:: 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.
diff --git a/service_container/import.rst b/service_container/import.rst
index d5056032115..293cb5b97c2 100644
--- a/service_container/import.rst
+++ b/service_container/import.rst
@@ -82,7 +82,6 @@ a relative or absolute path to the imported file:
App\:
resource: '../src/*'
- exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}'
# ...
@@ -104,8 +103,7 @@ a relative or absolute path to the imported file:
-
+
@@ -127,8 +125,7 @@ a relative or absolute path to the imported file:
->autoconfigure()
;
- $services->load('App\\', '../src/*')
- ->exclude('../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}');
+ $services->load('App\\', '../src/*');
};
When loading a configuration file, Symfony loads first the imported files and
diff --git a/service_container/service_closures.rst b/service_container/service_closures.rst
index cedbaaa2bf9..88b0ab64002 100644
--- a/service_container/service_closures.rst
+++ b/service_container/service_closures.rst
@@ -52,6 +52,13 @@ argument of type ``service_closure``:
# In case the dependency is optional
# arguments: [!service_closure '@?mailer']
+ # you can also use the special '@>' syntax as a shortcut of '!service_closure'
+ App\Service\AnotherService:
+ arguments: ['@>mailer']
+
+ # the shortcut also works for optional dependencies
+ # arguments: ['@>?mailer']
+
.. code-block:: xml
@@ -90,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 `
diff --git a/service_container/tags.rst b/service_container/tags.rst
index fab25ea910a..711041d98e4 100644
--- a/service_container/tags.rst
+++ b/service_container/tags.rst
@@ -1289,4 +1289,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
diff --git a/setup.rst b/setup.rst
index 6a75dd74315..a1fe9669a6e 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
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::
diff --git a/templates.rst b/templates.rst
index a93bbe742c6..c6312abb33c 100644
--- a/templates.rst
+++ b/templates.rst
@@ -870,7 +870,6 @@ errors. It's useful to run it before deploying your application to production
$ php bin/console lint:twig templates/article/recent_list.html.twig
# you can also show the deprecated features used in your templates
- # (only the first deprecation is shown, so run multiple times to catch all)
$ php bin/console lint:twig --show-deprecations templates/email/
# you can also excludes directories
@@ -880,6 +879,11 @@ errors. It's useful to run it before deploying your application to production
The option to exclude directories was introduced in Symfony 7.1.
+.. versionadded:: 7.3
+
+ Before Symfony 7.3, the ``--show-deprecations`` option only displayed the
+ first deprecation found, so you had to run the command repeatedly.
+
When running the linter inside `GitHub Actions`_, the output is automatically
adapted to the format required by GitHub, but you can force that format too:
diff --git a/translation.rst b/translation.rst
index c8996c08b48..86282090801 100644
--- a/translation.rst
+++ b/translation.rst
@@ -416,6 +416,84 @@ You can also specify the message domain and pass some additional variables:
major difference: automatic output escaping is **not** applied to translations
using a tag.
+Global Translation Parameters
+-----------------------------
+
+.. versionadded:: 7.3
+
+ The global translation parameters feature was introduced in Symfony 7.3.
+
+If the content of a translation parameter is repeated across multiple
+translation messages (e.g. a company name, or a version number), you can define
+it as a global translation parameter. This helps you avoid repeating the same
+values manually in each message.
+
+You can configure these global parameters in the ``translations.globals`` option
+of your main configuration file using either ``%...%`` or ``{...}`` syntax:
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/translator.yaml
+ translator:
+ # ...
+ globals:
+ # when using the '%' wrapping characters, you must escape them
+ '%%app_name%%': 'My application'
+ '{app_version}': '1.2.3'
+ '{url}': { message: 'url', parameters: { scheme: 'https://' }, domain: 'global' }
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+
+ My application
+
+
+ https://
+
+
+
+
+
+ .. code-block:: php
+
+ // config/packages/translator.php
+ use Symfony\Config\TwigConfig;
+
+ return static function (TwigConfig $translator): void {
+ // ...
+ // when using the '%' wrapping characters, you must escape them
+ $translator->globals('%%app_name%%')->value('My application');
+ $translator->globals('{app_version}')->value('1.2.3');
+ $translator->globals('{url}')->value(['message' => 'url', 'parameters' => ['scheme' => 'https://']]);
+ };
+
+Once defined, you can use these parameters in translation messages anywhere in
+your application:
+
+.. code-block:: twig
+
+ {{ 'Application version: {version}'|trans }}
+ {# output: "Application version: 1.2.3" #}
+
+ {# parameters passed to the message override global parameters #}
+ {{ 'Package version: {version}'|trans({'{version}': '2.3.4'}) }}
+ # Displays "Package version: 2.3.4"
+
Forcing the Translator Locale
-----------------------------
diff --git a/validation.rst b/validation.rst
index 4905283b18b..cfa8154b627 100644
--- a/validation.rst
+++ b/validation.rst
@@ -327,99 +327,13 @@ literature genre mostly associated with the author, which can be set to either
{
// ...
- $metadata->addPropertyConstraint('genre', new Assert\Choice([
- 'choices' => ['fiction', 'non-fiction'],
- 'message' => 'Choose a valid genre.',
- ]));
+ $metadata->addPropertyConstraint('genre', new Assert\Choice(
+ choices: ['fiction', 'non-fiction'],
+ message: 'Choose a valid genre.',
+ ));
}
}
-.. _validation-default-option:
-
-The options of a constraint can always be passed in as an array. Some constraints,
-however, also allow you to pass the value of one, "*default*", option in place
-of the array. In the case of the ``Choice`` constraint, the ``choices``
-options can be specified in this way.
-
-.. configuration-block::
-
- .. code-block:: php-attributes
-
- // src/Entity/Author.php
- namespace App\Entity;
-
- // ...
- use Symfony\Component\Validator\Constraints as Assert;
-
- class Author
- {
- #[Assert\Choice(['fiction', 'non-fiction'])]
- private string $genre;
-
- // ...
- }
-
- .. code-block:: yaml
-
- # config/validator/validation.yaml
- App\Entity\Author:
- properties:
- genre:
- - Choice: [fiction, non-fiction]
- # ...
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
- fiction
- non-fiction
-
-
-
-
-
-
-
- .. 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
- {
- private string $genre;
-
- public static function loadValidatorMetadata(ClassMetadata $metadata): void
- {
- // ...
-
- $metadata->addPropertyConstraint(
- 'genre',
- new Assert\Choice(['fiction', 'non-fiction'])
- );
- }
- }
-
-This is purely meant to make the configuration of the most common option of
-a constraint shorter and quicker.
-
-If you're ever unsure of how to specify an option, either check the namespace
-``Symfony\Component\Validator\Constraints`` for the constraint or play it safe
-by always passing in an array of options (the first method shown above).
-
Constraints in Form Classes
---------------------------
@@ -520,7 +434,7 @@ class to have at least 3 characters.
$metadata->addPropertyConstraint('firstName', new Assert\NotBlank());
$metadata->addPropertyConstraint(
'firstName',
- new Assert\Length(['min' => 3])
+ new Assert\Length(min: 3)
);
}
}
@@ -603,9 +517,9 @@ this method must return ``true``:
{
public static function loadValidatorMetadata(ClassMetadata $metadata): void
{
- $metadata->addGetterConstraint('passwordSafe', new Assert\IsTrue([
- 'message' => 'The password cannot match your first name',
- ]));
+ $metadata->addGetterConstraint('passwordSafe', new Assert\IsTrue(
+ message: 'The password cannot match your first name',
+ ));
}
}
diff --git a/validation/custom_constraint.rst b/validation/custom_constraint.rst
index 08ed86fb4ce..bb34775a71c 100644
--- a/validation/custom_constraint.rst
+++ b/validation/custom_constraint.rst
@@ -248,7 +248,7 @@ You can use custom validators like the ones provided by Symfony itself:
public static function loadValidatorMetadata(ClassMetadata $metadata): void
{
$metadata->addPropertyConstraint('name', new NotBlank());
- $metadata->addPropertyConstraint('name', new ContainsAlphanumeric(['mode' => 'loose']));
+ $metadata->addPropertyConstraint('name', new ContainsAlphanumeric(mode: 'loose'));
}
}
@@ -273,6 +273,7 @@ define those options as public properties on the constraint class::
// src/Validator/Foo.php
namespace App\Validator;
+ use Symfony\Component\Validator\Attribute\HasNamedArguments;
use Symfony\Component\Validator\Constraint;
#[\Attribute]
@@ -282,6 +283,7 @@ define those options as public properties on the constraint class::
public $message = 'This value is invalid';
public $optionalBarOption = false;
+ #[HasNamedArguments]
public function __construct(
$mandatoryFooOption,
?string $message = null,
@@ -402,10 +404,10 @@ the custom options like you pass any other option in built-in constraints:
public static function loadValidatorMetadata(ClassMetadata $metadata)
{
$metadata->addPropertyConstraint('name', new NotBlank());
- $metadata->addPropertyConstraint('name', new Foo([
- 'mandatoryFooOption' => 'bar',
- 'optionalBarOption' => true,
- ]));
+ $metadata->addPropertyConstraint('name', new Foo(
+ mandatoryFooOption: 'bar',
+ optionalBarOption: true,
+ ));
}
}
diff --git a/validation/groups.rst b/validation/groups.rst
index 8d84e52c0da..55625be702d 100644
--- a/validation/groups.rst
+++ b/validation/groups.rst
@@ -101,21 +101,21 @@ user registers and when a user updates their contact information later:
{
public static function loadValidatorMetadata(ClassMetadata $metadata): void
{
- $metadata->addPropertyConstraint('email', new Assert\Email([
- 'groups' => ['registration'],
- ]));
-
- $metadata->addPropertyConstraint('password', new Assert\NotBlank([
- 'groups' => ['registration'],
- ]));
- $metadata->addPropertyConstraint('password', new Assert\Length([
- 'min' => 7,
- 'groups' => ['registration'],
- ]));
-
- $metadata->addPropertyConstraint('city', new Assert\Length([
- 'min' => 2,
- ]));
+ $metadata->addPropertyConstraint('email', new Assert\Email(
+ groups: ['registration'],
+ ));
+
+ $metadata->addPropertyConstraint('password', new Assert\NotBlank(
+ groups: ['registration'],
+ ));
+ $metadata->addPropertyConstraint('password', new Assert\Length(
+ min: 7,
+ groups: ['registration'],
+ ));
+
+ $metadata->addPropertyConstraint('city', new Assert\Length(
+ min: 2,
+ ));
}
}
diff --git a/validation/sequence_provider.rst b/validation/sequence_provider.rst
index 836568c2327..c316a85d249 100644
--- a/validation/sequence_provider.rst
+++ b/validation/sequence_provider.rst
@@ -104,10 +104,10 @@ username and the password are different only if all other validation passes
$metadata->addPropertyConstraint('username', new Assert\NotBlank());
$metadata->addPropertyConstraint('password', new Assert\NotBlank());
- $metadata->addGetterConstraint('passwordSafe', new Assert\IsTrue([
- 'message' => 'The password cannot match your first name',
- 'groups' => ['Strict'],
- ]));
+ $metadata->addGetterConstraint('passwordSafe', new Assert\IsTrue(
+ message: 'The password cannot match your first name',
+ groups: ['Strict'],
+ ));
$metadata->setGroupSequence(['User', 'Strict']);
}
@@ -249,10 +249,10 @@ entity and a new constraint group called ``Premium``:
public static function loadValidatorMetadata(ClassMetadata $metadata): void
{
$metadata->addPropertyConstraint('name', new Assert\NotBlank());
- $metadata->addPropertyConstraint('creditCard', new Assert\CardScheme([
- 'schemes' => [Assert\CardScheme::VISA],
- 'groups' => ['Premium'],
- ]));
+ $metadata->addPropertyConstraint('creditCard', new Assert\CardScheme(
+ schemes: [Assert\CardScheme::VISA],
+ groups: ['Premium'],
+ ));
}
}
diff --git a/validation/severity.rst b/validation/severity.rst
index 632a99519d9..154c13d5e3e 100644
--- a/validation/severity.rst
+++ b/validation/severity.rst
@@ -105,15 +105,15 @@ Use the ``payload`` option to configure the error level for each constraint:
public static function loadValidatorMetadata(ClassMetadata $metadata): void
{
- $metadata->addPropertyConstraint('username', new Assert\NotBlank([
- 'payload' => ['severity' => 'error'],
- ]));
- $metadata->addPropertyConstraint('password', new Assert\NotBlank([
- 'payload' => ['severity' => 'error'],
- ]));
- $metadata->addPropertyConstraint('bankAccountNumber', new Assert\Iban([
- 'payload' => ['severity' => 'warning'],
- ]));
+ $metadata->addPropertyConstraint('username', new Assert\NotBlank(
+ payload: ['severity' => 'error'],
+ ));
+ $metadata->addPropertyConstraint('password', new Assert\NotBlank(
+ payload: ['severity' => 'error'],
+ ));
+ $metadata->addPropertyConstraint('bankAccountNumber', new Assert\Iban(
+ payload: ['severity' => 'warning'],
+ ));
}
}
diff --git a/validation/translations.rst b/validation/translations.rst
index 9a4ece17736..db2cd518eb7 100644
--- a/validation/translations.rst
+++ b/validation/translations.rst
@@ -83,9 +83,9 @@ property is not empty, add the following:
public static function loadValidatorMetadata(ClassMetadata $metadata): void
{
- $metadata->addPropertyConstraint('name', new NotBlank([
- 'message' => 'author.name.not_blank',
- ]));
+ $metadata->addPropertyConstraint('name', new NotBlank(
+ message: 'author.name.not_blank',
+ ));
}
}
@@ -136,13 +136,13 @@ You can also use :class:`Symfony\\Component\\Translation\\TranslatableMessage` t
use Symfony\Component\Translation\TranslatableMessage;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
-
+
#[Assert\Callback]
public function validate(ExecutionContextInterface $context, mixed $payload): void
{
// somehow you have an array of "fake names"
$fakeNames = [/* ... */];
-
+
// check if the name is actually a fake name
if (in_array($this->getFirstName(), $fakeNames, true)) {
$context->buildViolation(new TranslatableMessage('author.name.fake', [], 'validators'))
diff --git a/webhook.rst b/webhook.rst
index 9d6b56c7ef0..6e9408c12eb 100644
--- a/webhook.rst
+++ b/webhook.rst
@@ -28,6 +28,7 @@ Currently, the following third-party mailer providers support webhooks:
============== ============================================
Mailer Service Parser service name
============== ============================================
+AhaSend ``mailer.webhook.request_parser.ahasend``
Brevo ``mailer.webhook.request_parser.brevo``
Mandrill ``mailer.webhook.request_parser.mailchimp``
MailerSend ``mailer.webhook.request_parser.mailersend``
@@ -47,9 +48,13 @@ Sweego ``mailer.webhook.request_parser.sweego``
.. versionadded:: 7.2
- The ``Mandrill``, ``Mailomat``, ``Mailtrap``, and ``Sweego`` integrations were introduced in
+ The ``Mandrill``, ``Mailomat``, ``Mailtrap``, and ``Sweego`` integrations were introduced in
Symfony 7.2.
+.. versionadded:: 7.3
+
+ The ``AhaSend`` integration was introduced in Symfony 7.3.
+
.. note::
Install the third-party mailer provider you want to use as described in the
@@ -171,6 +176,7 @@ Currently, the following third-party SMS transports support webhooks:
SMS service Parser service name
============ ==========================================
Twilio ``notifier.webhook.request_parser.twilio``
+Smsbox ``notifier.webhook.request_parser.smsbox``
Sweego ``notifier.webhook.request_parser.sweego``
Vonage ``notifier.webhook.request_parser.vonage``
============ ==========================================