Merge branch '7.2' into 7.3

* 7.2:
  [Form][Validator] Merge all articles about using validation groups in forms
This commit is contained in:
Javier Eguiluz
2025-07-04 08:41:12 +02:00
8 changed files with 157 additions and 263 deletions

View File

@@ -576,3 +576,6 @@
/components/serializer /serializer
/serializer/custom_encoder /serializer/encoders#serializer-custom-encoder
/components/string /string
/form/button_based_validation /form/validation_groups
/form/data_based_validation /form/validation_groups
/form/validation_group_service_resolver /form/validation_groups

View File

@@ -1,36 +0,0 @@
How to Choose Validation Groups Based on the Clicked Button
===========================================================
When your form contains multiple submit buttons, you can change the validation
group depending on which button is used to submit the form. For example,
consider a form in a wizard that lets you advance to the next step or go back
to the previous step. Also assume that when returning to the previous step,
the data of the form should be saved, but not validated.
First, we need to add the two buttons to the form::
$form = $this->createFormBuilder($task)
// ...
->add('nextStep', SubmitType::class)
->add('previousStep', SubmitType::class)
->getForm();
Then, we configure the button for returning to the previous step to run
specific validation groups. In this example, we want it to suppress validation,
so we set its ``validation_groups`` option to false::
$form = $this->createFormBuilder($task)
// ...
->add('previousStep', SubmitType::class, [
'validation_groups' => false,
])
->getForm();
Now the form will skip your validation constraints. It will still validate
basic integrity constraints, such as checking whether an uploaded file was too
large or whether you tried to submit text in a number field.
.. seealso::
To see how to use a service to resolve ``validation_groups`` dynamically
read the :doc:`/form/validation_group_service_resolver` article.

View File

@@ -1,72 +0,0 @@
How to Choose Validation Groups Based on the Submitted Data
===========================================================
If you need some advanced logic to determine the validation groups (e.g.
based on submitted data), you can set the ``validation_groups`` option
to an array callback::
use App\Entity\Client;
use Symfony\Component\OptionsResolver\OptionsResolver;
// ...
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'validation_groups' => [
Client::class,
'determineValidationGroups',
],
]);
}
This will call the static method ``determineValidationGroups()`` on the
``Client`` class after the form is submitted, but before validation is
invoked. The Form object is passed as an argument to that method (see next
example). You can also define whole logic inline by using a ``Closure``::
use App\Entity\Client;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
// ...
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'validation_groups' => function (FormInterface $form): array {
$data = $form->getData();
if (Client::TYPE_PERSON == $data->getType()) {
return ['person'];
}
return ['company'];
},
]);
}
Using the ``validation_groups`` option overrides the default validation
group which is being used. If you want to validate the default constraints
of the entity as well you have to adjust the option as follows::
use App\Entity\Client;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
// ...
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'validation_groups' => function (FormInterface $form): array {
$data = $form->getData();
if (Client::TYPE_PERSON == $data->getType()) {
return ['Default', 'person'];
}
return ['Default', 'company'];
},
]);
}
You can find more information about how the validation groups and the default constraints
work in the article about :doc:`validation groups </validation/groups>`.

View File

@@ -1,58 +0,0 @@
How to Dynamically Configure Form Validation Groups
===================================================
Sometimes you need advanced logic to determine the validation groups. If they
can't be determined by a callback, you can use a service. Create a service
that implements ``__invoke()`` which accepts a ``FormInterface`` as a
parameter::
// src/Validation/ValidationGroupResolver.php
namespace App\Validation;
use Symfony\Component\Form\FormInterface;
class ValidationGroupResolver
{
public function __construct(
private object $service1,
private object $service2,
) {
}
public function __invoke(FormInterface $form): array
{
$groups = [];
// ... determine which groups to apply and return an array
return $groups;
}
}
Then in your form, inject the resolver and set it as the ``validation_groups``::
// src/Form/MyClassType.php;
namespace App\Form;
use App\Validation\ValidationGroupResolver;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolver;
class MyClassType extends AbstractType
{
public function __construct(
private ValidationGroupResolver $groupResolver,
) {
}
// ...
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'validation_groups' => $this->groupResolver,
]);
}
}
This will result in the form validator invoking your group resolver to set the
validation groups returned when validating.

