.. index:: single: Validation; Group Sequences single: Validation; Group Sequence Providers How to Sequentially Apply Validation Groups =========================================== In some cases, you want to validate your groups by steps. To do this, you can use the ``GroupSequence`` feature. In this case, an object defines a group sequence, which determines the order groups should be validated. For example, suppose you have a ``User`` class and want to validate that the username and the password are different only if all other validation passes (in order to avoid multiple error messages). .. configuration-block:: .. code-block:: php-annotations // src/Entity/User.php namespace App\Entity; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Validator\Constraints as Assert; /** * @Assert\GroupSequence({"User", "Strict"}) */ class User implements UserInterface { /** * @Assert\NotBlank */ private $username; /** * @Assert\NotBlank */ private $password; /** * @Assert\IsTrue(message="The password cannot match your username", groups={"Strict"}) */ public function isPasswordSafe() { return ($this->username !== $this->password); } } .. code-block:: yaml # config/validator/validation.yaml App\Entity\User: group_sequence: - User - Strict getters: passwordSafe: - 'IsTrue': message: 'The password cannot match your username' groups: [Strict] properties: username: - NotBlank: ~ password: - NotBlank: ~ .. code-block:: xml User Strict .. code-block:: php // src/Entity/User.php namespace App\Entity; use Symfony\Component\Validator\Constraints as Assert; use Symfony\Component\Validator\Mapping\ClassMetadata; class User { public static function loadValidatorMetadata(ClassMetadata $metadata) { $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->setGroupSequence(['User', 'Strict']); } } In this example, it will first validate all constraints in the group ``User`` (which is the same as the ``Default`` group). Only if all constraints in that group are valid, the second group, ``Strict``, will be validated. .. caution:: As you have already seen in :doc:`/validation/groups`, the ``Default`` group and the group containing the class name (e.g. ``User``) were identical. However, when using Group Sequences, they are no longer identical. The ``Default`` group will now reference the group sequence, instead of all constraints that do not belong to any group. This means that you have to use the ``{ClassName}`` (e.g. ``User``) group when specifying a group sequence. When using ``Default``, you get an infinite recursion (as the ``Default`` group references the group sequence, which will contain the ``Default`` group which references the same group sequence, ...). .. caution:: Calling ``validate()`` with a group in the sequence (``Strict`` in previous example) will cause a validation **only** with that group and not with all the groups in the sequence. This is because sequence is now referred to ``Default`` group validation. You can also define a group sequence in the ``validation_groups`` form option:: use Symfony\Component\Form\AbstractType; use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\Validator\Constraints\GroupSequence; // ... class MyType extends AbstractType { // ... public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ 'validation_groups' => new GroupSequence(['First', 'Second']), ]); } } Group Sequence Providers ------------------------ Imagine a ``User`` entity which can be a normal user or a premium user. When it's a premium user, some extra constraints should be added to the user entity (e.g. the credit card details). To dynamically determine which groups should be activated, you can create a Group Sequence Provider. First, create the entity and a new constraint group called ``Premium``: .. configuration-block:: .. code-block:: php-annotations // src/Entity/User.php namespace App\Entity; use Symfony\Component\Validator\Constraints as Assert; class User { /** * @Assert\NotBlank */ private $name; /** * @Assert\CardScheme( * schemes={"VISA"}, * groups={"Premium"}, * ) */ private $creditCard; // ... } .. code-block:: yaml # config/validator/validation.yaml App\Entity\User: properties: name: - NotBlank: ~ creditCard: - CardScheme: schemes: [VISA] groups: [Premium] .. code-block:: xml .. code-block:: php // src/Entity/User.php namespace App\Entity; use Symfony\Component\Validator\Constraints as Assert; use Symfony\Component\Validator\Mapping\ClassMetadata; class User { private $name; private $creditCard; // ... public static function loadValidatorMetadata(ClassMetadata $metadata) { $metadata->addPropertyConstraint('name', new Assert\NotBlank()); $metadata->addPropertyConstraint('creditCard', new Assert\CardScheme([ 'schemes' => ['VISA'], 'groups' => ['Premium'], ])); } } Now, change the ``User`` class to implement :class:`Symfony\\Component\\Validator\\GroupSequenceProviderInterface` and add the :method:`Symfony\\Component\\Validator\\GroupSequenceProviderInterface::getGroupSequence`, method, which should return an array of groups to use:: // src/Entity/User.php namespace App\Entity; // ... use Symfony\Component\Validator\GroupSequenceProviderInterface; class User implements GroupSequenceProviderInterface { // ... public function getGroupSequence() { // when returning a simple array, if there's a violation in any group // the rest of groups are not validated. E.g. if 'User' fails, // 'Premium' and 'Api' are not validated: return ['User', 'Premium', 'Api']; // when returning a nested array, all the groups included in each array // are validated. E.g. if 'User' fails, 'Premium' is also validated // (and you'll get its violations too) but 'Api' won't be validated: return [['User', 'Premium'], 'Api']; } } At last, you have to notify the Validator component that your ``User`` class provides a sequence of groups to be validated: .. configuration-block:: .. code-block:: php-annotations // src/Entity/User.php namespace App\Entity; // ... /** * @Assert\GroupSequenceProvider */ class User implements GroupSequenceProviderInterface { // ... } .. code-block:: yaml # config/validator/validation.yaml App\Entity\User: group_sequence_provider: true .. code-block:: xml .. code-block:: php // src/Entity/User.php namespace App\Entity; // ... use Symfony\Component\Validator\Mapping\ClassMetadata; class User implements GroupSequenceProviderInterface { // ... public static function loadValidatorMetadata(ClassMetadata $metadata) { $metadata->setGroupSequenceProvider(true); // ... } } How to Sequentially Apply Constraints on a Single Property ---------------------------------------------------------- Sometimes, you may want to apply constraints sequentially on a single property. The :doc:`Sequentially constraint` can solve this for you in a more straightforward way than using a ``GroupSequence``. .. versionadded:: 5.1 The ``Sequentially`` constraint was introduced in Symfony 5.1.