Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 932eb16

Browse filesBrowse files
committed
[Form] Fixed handling groups sequence validation
1 parent 5da141b commit 932eb16
Copy full SHA for 932eb16

File tree

5 files changed

+131
-23
lines changed
Filter options

5 files changed

+131
-23
lines changed

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php
+33-15Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,13 @@
2424
*/
2525
class FormValidator extends ConstraintValidator
2626
{
27+
private $resolvedGroups;
28+
29+
public function __construct()
30+
{
31+
$this->resolvedGroups = new \SplObjectStorage();
32+
}
33+
2734
/**
2835
* {@inheritdoc}
2936
*/
@@ -44,7 +51,7 @@ public function validate($form, Constraint $formConstraint)
4451

4552
if ($form->isSubmitted() && $form->isSynchronized()) {
4653
// Validate the form data only if transformation succeeded
47-
$groups = self::getValidationGroups($form);
54+
$groups = $this->getValidationGroups($form);
4855

4956
if (!$groups) {
5057
return;
@@ -55,31 +62,38 @@ public function validate($form, Constraint $formConstraint)
5562
// Validate the data against its own constraints
5663
if ($form->isRoot() && (\is_object($data) || \is_array($data))) {
5764
if (($groups && \is_array($groups)) || ($groups instanceof GroupSequence && $groups->groups)) {
58-
$validator->atPath('data')->validate($form->getData(), null, $groups);
65+
$validator->atPath('data')->validate($data, null, $groups);
5966
}
6067
}
6168

62-
// Validate the data against the constraints defined
63-
// in the form
69+
// Validate the data against the constraints defined in the form
70+
/** @var Constraint[] $constraints */
6471
$constraints = $config->getOption('constraints', []);
6572

6673
if ($groups instanceof GroupSequence) {
67-
$validator->atPath('data')->validate($form->getData(), $constraints, $groups);
68-
// Otherwise validate a constraint only once for the first
69-
// matching group
70-
foreach ($groups as $group) {
71-
if (\in_array($group, $formConstraint->groups)) {
72-
$validator->atPath('data')->validate($form->getData(), $formConstraint, $group);
73-
if (\count($this->context->getViolations()) > 0) {
74-
break;
74+
// Validate the form AND nested fields in sequence
75+
$violationsCount = $this->context->getViolations()->count();
76+
$fieldPropertyPath = \is_object($data) ? 'data.%s' : '%s';
77+
78+
foreach ($groups->groups as $group) {
79+
$validator->atPath('data')->validate($data, $constraints, $group);
80+
81+
foreach ($form->all() as $field) {
82+
if ($field->isSubmitted()) {
83+
$this->resolvedGroups[$field] = [$group];
84+
$validator->atPath(sprintf($fieldPropertyPath, $field->getPropertyPath()))->validate($field, $formConstraint);
7585
}
7686
}
87+
88+
if ($violationsCount < $this->context->getViolations()->count()) {
89+
break;
90+
}
7791
}
7892
} else {
7993
foreach ($constraints as $constraint) {
8094
// For the "Valid" constraint, validate the data in all groups
8195
if ($constraint instanceof Valid) {
82-
$validator->atPath('data')->validate($form->getData(), $constraint, $groups);
96+
$validator->atPath('data')->validate($data, $constraint, $groups);
8397

8498
continue;
8599
}
@@ -88,7 +102,7 @@ public function validate($form, Constraint $formConstraint)
88102
// matching group
89103
foreach ($groups as $group) {
90104
if (\in_array($group, $constraint->groups)) {
91-
$validator->atPath('data')->validate($form->getData(), $constraint, $group);
105+
$validator->atPath('data')->validate($data, $constraint, $group);
92106

93107
// Prevent duplicate validation
94108
if (!$constraint instanceof Composite) {
@@ -147,7 +161,7 @@ public function validate($form, Constraint $formConstraint)
147161
*
148162
* @return string|GroupSequence|(string|GroupSequence)[] The validation groups
149163
*/
150-
private static function getValidationGroups(FormInterface $form)
164+
private function getValidationGroups(FormInterface $form)
151165
{
152166
// Determine the clicked button of the complete form tree
153167
$clickedButton = null;
@@ -171,6 +185,10 @@ private static function getValidationGroups(FormInterface $form)
171185
return self::resolveValidationGroups($groups, $form);
172186
}
173187

188+
if (isset($this->resolvedGroups[$form])) {
189+
return $this->resolvedGroups[$form];
190+
}
191+
174192
$form = $form->getParent();
175193
} while (null !== $form);
176194

‎src/Symfony/Component/Form/Resources/config/validation.xml

Copy file name to clipboardExpand all lines: src/Symfony/Component/Form/Resources/config/validation.xml
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<class name="Symfony\Component\Form\Form">
88
<constraint name="Symfony\Component\Form\Extension\Validator\Constraints\Form" />
99
<property name="children">
10-
<constraint name="Valid" />
10+
<constraint name="Valid" />
1111
</property>
1212
</class>
1313
</constraint-mapping>

‎src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php
+36-2Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -402,7 +402,8 @@ public function testHandleGroupSequenceValidationGroups()
402402
$form->submit([]);
403403

404404
$this->expectValidateAt(0, 'data', $object, new GroupSequence(['group1', 'group2']));
405-
$this->expectValidateAt(1, 'data', $object, new GroupSequence(['group1', 'group2']));
405+
$this->expectValidateAt(1, 'data', $object, 'group1');
406+
$this->expectValidateAt(2, 'data', $object, 'group2');
406407

407408
$this->validator->validate($form, new Form());
408409

@@ -756,6 +757,39 @@ public function testCompositeConstraintValidatedInEachGroup()
756757
$this->assertSame('data[field2]', $context->getViolations()[1]->getPropertyPath());
757758
}
758759

760+
public function testCompositeConstraintValidatedInSequence()
761+
{
762+
$form = $this->getCompoundForm([], [
763+
'constraints' => [
764+
new Collection([
765+
'field1' => new NotBlank([
766+
'groups' => ['field1'],
767+
]),
768+
'field2' => new NotBlank([
769+
'groups' => ['field2'],
770+
]),
771+
]),
772+
],
773+
'validation_groups' => new GroupSequence(['field1', 'field2']),
774+
])
775+
->add($this->getForm('field1'))
776+
->add($this->getForm('field2'))
777+
;
778+
779+
$form->submit([
780+
'field1' => '',
781+
'field2' => '',
782+
]);
783+
784+
$context = new ExecutionContext(Validation::createValidator(), $form, new IdentityTranslator());
785+
$this->validator->initialize($context);
786+
$this->validator->validate($form, new Form());
787+
788+
$this->assertCount(1, $context->getViolations());
789+
$this->assertSame('This value should not be blank.', $context->getViolations()[0]->getMessage());
790+
$this->assertSame('data[field1]', $context->getViolations()[0]->getPropertyPath());
791+
}
792+
759793
protected function createValidator()
760794
{
761795
return new FormValidator();
@@ -784,7 +818,7 @@ private function getForm($name = 'name', $dataClass = null, array $options = [])
784818

785819
private function getCompoundForm($data, array $options = [])
786820
{
787-
return $this->getBuilder('name', \get_class($data), $options)
821+
return $this->getBuilder('name', \is_object($data) ? \get_class($data) : null, $options)
788822
->setData($data)
789823
->setCompound(true)
790824
->setDataMapper(new PropertyPathMapper())

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Form/Tests/Extension/Validator/Type/FormTypeValidatorExtensionTest.php
+32-3Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@
1616
use Symfony\Component\Form\Test\Traits\ValidatorExtensionTrait;
1717
use Symfony\Component\Form\Tests\Extension\Core\Type\FormTypeTest;
1818
use Symfony\Component\Form\Tests\Extension\Core\Type\TextTypeTest;
19-
use Symfony\Component\Validator\Constraints\Email;
2019
use Symfony\Component\Validator\Constraints\GroupSequence;
2120
use Symfony\Component\Validator\Constraints\Length;
21+
use Symfony\Component\Validator\Constraints\NotBlank;
2222
use Symfony\Component\Validator\Constraints\Valid;
2323
use Symfony\Component\Validator\ConstraintViolationList;
2424
use Symfony\Component\Validator\Validation;
@@ -64,14 +64,43 @@ public function testGroupSequenceWithConstraintsOption()
6464
->add('field', TextTypeTest::TESTED_TYPE, [
6565
'constraints' => [
6666
new Length(['min' => 10, 'groups' => ['First']]),
67-
new Email(['groups' => ['Second']]),
67+
new NotBlank(['groups' => ['Second']]),
6868
],
6969
])
7070
;
7171

7272
$form->submit(['field' => 'wrong']);
7373

74-
$this->assertCount(1, $form->getErrors(true));
74+
$errors = $form->getErrors(true);
75+
76+
$this->assertCount(1, $errors);
77+
$this->assertInstanceOf(Length::class, $errors[0]->getCause()->getConstraint());
78+
}
79+
80+
public function testManyFieldsGroupSequenceWithConstraintsOption()
81+
{
82+
$form = Forms::createFormFactoryBuilder()
83+
->addExtension(new ValidatorExtension(Validation::createValidator()))
84+
->getFormFactory()
85+
->create(FormTypeTest::TESTED_TYPE, null, (['validation_groups' => new GroupSequence(['First', 'Second'])]))
86+
->add('field1', TextTypeTest::TESTED_TYPE, [
87+
'constraints' => [
88+
new Length(['min' => 10, 'groups' => ['First']]),
89+
],
90+
])
91+
->add('field2', TextTypeTest::TESTED_TYPE, [
92+
'constraints' => [
93+
new NotBlank(['groups' => ['Second']]),
94+
],
95+
])
96+
;
97+
98+
$form->submit(['field1' => 'wrong_1', 'field2' => 'wrong_2']);
99+
100+
$errors = $form->getErrors(true);
101+
102+
$this->assertCount(1, $errors);
103+
$this->assertInstanceOf(Length::class, $errors[0]->getCause()->getConstraint());
75104
}
76105

77106
protected function createForm(array $options = [])

‎src/Symfony/Component/Form/Tests/Extension/Validator/ValidatorExtensionTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Form/Tests/Extension/Validator/ValidatorExtensionTest.php
+29-2Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,17 @@
1313

1414
use PHPUnit\Framework\TestCase;
1515
use Symfony\Component\Form\AbstractType;
16+
use Symfony\Component\Form\Extension\Core\Type\FormType;
17+
use Symfony\Component\Form\Extension\Core\Type\TextType;
1618
use Symfony\Component\Form\Extension\Validator\Constraints\Form as FormConstraint;
1719
use Symfony\Component\Form\Extension\Validator\ValidatorExtension;
1820
use Symfony\Component\Form\Extension\Validator\ValidatorTypeGuesser;
1921
use Symfony\Component\Form\Form;
2022
use Symfony\Component\Form\FormBuilderInterface;
2123
use Symfony\Component\Form\FormFactoryBuilder;
2224
use Symfony\Component\OptionsResolver\OptionsResolver;
25+
use Symfony\Component\Validator\Constraints\GroupSequence;
26+
use Symfony\Component\Validator\Constraints\Length;
2327
use Symfony\Component\Validator\Constraints\NotBlank;
2428
use Symfony\Component\Validator\Mapping\CascadingStrategy;
2529
use Symfony\Component\Validator\Mapping\ClassMetadata;
@@ -49,6 +53,8 @@ public function test2Dot5ValidationApi()
4953
$this->assertCount(1, $metadata->getConstraints());
5054
$this->assertInstanceOf(FormConstraint::class, $metadata->getConstraints()[0]);
5155

56+
$this->assertSame(CascadingStrategy::NONE, $metadata->cascadingStrategy);
57+
$this->assertSame(TraversalStrategy::IMPLICIT, $metadata->traversalStrategy);
5258
$this->assertSame(CascadingStrategy::CASCADE, $metadata->getPropertyMetadata('children')[0]->cascadingStrategy);
5359
$this->assertSame(TraversalStrategy::IMPLICIT, $metadata->getPropertyMetadata('children')[0]->traversalStrategy);
5460
}
@@ -86,7 +92,28 @@ public function testFieldConstraintsInvalidateFormIfFieldIsSubmitted()
8692
$this->assertFalse($form->get('baz')->isValid());
8793
}
8894

89-
private function createForm($type)
95+
public function testFieldsValidateInSequence()
96+
{
97+
$form = $this->createForm(FormType::class, null, [
98+
'validation_groups' => new GroupSequence(['group1', 'group2']),
99+
])
100+
->add('foo', TextType::class, [
101+
'constraints' => [new Length(['min' => 10, 'groups' => ['group1']])],
102+
])
103+
->add('bar', TextType::class, [
104+
'constraints' => [new NotBlank(['groups' => ['group2']])],
105+
])
106+
;
107+
108+
$form->submit(['foo' => 'invalid', 'bar' => null]);
109+
110+
$errors = $form->getErrors(true);
111+
112+
$this->assertCount(1, $errors);
113+
$this->assertInstanceOf(Length::class, $errors[0]->getCause()->getConstraint());
114+
}
115+
116+
private function createForm($type, $data = null, array $options = [])
90117
{
91118
$validator = Validation::createValidatorBuilder()
92119
->setMetadataFactory(new LazyLoadingMetadataFactory(new StaticMethodLoader()))
@@ -95,7 +122,7 @@ private function createForm($type)
95122
$formFactoryBuilder->addExtension(new ValidatorExtension($validator));
96123
$formFactory = $formFactoryBuilder->getFormFactory();
97124

98-
return $formFactory->create($type);
125+
return $formFactory->create($type, $data, $options);
99126
}
100127
}
101128

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.