View File

@@ -1,19 +1,11 @@
How to Define the Validation Groups to Use
==========================================
Configuring Validation Groups in Forms
======================================
Validation Groups
-----------------
If the object handled in your form uses :doc:`validation groups </validation/groups>`,
you need to specify which validation group(s) the form should apply.
If your object takes advantage of :doc:`validation groups </validation/groups>`,
you'll need to specify which validation group(s) your form should use. Pass
this as an option when :ref:`creating forms in controllers <creating-forms-in-controllers>`::
$form = $this->createFormBuilder($user, [
'validation_groups' => ['registration'],
])->add(/* ... */);
When :ref:`creating forms in classes <creating-forms-in-classes>`, add the
following to the ``configureOptions()`` method::
To define them when :ref:`creating forms in classes <creating-forms-in-classes>`,
use the ``configureOptions()`` method::
use Symfony\Component\OptionsResolver\OptionsResolver;
@@ -25,15 +17,147 @@ following to the ``configureOptions()`` method::
]);
}
In both of these cases, *only* the ``registration`` validation group will
be used to validate the underlying object. To apply the ``registration``
group *and* all constraints that are not in a group, use::
When :ref:`creating forms in controllers <creating-forms-in-controllers>`, pass
it as a form option::
'validation_groups' => ['Default', 'registration']
$form = $this->createFormBuilder($user, [
'validation_groups' => ['registration'],
])->add(/* ... */);
In both cases, *only* the ``registration`` group will be used to validate the
object. To apply the ``registration`` group *and* all constraints not in any
other group, add the special ``Default`` group::
[
// ...
'validation_groups' => ['Default', 'registration'],
]
.. note::
You can choose any name for your validation groups, but Symfony recommends
using "lower snake case" names (e.g. ``foo_bar``) in contrast with the
automatic validation groups created by Symfony, which use "upper camel case"
(e.g. ``Default``, ``SomeClassName``).
You can use any name for your validation groups. Symfony recommends using
"lower snake case" (e.g. ``foo_bar``), while automatically generated
groups use "UpperCamelCase" (e.g. ``Default``, ``SomeClassName``).
Choosing Validation Groups Based on the Clicked Button
------------------------------------------------------
When your form has :doc:`multiple submit buttons </form/multiple_buttons>`, you
can change the validation group based on the clicked button. For example, in a
multi-step form like the following, you might want to skip validation when
returning to a previous step::
$form = $this->createFormBuilder($task)
// ...
->add('nextStep', SubmitType::class)
->add('previousStep', SubmitType::class)
->getForm();
To do so, configure the validation groups of the ``previousStep`` button to
``false``, which is a special value that skips validation::
$form = $this->createFormBuilder($task)
// ...
->add('previousStep', SubmitType::class, [
'validation_groups' => false,
])
->getForm();
Now the form will skip your validation constraints when that button is clicked.
It will still validate basic integrity constraints, such as checking whether an
uploaded file was too large or whether you tried to submit text in a number field.
Choosing Validation Groups Based on Submitted Data
--------------------------------------------------
To determine validation groups dynamically based on submitted data, use a
callback. This is called after the form is submitted, but before validation is
invoked. The callback receives the form object as its first argument::
use App\Entity\Client;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'validation_groups' => function (FormInterface $form): array {
$data = $form->getData();
if (Client::TYPE_PERSON === $data->getType()) {
return ['Default', 'person'];
}
return ['Default', 'company'];
},
]);
}
.. note::
Adding ``Default`` to the list of validation groups is common but not mandatory.
See the main :doc:`article about validation groups </validation/groups>` to
learn more about validation groups and the default constraints.
You can also pass a static class method callback::
'validation_groups' => [Client::class, 'determineValidationGroups']
Choosing Validation Groups via a Service
----------------------------------------
If validation group logic requires services or can't fit in a closure, use a
dedicated validation group resolver service. The class of this service must
be invokable and receives the form object as its first argument::
// src/Validation/ValidationGroupResolver.php
namespace App\Validation;
use Symfony\Component\Form\FormInterface;
class ValidationGroupResolver
{
public function __construct(
private object $service1,
private object $service2,
) {
}
public function __invoke(FormInterface $form): array
{
$groups = [];
// ... determine which groups to return
return $groups;
}
}
Then use the service in your form type::
namespace App\Form;
use App\Validation\ValidationGroupResolver;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolver;
class MyClassType extends AbstractType
{
public function __construct(
private ValidationGroupResolver $groupResolver,
) {
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'validation_groups' => $this->groupResolver,
]);
}
}
Learn More
----------
For more information about how validation groups work, see
:doc:`/validation/groups`.

