Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit e6209a6

Browse files
przemyslaw-boguszfabpot
authored andcommitted
[Validator] Add AtLeastOne constraint and validator
1 parent 421c7f8 commit e6209a6

File tree

5 files changed

+328
-0
lines changed

5 files changed

+328
-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 <[email protected]>
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+
/**
15+
* @Annotation
16+
* @Target({"PROPERTY", "METHOD", "ANNOTATION"})
17+
*
18+
* @author Przemysław Bogusz <[email protected]>
19+
*/
20+
class AtLeastOneOf extends Composite
21+
{
22+
public const AT_LEAST_ONE_ERROR = 'f27e6d6c-261a-4056-b391-6673a623531c';
23+
24+
protected static $errorNames = [
25+
self::AT_LEAST_ONE_ERROR => 'AT_LEAST_ONE_ERROR',
26+
];
27+
28+
public $constraints = [];
29+
public $message = 'This value should satisfy at least one of the following constraints:';
30+
public $messageCollection = 'Each element of this collection should satisfy its own set of constraints.';
31+
public $includeInternalMessages = true;
32+
33+
public function getDefaultOption()
34+
{
35+
return 'constraints';
36+
}
37+
38+
public function getRequiredOptions()
39+
{
40+
return ['constraints'];
41+
}
42+
43+
protected function getCompositeOption()
44+
{
45+
return 'constraints';
46+
}
47+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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+
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
17+
18+
/**
19+
* @author Przemysław Bogusz <[email protected]>
20+
*/
21+
class AtLeastOneOfValidator extends ConstraintValidator
22+
{
23+
/**
24+
* {@inheritdoc}
25+
*/
26+
public function validate($value, Constraint $constraint)
27+
{
28+
if (!$constraint instanceof AtLeastOneOf) {
29+
throw new UnexpectedTypeException($constraint, AtLeastOneOf::class);
30+
}
31+
32+
$validator = $this->context->getValidator();
33+
34+
$messages = [$constraint->message];
35+
36+
foreach ($constraint->constraints as $key => $item) {
37+
$violations = $validator->validate($value, $item);
38+
39+
if (0 === \count($violations)) {
40+
return;
41+
}
42+
43+
if ($constraint->includeInternalMessages) {
44+
$message = ' ['.($key + 1).'] ';
45+
46+
if ($item instanceof All || $item instanceof Collection) {
47+
$message .= $constraint->messageCollection;
48+
} else {
49+
$message .= $violations->get(0)->getMessage();
50+
}
51+
52+
$messages[] = $message;
53+
}
54+
}
55+
56+
$this->context->buildViolation(implode('', $messages))
57+
->setCode(AtLeastOneOf::AT_LEAST_ONE_ERROR)
58+
->addViolation()
59+
;
60+
}
61+
}

src/Symfony/Component/Validator/Test/ConstraintValidatorTestCase.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,25 @@ protected function expectValidateValueAt($i, $propertyPath, $value, $constraints
203203
->willReturn($contextualValidator);
204204
}
205205

206+
protected function expectViolationsAt($i, $value, Constraint $constraint)
207+
{
208+
$context = $this->createContext();
209+
210+
$validatorClassname = $constraint->validatedBy();
211+
212+
$validator = new $validatorClassname();
213+
$validator->initialize($context);
214+
$validator->validate($value, $constraint);
215+
216+
$this->context->getValidator()
217+
->expects($this->at($i))
218+
->method('validate')
219+
->willReturn($context->getViolations())
220+
;
221+
222+
return $context->getViolations();
223+
}
224+
206225
protected function assertNoViolation()
207226
{
208227
$this->assertSame(0, $violationsCount = \count($this->context->getViolations()), sprintf('0 violation expected. Got %u.', $violationsCount));
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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 PHPUnit\Framework\TestCase;
15+
use Symfony\Component\Validator\Constraints\AtLeastOneOf;
16+
use Symfony\Component\Validator\Constraints\Valid;
17+
18+
/**
19+
* @author Przemysław Bogusz <[email protected]>
20+
*/
21+
class AtLeastOneOfTest extends TestCase
22+
{
23+
public function testRejectNonConstraints()
24+
{
25+
$this->expectException('Symfony\Component\Validator\Exception\ConstraintDefinitionException');
26+
new AtLeastOneOf([
27+
'foo',
28+
]);
29+
}
30+
31+
public function testRejectValidConstraint()
32+
{
33+
$this->expectException('Symfony\Component\Validator\Exception\ConstraintDefinitionException');
34+
new AtLeastOneOf([
35+
new Valid(),
36+
]);
37+
}
38+
}
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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\AtLeastOneOf;
15+
use Symfony\Component\Validator\Constraints\AtLeastOneOfValidator;
16+
use Symfony\Component\Validator\Constraints\Choice;
17+
use Symfony\Component\Validator\Constraints\Count;
18+
use Symfony\Component\Validator\Constraints\Country;
19+
use Symfony\Component\Validator\Constraints\DivisibleBy;
20+
use Symfony\Component\Validator\Constraints\EqualTo;
21+
use Symfony\Component\Validator\Constraints\GreaterThanOrEqual;
22+
use Symfony\Component\Validator\Constraints\IdenticalTo;
23+
use Symfony\Component\Validator\Constraints\Language;
24+
use Symfony\Component\Validator\Constraints\Length;
25+
use Symfony\Component\Validator\Constraints\LessThan;
26+
use Symfony\Component\Validator\Constraints\Negative;
27+
use Symfony\Component\Validator\Constraints\Range;
28+
use Symfony\Component\Validator\Constraints\Regex;
29+
use Symfony\Component\Validator\Constraints\Unique;
30+
use Symfony\Component\Validator\Test\ConstraintValidatorTestCase;
31+
32+
/**
33+
* @author Przemysław Bogusz <[email protected]>
34+
*/
35+
class AtLeastOneOfValidatorTest extends ConstraintValidatorTestCase
36+
{
37+
protected function createValidator()
38+
{
39+
return new AtLeastOneOfValidator();
40+
}
41+
42+
/**
43+
* @dataProvider getValidCombinations
44+
*/
45+
public function testValidCombinations($value, $constraints)
46+
{
47+
$i = 0;
48+
49+
foreach ($constraints as $constraint) {
50+
$this->expectViolationsAt($i++, $value, $constraint);
51+
}
52+
53+
$this->validator->validate($value, new AtLeastOneOf($constraints));
54+
55+
$this->assertNoViolation();
56+
}
57+
58+
public function getValidCombinations()
59+
{
60+
return [
61+
['symfony', [
62+
new Length(['min' => 10]),
63+
new EqualTo(['value' => 'symfony']),
64+
]],
65+
[150, [
66+
new Range(['min' => 10, 'max' => 20]),
67+
new GreaterThanOrEqual(['value' => 100]),
68+
]],
69+
[7, [
70+
new LessThan(['value' => 5]),
71+
new IdenticalTo(['value' => 7]),
72+
]],
73+
[-3, [
74+
new DivisibleBy(['value' => 4]),
75+
new Negative(),
76+
]],
77+
['FOO', [
78+
new Choice(['choices' => ['bar', 'BAR']]),
79+
new Regex(['pattern' => '/foo/i']),
80+
]],
81+
['fr', [
82+
new Country(),
83+
new Language(),
84+
]],
85+
[[1, 3, 5], [
86+
new Count(['min' => 5]),
87+
new Unique(),
88+
]],
89+
];
90+
}
91+
92+
/**
93+
* @dataProvider getInvalidCombinations
94+
*/
95+
public function testInvalidCombinationsWithDefaultMessage($value, $constraints)
96+
{
97+
$atLeastOneOf = new AtLeastOneOf(['constraints' => $constraints]);
98+
99+
$message = [$atLeastOneOf->message];
100+
101+
$i = 0;
102+
103+
foreach ($constraints as $constraint) {
104+
$message[] = ' ['.($i + 1).'] '.$this->expectViolationsAt($i++, $value, $constraint)->get(0)->getMessage();
105+
}
106+
107+
$this->validator->validate($value, $atLeastOneOf);
108+
109+
$this->buildViolation(implode('', $message))->setCode(AtLeastOneOf::AT_LEAST_ONE_ERROR)->assertRaised();
110+
}
111+
112+
/**
113+
* @dataProvider getInvalidCombinations
114+
*/
115+
public function testInvalidCombinationsWithCustomMessage($value, $constraints)
116+
{
117+
$atLeastOneOf = new AtLeastOneOf(['constraints' => $constraints, 'message' => 'foo', 'includeInternalMessages' => false]);
118+
119+
$i = 0;
120+
121+
foreach ($constraints as $constraint) {
122+
$this->expectViolationsAt($i++, $value, $constraint);
123+
}
124+
125+
$this->validator->validate($value, $atLeastOneOf);
126+
127+
$this->buildViolation('foo')->setCode(AtLeastOneOf::AT_LEAST_ONE_ERROR)->assertRaised();
128+
}
129+
130+
public function getInvalidCombinations()
131+
{
132+
return [
133+
['symphony', [
134+
new Length(['min' => 10]),
135+
new EqualTo(['value' => 'symfony']),
136+
]],
137+
[70, [
138+
new Range(['min' => 10, 'max' => 20]),
139+
new GreaterThanOrEqual(['value' => 100]),
140+
]],
141+
[8, [
142+
new LessThan(['value' => 5]),
143+
new IdenticalTo(['value' => 7]),
144+
]],
145+
[3, [
146+
new DivisibleBy(['value' => 4]),
147+
new Negative(),
148+
]],
149+
['F_O_O', [
150+
new Choice(['choices' => ['bar', 'BAR']]),
151+
new Regex(['pattern' => '/foo/i']),
152+
]],
153+
['f_r', [
154+
new Country(),
155+
new Language(),
156+
]],
157+
[[1, 3, 3], [
158+
new Count(['min' => 5]),
159+
new Unique(),
160+
]],
161+
];
162+
}
163+
}

0 commit comments

Comments
 (0)