[Validator] Add a constraint to sequentially validate a set of constraints
This commit is contained in:
parent
83a53a5edf
commit
dfd9038d28
@ -6,6 +6,7 @@ CHANGELOG
|
|||||||
|
|
||||||
* added the `Hostname` constraint and validator
|
* added the `Hostname` constraint and validator
|
||||||
* added option `alpha3` to `Country` constraint
|
* added option `alpha3` to `Country` constraint
|
||||||
|
* added `Sequentially` constraint, to sequentially validate a set of constraints (any violation raised will prevent further validation of the nested constraints)
|
||||||
|
|
||||||
5.0.0
|
5.0.0
|
||||||
-----
|
-----
|
||||||
|
41
src/Symfony/Component/Validator/Constraints/Sequentially.php
Normal file
41
src/Symfony/Component/Validator/Constraints/Sequentially.php
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\Validator\Constraints;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this constraint to sequentially validate nested constraints.
|
||||||
|
* Validation for the nested constraints collection will stop at first violation.
|
||||||
|
*
|
||||||
|
* @Annotation
|
||||||
|
* @Target({"PROPERTY", "METHOD", "ANNOTATION"})
|
||||||
|
*
|
||||||
|
* @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
|
||||||
|
*/
|
||||||
|
class Sequentially extends Composite
|
||||||
|
{
|
||||||
|
public $constraints = [];
|
||||||
|
|
||||||
|
public function getDefaultOption()
|
||||||
|
{
|
||||||
|
return 'constraints';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRequiredOptions()
|
||||||
|
{
|
||||||
|
return ['constraints'];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getCompositeOption()
|
||||||
|
{
|
||||||
|
return 'constraints';
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\Validator\Constraints;
|
||||||
|
|
||||||
|
use Symfony\Component\Validator\Constraint;
|
||||||
|
use Symfony\Component\Validator\ConstraintValidator;
|
||||||
|
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
|
||||||
|
*/
|
||||||
|
class SequentiallyValidator extends ConstraintValidator
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function validate($value, Constraint $constraint)
|
||||||
|
{
|
||||||
|
if (!$constraint instanceof Sequentially) {
|
||||||
|
throw new UnexpectedTypeException($constraint, Sequentially::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
$context = $this->context;
|
||||||
|
|
||||||
|
$validator = $context->getValidator()->inContext($context);
|
||||||
|
|
||||||
|
$originalCount = $validator->getViolations()->count();
|
||||||
|
|
||||||
|
foreach ($constraint->constraints as $c) {
|
||||||
|
if ($originalCount !== $validator->validate($value, $c)->getViolations()->count()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\Validator\Tests\Constraints;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Symfony\Component\Validator\Constraints\Sequentially;
|
||||||
|
use Symfony\Component\Validator\Constraints\Valid;
|
||||||
|
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
|
||||||
|
|
||||||
|
class SequentiallyTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testRejectNonConstraints()
|
||||||
|
{
|
||||||
|
$this->expectException(ConstraintDefinitionException::class);
|
||||||
|
$this->expectExceptionMessage('The value foo is not an instance of Constraint in constraint Symfony\Component\Validator\Constraints\Sequentially');
|
||||||
|
new Sequentially([
|
||||||
|
'foo',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRejectValidConstraint()
|
||||||
|
{
|
||||||
|
$this->expectException(ConstraintDefinitionException::class);
|
||||||
|
$this->expectExceptionMessage('The constraint Valid cannot be nested inside constraint Symfony\Component\Validator\Constraints\Sequentially');
|
||||||
|
new Sequentially([
|
||||||
|
new Valid(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,87 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\Validator\Tests\Constraints;
|
||||||
|
|
||||||
|
use Symfony\Component\Validator\Constraints\NotEqualTo;
|
||||||
|
use Symfony\Component\Validator\Constraints\Range;
|
||||||
|
use Symfony\Component\Validator\Constraints\Regex;
|
||||||
|
use Symfony\Component\Validator\Constraints\Sequentially;
|
||||||
|
use Symfony\Component\Validator\Constraints\SequentiallyValidator;
|
||||||
|
use Symfony\Component\Validator\Constraints\Type;
|
||||||
|
use Symfony\Component\Validator\ConstraintViolation;
|
||||||
|
use Symfony\Component\Validator\Test\ConstraintValidatorTestCase;
|
||||||
|
|
||||||
|
class SequentiallyValidatorTest extends ConstraintValidatorTestCase
|
||||||
|
{
|
||||||
|
protected function createValidator()
|
||||||
|
{
|
||||||
|
return new SequentiallyValidator();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testWalkThroughConstraints()
|
||||||
|
{
|
||||||
|
$constraints = [
|
||||||
|
new Type('number'),
|
||||||
|
new Range(['min' => 4]),
|
||||||
|
];
|
||||||
|
|
||||||
|
$value = 6;
|
||||||
|
|
||||||
|
$contextualValidator = $this->context->getValidator()->inContext($this->context);
|
||||||
|
$contextualValidator->expects($this->any())->method('getViolations')->willReturn($this->context->getViolations());
|
||||||
|
$contextualValidator->expects($this->exactly(2))
|
||||||
|
->method('validate')
|
||||||
|
->withConsecutive(
|
||||||
|
[$value, $constraints[0]],
|
||||||
|
[$value, $constraints[1]]
|
||||||
|
)
|
||||||
|
->willReturn($contextualValidator);
|
||||||
|
|
||||||
|
$this->validator->validate($value, new Sequentially($constraints));
|
||||||
|
|
||||||
|
$this->assertNoViolation();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testStopsAtFirstConstraintWithViolations()
|
||||||
|
{
|
||||||
|
$constraints = [
|
||||||
|
new Type('string'),
|
||||||
|
new Regex(['pattern' => '[a-z]']),
|
||||||
|
new NotEqualTo('Foo'),
|
||||||
|
];
|
||||||
|
|
||||||
|
$value = 'Foo';
|
||||||
|
|
||||||
|
$contextualValidator = $this->context->getValidator()->inContext($this->context);
|
||||||
|
$contextualValidator->expects($this->any())->method('getViolations')->willReturn($this->context->getViolations());
|
||||||
|
$contextualValidator->expects($this->exactly(2))
|
||||||
|
->method('validate')
|
||||||
|
->withConsecutive(
|
||||||
|
[$value, $constraints[0]],
|
||||||
|
[$value, $constraints[1]]
|
||||||
|
)
|
||||||
|
->will($this->onConsecutiveCalls(
|
||||||
|
// Noop, just return the validator:
|
||||||
|
$this->returnValue($contextualValidator),
|
||||||
|
// Add violation on second call:
|
||||||
|
$this->returnCallback(function () use ($contextualValidator) {
|
||||||
|
$this->context->getViolations()->add($violation = new ConstraintViolation('regex error', null, [], null, '', null, null, 'regex'));
|
||||||
|
|
||||||
|
return $contextualValidator;
|
||||||
|
}
|
||||||
|
)));
|
||||||
|
|
||||||
|
$this->validator->validate($value, new Sequentially($constraints));
|
||||||
|
|
||||||
|
$this->assertCount(1, $this->context->getViolations());
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user