View File

@@ -995,8 +995,6 @@ Validation:
:maxdepth: 1
/form/validation_groups
/form/validation_group_service_resolver
/form/button_based_validation
/form/disabling_validation
Misc.:

View File

@@ -1,59 +1,14 @@
``validation_groups``
~~~~~~~~~~~~~~~~~~~~~
**type**: ``array``, ``string``, ``callable``, :class:`Symfony\\Component\\Validator\\Constraints\\GroupSequence` or ``null`` **default**: ``null``
**type**: ``array``, ``string``, ``callable``, :class:`Symfony\\Component\\Validator\\Constraints\\GroupSequence`, or ``null`` **default**: ``null``
This option is only valid on the root form and is used to specify which
groups will be used by the validator.
This option is only valid on the root form. It specifies which validation groups
will be used by the validator.
For ``null`` the validator will just use the ``Default`` group.
If you specify the groups as an array or string they will be used by the
validator as they are::
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'validation_groups' => 'Registration',
]);
}
This is equivalent to passing the group as array::
'validation_groups' => ['Registration'],
The form's data will be :doc:`validated against all given groups </form/validation_groups>`.
If the validation groups depend on the form's data a callable may be passed to
the option. Symfony will then pass the form when calling it::
use Symfony\Component\Form\FormInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
// ...
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'validation_groups' => function (FormInterface $form): array {
$entity = $form->getData();
return $entity->isUser() ? ['User'] : ['Company'];
},
]);
}
.. seealso::
You can read more about this in :doc:`/form/data_based_validation`.
.. note::
When your form contains multiple submit buttons, you can change the
validation group depending on :doc:`which button is used </form/button_based_validation>`
to submit the form.
If you need advanced logic to determine the validation groups have
a look at :doc:`/form/validation_group_service_resolver`.
If set to ``null``, the validator will use only the ``Default`` group. For the
other possible values, see the main article about
:doc:`using validation groups in Symfony forms </form/validation_groups>`
In some cases, you want to validate your groups step by step. To do this, you
can pass a :class:`Symfony\\Component\\Validator\\Constraints\\GroupSequence`

View File

@@ -102,28 +102,8 @@ validation_groups
**type**: ``array`` **default**: ``null``
When your form contains multiple submit buttons, you can change the validation
group based on the button which was used to submit the form. Imagine a registration
form wizard with buttons to go to the previous or the next step::
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
// ...
$form = $this->createFormBuilder($user)
->add('previousStep', SubmitType::class, [
'validation_groups' => false,
])
->add('nextStep', SubmitType::class, [
'validation_groups' => ['Registration'],
])
->getForm();
The special ``false`` ensures that no validation is performed when the previous
step button is clicked. When the second button is clicked, all constraints
from the "Registration" are validated.
.. seealso::
You can read more about this in :doc:`/form/data_based_validation`.
group based on the clicked button. Read the article about
:doc:`using validation groups in Symfony forms </form/validation_groups>`.
Form Variables
--------------