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

Skip to content

Commit 6be9d50

Browse files
committed
filter out excess violations while validating form field constraints with sequence of groups
1 parent af46fd6 commit 6be9d50

File tree

2 files changed

+176
-0
lines changed

2 files changed

+176
-0
lines changed

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

Lines changed: 49 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,52 @@ public function validate($form, Constraint $formConstraint)
142145
}
143146
}
144147

148+
/**
149+
* Filter out form field violations to meet the requirements of the sequence of groups.
150+
*
151+
* If there is a violation with a group of current groups of the sequence,
152+
* remove all other violations that don't belong this groups.
153+
*
154+
* This is necessary because each form field is validated independently.
155+
*/
156+
private function filterFormFieldsGroupSequenceViolations(GroupSequence $groupSequence)
157+
{
158+
if (\count($violations = $this->context->getViolations()) < 2) {
159+
return;
160+
}
161+
162+
$violationGroups = [];
163+
164+
foreach ($violations as $offset => $violation) {
165+
$violationGroups[$offset] = array_map(static function ($group) {
166+
return $group;
167+
}, $violation->getConstraint()->groups);
168+
}
169+
170+
$groupsToKeep = [];
171+
172+
foreach ($groupSequence->groups as $seqGroups) {
173+
$seqGroups = !\is_array($seqGroups) ? [$seqGroups] : $seqGroups;
174+
175+
if (array_filter($violationGroups, static function ($groups) use ($seqGroups) {
176+
return array_filter($groups, static function ($group) use ($seqGroups) {
177+
return \in_array($group, $seqGroups, true);
178+
});
179+
})) {
180+
$groupsToKeep = $seqGroups;
181+
break;
182+
}
183+
}
184+
185+
foreach ($violationGroups as $offset => $groups) {
186+
if (!array_filter($groups, static function ($group) use ($groupsToKeep) {
187+
return \in_array($group, $groupsToKeep, true);
188+
})) {
189+
$violations->remove($offset);
190+
}
191+
}
192+
}
193+
145194
/**
146195
* Returns the validation groups of the given form.
147196
*

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

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
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;
@@ -74,6 +75,132 @@ public function testGroupSequenceWithConstraintsOption()
7475
$this->assertCount(1, $form->getErrors(true));
7576
}
7677

78+
/**
79+
* @dataProvider provideGroupsSequenceAndResultData
80+
*/
81+
public function testGroupSequenceWithConstraintsOptionMatrix(
82+
array $groups,
83+
array $sequence,
84+
$errorCount,
85+
array $propertyPaths
86+
) {
87+
$form = Forms::createFormFactoryBuilder()
88+
->addExtension(new ValidatorExtension(Validation::createValidator()))
89+
->getFormFactory()
90+
->create(FormTypeTest::TESTED_TYPE, null, ([
91+
'validation_groups' => new GroupSequence($sequence),
92+
]));
93+
94+
$data = [];
95+
foreach ($groups as $fieldName => $fieldGroups) {
96+
$form = $form->add(
97+
$fieldName, TextTypeTest::
98+
TESTED_TYPE,
99+
[
100+
'constraints' => [new NotBlank(['groups' => $fieldGroups])],
101+
]);
102+
103+
$data[$fieldName] = '';
104+
}
105+
106+
$form->submit($data);
107+
108+
$errors = $form->getErrors(true);
109+
$this->assertCount($errorCount, $form->getErrors(true));
110+
111+
foreach ($errors as $i => $error) {
112+
$this->assertEquals('children['.$propertyPaths[$i].'].data', $error->getCause()->getPropertyPath());
113+
}
114+
}
115+
116+
public function provideGroupsSequenceAndResultData()
117+
{
118+
return [
119+
// two fields (sequence of groups and group order):
120+
[
121+
'groups' => [
122+
'field1' => ['First'],
123+
'field2' => ['Second'],
124+
],
125+
'sequence' => ['First'],
126+
'errors' => 1,
127+
'propertyPaths' => ['field1'],
128+
],
129+
[
130+
'groups' => [
131+
'field1' => ['First'],
132+
'field2' => ['Second'],
133+
],
134+
'sequence' => ['Second'],
135+
'errors' => 1,
136+
'propertyPaths' => ['field2'],
137+
],
138+
[
139+
'groups' => [
140+
'field1' => ['First'],
141+
'field2' => ['Second'],
142+
],
143+
'sequence' => ['First', 'Second'],
144+
'errors' => 1,
145+
'propertyPaths' => ['field1'],
146+
],
147+
[
148+
'groups' => [
149+
'field1' => ['First'],
150+
'field2' => ['Second'],
151+
],
152+
'sequence' => ['Second', 'First'],
153+
'errors' => 1,
154+
'propertyPaths' => ['field2'],
155+
],
156+
157+
// two fields (field with sequence of groups)
158+
[
159+
'groups' => [
160+
'field1' => ['First'],
161+
'field2' => ['Second', 'First'],
162+
],
163+
'sequence' => ['First'],
164+
'errors' => 2,
165+
'propertyPaths' => ['field1', 'field2'],
166+
],
167+
168+
// three fields (sequence with multigroup)
169+
[
170+
'groups' => [
171+
'field1' => ['First'],
172+
'field2' => ['Second'],
173+
'field3' => ['Third'],
174+
],
175+
'sequence' => [['First', 'Second'], 'Third'],
176+
'errors' => 2,
177+
'propertyPaths' => ['field1', 'field2'],
178+
],
179+
[
180+
'groups' => [
181+
'field1' => ['First'],
182+
'field2' => ['Second'],
183+
'field3' => ['Third'],
184+
],
185+
'sequence' => ['First', ['Second', 'Third']],
186+
'errors' => 1,
187+
'propertyPaths' => ['field1'],
188+
],
189+
190+
// three fields (field with sequence of groups)
191+
[
192+
'groups' => [
193+
'field1' => ['First'],
194+
'field2' => ['Second'],
195+
'field3' => ['Third', 'Second'],
196+
],
197+
'sequence' => [['First', 'Second'], 'Third'],
198+
'errors' => 3,
199+
'propertyPaths' => ['field1', 'field2', 'field3'],
200+
],
201+
];
202+
}
203+
77204
protected function createForm(array $options = [])
78205
{
79206
return $this->factory->create(FormTypeTest::TESTED_TYPE, null, $options);

0 commit comments

Comments
 (0)
0