From e2c7c3373d1810726b37a7d6df6aa7361ff6f153 Mon Sep 17 00:00:00 2001 From: Johan de Ruijter Date: Fri, 25 Sep 2020 11:24:52 +0200 Subject: [PATCH 1/2] [Form] [Validator] Add failing testcase to demonstrate group sequence issue When using group sequences on a form, sometimes constraints are ignored even though they should fail. --- .../FormValidatorFunctionalTest.php | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorFunctionalTest.php b/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorFunctionalTest.php index d4ca0cb1bf..f133eb20dc 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorFunctionalTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorFunctionalTest.php @@ -232,6 +232,34 @@ class FormValidatorFunctionalTest extends TestCase $this->assertInstanceOf(Length::class, $errors[0]->getCause()->getConstraint()); } + public function testConstraintsInDifferentGroupsOnSingleFieldWithAdditionalFieldThatHasNoConstraintsAddedBeforeTheFieldWithConstraints() + { + $form = $this->formFactory->create(FormType::class, null, [ + 'validation_groups' => new GroupSequence(['group1', 'group2']), + ]) + ->add('bar') + ->add('foo', TextType::class, [ + 'constraints' => [ + new NotBlank([ + 'groups' => ['group1'], + ]), + new Length([ + 'groups' => ['group2'], + 'max' => 3, + ]), + ], + ]); + $form->submit([ + 'foo' => 'test@example.com', + ]); + + $errors = $form->getErrors(true); + + $this->assertFalse($form->isValid()); + $this->assertCount(1, $errors); + $this->assertInstanceOf(Length::class, $errors[0]->getCause()->getConstraint()); + } + public function testCascadeValidationToChildFormsUsingPropertyPaths() { $form = $this->formFactory->create(FormType::class, null, [ From 04f5698e2908efe4881629f7afae79c02acfedc8 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Fri, 2 Oct 2020 11:42:08 +0200 Subject: [PATCH 2/2] propagate validation groups to subforms --- .../Extension/Validator/Constraints/FormValidator.php | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php b/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php index 8f7acb35d8..8c54aeaafb 100644 --- a/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php +++ b/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php @@ -25,6 +25,7 @@ use Symfony\Component\Validator\Exception\UnexpectedTypeException; class FormValidator extends ConstraintValidator { private $resolvedGroups; + private $fieldFormConstraints; /** * {@inheritdoc} @@ -67,6 +68,7 @@ class FormValidator extends ConstraintValidator if ($hasChildren && $form->isRoot()) { $this->resolvedGroups = new \SplObjectStorage(); + $this->fieldFormConstraints = []; } if ($groups instanceof GroupSequence) { @@ -84,14 +86,16 @@ class FormValidator extends ConstraintValidator foreach ($form->all() as $field) { if ($field->isSubmitted()) { - // remember to validate this field is one group only + // remember to validate this field in one group only // otherwise resolving the groups would reuse the same // sequence recursively, thus some fields could fail // in different steps without breaking early enough $this->resolvedGroups[$field] = (array) $group; $fieldFormConstraint = new Form(); + $fieldFormConstraint->groups = $group; + $this->fieldFormConstraints[] = $fieldFormConstraint; $this->context->setNode($this->context->getValue(), $field, $this->context->getMetadata(), $this->context->getPropertyPath()); - $validator->atPath(sprintf('children[%s]', $field->getName()))->validate($field, $fieldFormConstraint); + $validator->atPath(sprintf('children[%s]', $field->getName()))->validate($field, $fieldFormConstraint, $group); } } @@ -130,6 +134,7 @@ class FormValidator extends ConstraintValidator if ($field->isSubmitted()) { $this->resolvedGroups[$field] = $groups; $fieldFormConstraint = new Form(); + $this->fieldFormConstraints[] = $fieldFormConstraint; $this->context->setNode($this->context->getValue(), $field, $this->context->getMetadata(), $this->context->getPropertyPath()); $validator->atPath(sprintf('children[%s]', $field->getName()))->validate($field, $fieldFormConstraint); } @@ -139,6 +144,7 @@ class FormValidator extends ConstraintValidator if ($hasChildren && $form->isRoot()) { // destroy storage to avoid memory leaks $this->resolvedGroups = new \SplObjectStorage(); + $this->fieldFormConstraints = []; } } elseif (!$form->isSynchronized()) { $childrenSynchronized = true; @@ -149,6 +155,7 @@ class FormValidator extends ConstraintValidator $childrenSynchronized = false; $fieldFormConstraint = new Form(); + $this->fieldFormConstraints[] = $fieldFormConstraint; $this->context->setNode($this->context->getValue(), $child, $this->context->getMetadata(), $this->context->getPropertyPath()); $validator->atPath(sprintf('children[%s]', $child->getName()))->validate($child, $fieldFormConstraint); }