8000 filter out excess violations while validating form field constraints … · symfony/symfony@b6c71db · GitHub
[go: up one dir, main page]

Skip to content

Commit b6c71db

Browse files
committed
filter out excess violations while validating form field constraints with sequence of groups
1 parent e02e74d commit b6c71db

File tree

3 files changed

+185
-0
lines changed

3 files changed

+185
-0
lines changed

src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ public function validate($form, Constraint $formConstraint)
6565

6666
if ($groups instanceof GroupSequence) {
6767
$validator->atPath('data')->validate($form->getData(), $constraints, $groups);
68+
69+
$this->filterFormFieldsGroupSequenceViolations($groups);
70+
6871
// Otherwise validate a constraint only once for the first
6972
// matching group
7073
foreach ($groups as $group) {
@@ -142,6 +145,54 @@ public function validate($form, Constraint $formConstraint)
142145
}
143146
}
144147

148+
/**
149+
* Filter out form filed violations to meet the requirements of the sequence of groups.
150+
*
151+
* This is necessary because each form field is validated independently.
152+
*/
153+
private function filterFormFieldsGroupSequenceViolations(GroupSequence $groupSequence)
154+
{
155+
if (\count($violations = $this->context->getViolations()) < 2) {
156+
return;
157+
}
158+
159+
// collect violation offsets with an associated group
160+
$violationGroups = [];
161+
162+
foreach ($violations as $offset => $violation) {
163+
$violationGroups[$offset] = array_map(static function ($group) {
164+
return $group;
165+
}, $violation->getConstraint()->groups);
166+
}
167+
168+
// collect groups that meet the requirements
169+
$groupsToKeep = [];
170+
171+
foreach ($groupSequence->groups as $seqGroups) {
172+
$seqGroups = !\is_array($seqGroups) ? [$seqGroups] : $seqGroups;
173+
174+
// if violations have groups in the current groups of the sequence,
175+
// keep this current groups to remove all other violations that don't belong this groups later
176+
if (array_filter($violationGroups, static function ($groups) use ($seqGroups) {
177+
return array_filter($groups, static function ($group) use ($seqGroups) {
178+
return \in_array($group, $seqGroups, true);
179+
});
180+
})) {
181+
$groupsToKeep = $seqGroups;
182+
break;
183+
}
184+
}
185+
186+
// remove all other violations
187+
foreach ($violationGroups as $offset => $groups) {
188+
if (!array_filter($groups, static function ($group) use ($groupsToKeep) {
189+
return \in_array($group, $groupsToKeep, true);
190+
})) {
191+
$violations->remove($offset);
192+
}
193+
}
194+
}
195+
145196
/**
146197
* Returns the validation groups of the given form.
147198
*
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
<?php
2+
3+
namespace Symfony\Component\Form\Tests\Extension\Validator\Type;
4+
5+
trait FormTypeValidatorExtensionProvider
6+
{
7+
public function provideGroupsSequenceAndResultData()
8+
{
9+
return [
10+
// two fields (sequence of groups and group order):
11+
[
12+
'groups' => [
13+
'field1' => ['First'],
14+
'field2' => ['Second'],
15+
],
16+
'sequence' => ['First'],
17+
'errors' => 1,
18+
'propertyPaths' => ['field1'],
19+
],
20+
[
21+
'groups' => [
22+
'field1' => ['First'],
23+
'field2' => ['Second'],
24+
],
25+
'sequence' => ['Second'],
26+
'errors' => 1,
27+
'propertyPaths' => ['field2'],
28+
],
29+
[
30+
'groups' => [
31+
'field1' => ['First'],
32+
'field2' => ['Second'],
33+
],
34+
'sequence' => ['First', 'Second'],
35+
'errors' => 1,
36+
'propertyPaths' => ['field1'],
37+
],
38+
[
39+
'groups' => [
40+
'field1' => ['First'],
41+
'field2' => ['Second'],
42+
],
43+
'sequence' => ['Second', 'First'],
44+
'errors' => 1,
45+
'propertyPaths' => ['field2'],
46+
],
47+
48+
// two fields (field with sequence of groups)
49+
[
50+
'groups' => [
51+
'field1' => ['First'],
52+
'field2' => ['Second', 'First'],
53+
],
54+
'sequence' => ['First'],
55+
'errors' => 2,
56+
'propertyPaths' => ['field1', 'field2'],
57+
],
58+
59+
// three fields (sequence with multigroup)
60+
[
61+
'groups' => [
62+
'field1' => ['First'],
63+
'field2' => ['Second'],
64+
'field3' => ['Third'],
65+
],
66+
'sequence' => [['First', 'Second'], 'Third'],
67+
'errors' => 2,
68+
'propertyPaths' => ['field1', 'field2'],
69+
],
70+
[
71+
'groups' => [
72+
'field1' => ['First'],
73+
'field2' => ['Second'],
74+
'field3' => ['Third'],
75+
],
76+
'sequence' => ['First', ['Second', 'Third']],
77+
'errors' => 1,
78+
'propertyPaths' => ['field1'],
79+
],
80+
81+
// three fields (field with sequence of groups)
82+
[
83+
'groups' => [
84+
'field1' => ['First'],
85+
'field2' => ['Second'],
86+
'field3' => ['Third', 'Second'],
87+
],
88+
'sequence' => [['First', 'Second'], 'Third'],
89+
'errors' => 3,
90+
'propertyPaths' => ['field1', 'field2', 'field3'],
91+
],
92+
];
93+
}
94+
}

src/Symfony/Component/Form/Tests/Extension/Validator/Type/FormTypeValidatorExtensionTest.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,15 @@
1919
use Symfony\Component\Validator\Constraints\Email;
2020
use Symfony\Component\Validator\Constraints\GroupSequence;
2121
use Symfony\Component\Validator\Constraints\Length;
22+
use Symfony\Component\Validator\Constraints\NotBlank;
2223
use Symfony\Component\Validator\Constraints\Valid;
2324
use Symfony\Component\Validator\ConstraintViolationList;
2425
use Symfony\Component\Validator\Validation;
2526

2627
class FormTypeValidatorExtensionTest extends BaseValidatorExtensionTest
2728
{
2829
use ValidatorExtensionTrait;
30+
use FormTypeValidatorExtensionProvider;
2931

3032
public function testSubmitValidatesData()
3133
{
@@ -74,6 +76,44 @@ public function testGroupSequenceWithConstraintsOption()
7476
$this->assertCount(1, $form->getErrors(true));
7577
}
7678

79+
/**
80+
* @dataProvider provideGroupsSequenceAndResultData
81+
*/
82+
public function testGroupSequenceWithConstraintsOptionMatrix(
83+
array $groups,
84+
array $sequence,
85+
int $errorCount,
86+
array $propertyPaths
87+
) {
88+
$form = Forms::createFormFactoryBuilder()
89+
->addExtension(new ValidatorExtension(Validation::createValidator()))
90+
->getFormFactory()
91+
->create(FormTypeTest::TESTED_TYPE, null, ([
92+
'validation_groups' => new GroupSequence($sequence),
93+
]));
94+
95+
$data = [];
96+
foreach ($groups as $fieldName => $fieldGroups) {
97+
$form = $form->add(
98+
$fieldName, TextTypeTest::
99+
TESTED_TYPE,
100+
[
101+
'constraints' => [new NotBlank(['groups' => $fieldGroups])],
102+
]);
103+
104+
$data[$fieldName] = '';
105+
}
106+
107+
$form->submit($data);
108+
109+
$errors = $form->getErrors(true);
110+
$this->assertCount($errorCount, $form->getErrors(true));
111+
112+
foreach ($errors as $i => $error) {
113+
$this->assertEquals('children['.$propertyPaths[$i].'].data', $error->getCause()->getPropertyPath());
114+
}
115+
}
116+
77117
protected function createForm(array $options = [])
78118
{
79119
return $this->factory->create(FormTypeTest::TESTED_TYPE, null, $options);

0 commit comments

Comments
 (0)
0