Skip to content

Commit 644e47d

Browse files
Oleg Stepuraolegstepura
Oleg Stepura
authored andcommitted
Added AnyOf Validation constraint and validator
1 parent 0f5af86 commit 644e47d

File tree

3 files changed

+236
-0
lines changed

3 files changed

+236
-0
lines changed
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Validator\Constraints;
13+
14+
use Symfony\Component\Validator\Constraint;
15+
16+
/**
17+
* @Annotation
18+
*
19+
* @api
20+
*
21+
* @author Oleg Stepura <github@oleg.stepura.com>
22+
*/
23+
class AnyOf extends Constraint
24+
{
25+
public $message = 'Value "{{ value }}" did not pass validation against any of constraints in the list [{{ constraints }}]';
26+
27+
/**
28+
* @var array
29+
*/
30+
public $constraints = array();
31+
32+
/**
33+
* @return string
34+
*/
35+
public function getDefaultOption()
36+
{
37+
return 'constraints';
38+
}
39+
40+
/**
41+
* @return array
42+
*/
43+
public function getRequiredOptions()
44+
{
45+
return array('constraints');
46+
}
47+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Validator\Constraints;
13+
14+
use Symfony\Component\Validator\Constraint;
15+
use Symfony\Component\Validator\ConstraintValidator;
16+
17+
/**
18+
* AnyValidator class.
19+
*
20+
* Validates against all constraint and fires a violation
21+
* only if not validated against any of given constraint.
22+
* If successfully validated against at least one constraint
23+
* value is considered to be valid.
24+
*
25+
* @author Oleg Stepura <github@oleg.stepura.com>
26+
*/
27+
class AnyOfValidator extends ConstraintValidator
28+
{
29+
/**
30+
* {@inheritDoc}
31+
*/
32+
public function validate($value, Constraint $constraint)
33+
{
34+
$violationListBefore = clone $this->context->getViolations();
35+
36+
// cannot simply cast to array, because then the object is converted to an
37+
// array instead of wrapped inside
38+
$constraints = is_array($constraint->constraints) ? $constraint->constraints : array($constraint->constraints);
39+
40+
$walker = $this->context->getGraphWalker();
41+
42+
$violationCountPrevious = $violationListBefore->count();
43+
$violationListAfter = $this->context->getViolations();
44+
$validationFailed = true;
45+
46+
foreach ($constraints as $constr) {
47+
$walker->walkConstraint($constr, $value, '', '');
48+
$violationCount = $violationListAfter->count();
49+
50+
if ($violationCount === $violationCountPrevious) {
51+
// At least one constraint did not fail
52+
$validationFailed = false;
53+
}
54+
55+
$violationCountPrevious = $violationCount;
56+
}
57+
58+
foreach ($violationListAfter as $id => $violation) {
59+
if (!$violationListBefore->has($id)) {
60+
$violationListAfter->remove($id);
61+
}
62+
}
63+
64+
if ($validationFailed) {
65+
$constraintClassList = array();
66+
foreach ($constraints as $constr) {
67+
$constraintClassList[] = get_class($constr);
68+
}
69+
70+
$this->context->addViolation(
71+
$constraint->message,
72+
array(
73+
'{{ value }}' => $value,
74+
'{{ constraints }}' => implode(', ', $constraintClassList)
75+
)
76+
);
77+
}
78+
}
79+
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Validator\Tests\Constraints;
13+
14+
use Symfony\Component\Validator\Constraints\AnyOf;
15+
use Symfony\Component\Validator\Constraints\MinLength;
16+
use Symfony\Component\Validator\Constraints\Null;
17+
use Symfony\Component\Validator\Constraints\AnyOfValidator;
18+
19+
class AnyOfValidatorTest extends \PHPUnit_Framework_TestCase
20+
{
21+
protected $walker;
22+
protected $context;
23+
protected $violations;
24+
protected $validator;
25+
protected $constraint;
26+
27+
protected function setUp()
28+
{
29+
$this->violations = $this->getMock('Symfony\Component\Validator\ConstraintViolationList', array(), array(), '', false);
30+
$this->context = $this->getMock('Symfony\Component\Validator\ExecutionContext', array(), array(), '', false);
31+
$this->walker = $this->getMock('Symfony\Component\Validator\GraphWalker', array(), array(), '', false);
32+
33+
$this->validator = new AnyOfValidator();
34+
$this->validator->initialize($this->context);
35+
$this->constraint = new AnyOf(
36+
array('constraints' => array(
37+
new MinLength(5),
38+
new Null
39+
))
40+
);
41+
42+
$this->context->expects($this->any())
43+
->method('getGraphWalker')
44+
->will($this->returnValue($this->walker));
45+
$this->context->expects($this->any())
46+
->method('getViolations')
47+
->will($this->returnValue($this->violations));
48+
$this->violations->expects($this->any())
49+
->method('has')
50+
->will($this->returnValue(false));
51+
52+
for ($i = 0; $i < count($this->constraint->constraints); $i ++) {
53+
$this->walker->expects($this->at($i))
54+
->method('walkConstraint');
55+
}
56+
57+
$this->violations->expects($this->once())
58+
->method('getIterator')
59+
->will($this->returnValue(new \ArrayIterator(array(1,2,3))));
60+
}
61+
62+
protected function tearDown()
63+
{
64+
$this->walker = null;
65+
$this->context = null;
66+
$this->validator = null;
67+
$this->violations = null;
68+
$this->constraint = null;
69+
}
70+
71+
public function testNoOneIsValid()
72+
{
73+
$value = '123';
74+
75+
$constraintsClasses =
76+
get_class($this->constraint->constraints[0]) . ', ' .
77+
get_class($this->constraint->constraints[1]);
78+
79+
$this->context->expects($this->once())
80+
->method('addViolation')
81+
->with(
82+
$this->constraint->message,
83+
array('{{ value }}' => $value, '{{ constraints }}' => $constraintsClasses)
84+
);
85+
86+
for ($i = 0; $i < 3; $i ++) {
87+
$this->violations->expects($this->at($i))
88+
->method('count')
89+
->will($this->returnValue($i));
90+
}
91+
92+
$this->validator->validate($value, $this->constraint);
93+
}
94+
95+
public function testOneIsValid()
96+
{
97+
$this->context->expects($this->never())
98+
->method('addViolation');
99+
100+
$this->validator->validate(null, $this->constraint);
101+
}
102+
103+
public function testSecondOneIsValid()
104+
{
105+
$this->context->expects($this->never())
106+
->method('addViolation');
107+
108+
$this->validator->validate('123456', $this->constraint);
109+
}
110+
}

0 commit comments

Comments
 (0)