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

Skip to content

Commit 9e61cd3

Browse files
committed
[Form] Fixed handling groups sequence validation
1 parent 5da141b commit 9e61cd3

File tree

5 files changed

+133
-20
lines changed

5 files changed

+133
-20
lines changed

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

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,15 @@
2424
*/
2525
class FormValidator extends ConstraintValidator
2626
{
27+
private static $resolvedGroups;
28+
29+
public function __construct()
30+
{
31+
if (null === self::$resolvedGroups) {
32+
self::$resolvedGroups = new \SplObjectStorage();
33+
}
34+
}
35+
2736
/**
2837
* {@inheritdoc}
2938
*/
@@ -55,31 +64,38 @@ public function validate($form, Constraint $formConstraint)
5564
// Validate the data against its own constraints
5665
if ($form->isRoot() && (\is_object($data) || \is_array($data))) {
5766
if (($groups && \is_array($groups)) || ($groups instanceof GroupSequence && $groups->groups)) {
58-
$validator->atPath('data')->validate($form->getData(), null, $groups);
67+
$validator->atPath('data')->validate($data, null, $groups);
5968
}
6069
}
6170

62-
// Validate the data against the constraints defined
63-
// in the form
71+
// Validate the data against the constraints defined in the form
72+
/** @var Constraint[] $constraints */
6473
$constraints = $config->getOption('constraints', []);
6574

6675
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;
76+
// Validate the form AND nested fields in sequence
77+
$violationsCount = $this->context->getViolations()->count();
78+
$fieldPropertyPath = \is_object($data) ? 'data.%s' : '%s';
79+
80+
foreach ($groups->groups as $group) {
81+
$validator->atPath('data')->validate($data, $constraints, $group);
82+
83+
foreach ($form->all() as $field) {
84+
if ($field->isSubmitted()) {
85+
self::$resolvedGroups[$field] = [$group];
86+
$validator->atPath(sprintf($fieldPropertyPath, $field->getPropertyPath()))->validate($field, $formConstraint);
7587
}
7688
}
89+
90+
if ($violationsCount < $this->context->getViolations()->count()) {
91+
break;
92+
}
7793
}
7894
} else {
7995
foreach ($constraints as $constraint) {
8096
// For the "Valid" constraint, validate the data in all groups
8197
if ($constraint instanceof Valid) {
82-
$validator->atPath('data')->validate($form->getData(), $constraint, $groups);
98+
$validator->atPath('data')->validate($data, $constraint, $groups);
8399

84100
continue;
85101
}
@@ -88,7 +104,7 @@ public function validate($form, Constraint $formConstraint)
88104
// matching group
89105
foreach ($groups as $group) {
90106
if (\in_array($group, $constraint->groups)) {
91-
$validator->atPath('data')->validate($form->getData(), $constraint, $group);
107+
$validator->atPath('data')->validate($data, $constraint, $group);
92108

93109
// Prevent duplicate validation
94110
if (!$constraint instanceof Composite) {
@@ -171,6 +187,10 @@ private static function getValidationGroups(FormInterface $form)
171187
return self::resolveValidationGroups($groups, $form);
172188
}
173189

190+
if (isset(self::$resolvedGroups[$form])) {
191+
return self::$resolvedGroups[$form];
192+
}
193+
174194
$form = $form->getParent();
175195
} while (null !== $form);
176196

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

Lines 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

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use Symfony\Component\Form\CallbackTransformer;
1717
use Symfony\Component\Form\Exception\TransformationFailedException;
1818
use Symfony\Component\Form\Extension\Core\DataMapper\PropertyPathMapper;
19+
use Symfony\Component\Form\Extension\Core\Type\TextType;
1920
use Symfony\Component\Form\Extension\Validator\Constraints\Form;
2021
use Symfony\Component\Form\Extension\Validator\Constraints\FormValidator;
2122
use Symfony\Component\Form\FormBuilder;
@@ -402,7 +403,8 @@ public function testHandleGroupSequenceValidationGroups()
402403
$form->submit([]);
403404

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

407409
$this->validator->validate($form, new Form());
408410

@@ -756,6 +758,39 @@ public function testCompositeConstraintValidatedInEachGroup()
756758
$this->assertSame('data[field2]', $context->getViolations()[1]->getPropertyPath());
757759
}
758760

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

785820
private function getCompoundForm($data, array $options = [])
786821
{
787-
return $this->getBuilder('name', \get_class($data), $options)
822+
return $this->getBuilder('name', \is_object($data) ? \get_class($data) : null, $options)
788823
->setData($data)
789824
->setCompound(true)
790825
->setDataMapper(new PropertyPathMapper())

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

Lines changed: 32 additions & 2 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;
@@ -64,14 +65,43 @@ public function testGroupSequenceWithConstraintsOption()
6465
->add('field', TextTypeTest::TESTED_TYPE, [
6566
'constraints' => [
6667
new Length(['min' => 10, 'groups' => ['First']]),
67-
new Email(['groups' => ['Second']]),
68+
new NotBlank(['groups' => ['Second']]),
6869
],
6970
])
7071
;
7172

7273
$form->submit(['field' => 'wrong']);
7374

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

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

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

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,19 @@
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;
28+
use Symfony\Component\Validator\Constraints\NotNull;
2429
use Symfony\Component\Validator\Mapping\CascadingStrategy;
2530
use Symfony\Component\Validator\Mapping\ClassMetadata;
2631
use Symfony\Component\Validator\Mapping\Factory\LazyLoadingMetadataFactory;
@@ -49,6 +54,8 @@ public function test2Dot5ValidationApi()
4954
$this->assertCount(1, $metadata->getConstraints());
5055
$this->assertInstanceOf(FormConstraint::class, $metadata->getConstraints()[0]);
5156

57+
$this->assertSame(CascadingStrategy::NONE, $metadata->cascadingStrategy);
58+
$this->assertSame(TraversalStrategy::IMPLICIT, $metadata->traversalStrategy);
5259
$this->assertSame(CascadingStrategy::CASCADE, $metadata->getPropertyMetadata('children')[0]->cascadingStrategy);
5360
$this->assertSame(TraversalStrategy::IMPLICIT, $metadata->getPropertyMetadata('children')[0]->traversalStrategy);
5461
}
@@ -86,7 +93,28 @@ public function testFieldConstraintsInvalidateFormIfFieldIsSubmitted()
8693
$this->assertFalse($form->get('baz')->isValid());
8794
}
8895

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

98-
return $formFactory->create($type);
126+
return $formFactory->create($type, $data, $options);
99127
}
100128
}
101129

0 commit comments

Comments
 (0)