Skip to content

[Validator] Add a constraint to sequentially validate a set of constraints #13206

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions reference/constraints.rst
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ Validation Constraints Reference
constraints/Isbn
constraints/Issn

constraints/Sequentially
constraints/Compound
constraints/Callback
constraints/Expression
Expand Down
142 changes: 142 additions & 0 deletions reference/constraints/Sequentially.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
Sequentially
============

This constraint allows you to apply a set of rules that should be validated
step-by-step, allowing to interrupt the validation once the first violation is raised.

As an alternative in situations ``Sequentially`` cannot solve, you may consider
using :doc:`GroupSequence</validation/sequence_provider>` which allows more control.

.. versionadded:: 5.1

The ``Sequentially`` constraint was introduced in Symfony 5.1.

========== ===================================================================
Applies to :ref:`property or method <validation-property-target>`
Options - `constraints`_
- `groups`_
- `payload`_
Class :class:`Symfony\\Component\\Validator\\Constraints\\Sequentially`
Validator :class:`Symfony\\Component\\Validator\\Constraints\\SequentiallyValidator`
========== ===================================================================

Basic Usage
-----------

Suppose that you have a ``Place`` object with an ``$address`` property which
must match the following requirements:
- it's a non-blank string
- of at least 10 chars long
- with a specific format
- and geolocalizable using an external service

In such situations, you may encounter three issues:
- the ``Length`` or ``Regex`` constraints may fail hard with a :class:`Symfony\\Component\\Validator\\Exception\\UnexpectedValueException`
exception if the actual value is not a string, as enforced by ``Type``.
- you may end with multiple error messages for the same property
- you may perform a useless and heavy external call to geolocalize the address,
while the format isn't valid.

You can validate each of these constraints sequentially to solve these issues:

.. configuration-block::

.. code-block:: php-annotations

// src/Localization/Place.php
namespace App\Localization;

use App\Validator\Constraints as AcmeAssert;
use Symfony\Component\Validator\Constraints as Assert;

class Place
{
/**
* @var string
*
* @Assert\Sequentially({
* @Assert\NotBlank(),
* @Assert\Type("string"),
* @Assert\Length(min=10),
* @Assert\Regex(Place::ADDRESS_REGEX),
Copy link
Contributor

@OskarStark OskarStark Feb 23, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One minor, do you have a regex pattern which can be added? Do we really need the regex here? If yes, should we add the constant to the code example?

But adding a regex is maybe distracting, could we inline the pattern or is it anyway to complex?

What do you think of removing it completely from the example?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer to avoid adding a regex pattern. I used (but didn't show) a constant for the reason mentioned: the regex isn't important on its own and would be distracting.
I think the @Regex in the example deserve a purpose to demonstrate the step-by-step application of the rules, as it'll usually fail hard despite being used conjointly with Type without the Sequential constraint. But @Length demonstrate the same, so if you really prefer, I can remove it indeed.

* @AcmeAssert\Geolocalizable(),
* })
*/
public $address;
}

.. code-block:: yaml

# config/validator/validation.yaml
App\Localization\Place:
properties:
address:
- Sequentially:
- NotBlank: ~
- Type: string
- Length: { min: 10 }
- Regex: !php/const App\Localization\Place::ADDRESS_REGEX
- App\Validator\Constraints\Geolocalizable: ~

.. code-block:: xml

<!-- config/validator/validation.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping https://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd">

<class name="App\Localization\Place">
<property name="address">
<constraint name="Sequentially">
<constraint name="NotBlank"/>
<constraint name="Type">string</constraint>
<constraint name="Length">
<option name="min">10</option>
</constraint>
<constraint name="Regex">
<option name="pattern">/address-regex/</option>
</constraint>
<constraint name="App\Validator\Constraints\Geolocalizable"/>
</constraint>
</property>
</class>
</constraint-mapping>

.. code-block:: php

// src/Localization/Place.php
namespace App\Localization;

use App\Validator\Constraints as AcmeAssert;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\Mapping\ClassMetadata;

class Place
{
public static function loadValidatorMetadata(ClassMetadata $metadata)
{
$metadata->addPropertyConstraint('address', new Assert\Sequentially([
new Assert\NotBlank(),
new Assert\Type("string"),
new Assert\Length(['min' => 10]),
new Assert\Regex(self::ADDRESS_REGEX),
new AcmeAssert\Geolocalizable(),
]));
}
}

Options
-------

``constraints``
~~~~~~~~~~~~~~~

**type**: ``array`` [:ref:`default option <validation-default-option>`]

This required option is the array of validation constraints that you want
to apply sequentially.

.. include:: /reference/constraints/_groups-option.rst.inc

.. include:: /reference/constraints/_payload-option.rst.inc
1 change: 1 addition & 0 deletions reference/constraints/map.rst.inc
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ Financial and other Number Constraints
Other Constraints
~~~~~~~~~~~~~~~~~

* :doc:`Sequentially </reference/constraints/Sequentially>`
* :doc:`Compound </reference/constraints/Compound>`
* :doc:`Callback </reference/constraints/Callback>`
* :doc:`Expression </reference/constraints/Expression>`
Expand Down
11 changes: 11 additions & 0 deletions validation/sequence_provider.rst
Original file line number Diff line number Diff line change
Expand Up @@ -355,3 +355,14 @@ provides a sequence of groups to be validated:
// ...
}
}

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</reference/constraints/Sequentially>`
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.