Skip to content

Commit 27d9f82

Browse files
committed
validate subforms in all validation groups
1 parent 5ec5bfb commit 27d9f82

File tree

4 files changed

+58
-15
lines changed

4 files changed

+58
-15
lines changed

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

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,16 @@ public function validate($form, Constraint $formConstraint)
6363
/** @var Constraint[] $constraints */
6464
$constraints = $config->getOption('constraints', []);
6565

66+
$hasChildren = $form->count() > 0;
67+
68+
if ($hasChildren && $form->isRoot()) {
69+
$this->resolvedGroups = new \SplObjectStorage();
70+
}
71+
6672
if ($groups instanceof GroupSequence) {
6773
// Validate the data, the form AND nested fields in sequence
6874
$violationsCount = $this->context->getViolations()->count();
6975
$fieldPropertyPath = \is_object($data) ? 'children[%s]' : 'children%s';
70-
$hasChildren = $form->count() > 0;
71-
$this->resolvedGroups = $hasChildren ? new \SplObjectStorage() : null;
7276

7377
foreach ($groups->groups as $group) {
7478
if ($validateDataGraph) {
@@ -86,20 +90,18 @@ public function validate($form, Constraint $formConstraint)
8690
// sequence recursively, thus some fields could fail
8791
// in different steps without breaking early enough
8892
$this->resolvedGroups[$field] = (array) $group;
89-
$validator->atPath(sprintf($fieldPropertyPath, $field->getPropertyPath()))->validate($field, $formConstraint);
93+
$fieldFormConstraint = new Form();
94+
$validator->atPath(sprintf($fieldPropertyPath, $field->getPropertyPath()))->validate($field, $fieldFormConstraint);
9095
}
9196
}
9297

9398
if ($violationsCount < $this->context->getViolations()->count()) {
9499
break;
95100
}
96101
}
97-
98-
if ($hasChildren) {
99-
// destroy storage at the end of the sequence to avoid memory leaks
100-
$this->resolvedGroups = null;
101-
}
102102
} else {
103+
$fieldPropertyPath = \is_object($data) ? 'children[%s]' : 'children%s';
104+
103105
if ($validateDataGraph) {
104106
$validator->atPath('data')->validate($data, null, $groups);
105107
}
@@ -125,6 +127,19 @@ public function validate($form, Constraint $formConstraint)
125127
}
126128
}
127129
}
130+
131+
foreach ($form->all() as $field) {
132+
if ($field->isSubmitted()) {
133+
$this->resolvedGroups[$field] = $groups;
134+
$fieldFormConstraint = new Form();
135+
$validator->atPath(sprintf($fieldPropertyPath, $field->getPropertyPath()))->validate($field, $fieldFormConstraint);
136+
}
137+
}
138+
}
139+
140+
if ($hasChildren && $form->isRoot()) {
141+
// destroy storage to avoid memory leaks
142+
$this->resolvedGroups = null;
128143
}
129144
} elseif (!$form->isSynchronized()) {
130145
$childrenSynchronized = true;

src/Symfony/Component/Form/Extension/Validator/ValidatorExtension.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313

1414
use Symfony\Component\Form\AbstractExtension;
1515
use Symfony\Component\Form\Extension\Validator\Constraints\Form;
16-
use Symfony\Component\Validator\Constraints\Valid;
1716
use Symfony\Component\Validator\Mapping\ClassMetadata;
17+
use Symfony\Component\Validator\Mapping\TraversalStrategy;
1818
use Symfony\Component\Validator\Validator\ValidatorInterface;
1919

2020
/**
@@ -37,7 +37,7 @@ public function __construct(ValidatorInterface $validator)
3737

3838
/* @var $metadata ClassMetadata */
3939
$metadata->addConstraint(new Form());
40-
$metadata->addPropertyConstraint('children', new Valid());
40+
$metadata->traversalStrategy = TraversalStrategy::NONE;
4141

4242
$this->validator = $validator;
4343
}

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -615,7 +615,8 @@ public function testViolationIfExtraData()
615615

616616
$this->assertTrue($form->isSubmitted());
617617
$this->assertTrue($form->isSynchronized());
618-
$this->expectNoValidate();
618+
619+
$this->expectValidateValueAt(0, 'children[child]', $form->get('child'), new Form());
619620

620621
$this->validator->validate($form, new Form());
621622

@@ -638,7 +639,8 @@ public function testViolationFormatIfMultipleExtraFields()
638639

639640
$this->assertTrue($form->isSubmitted());
640641
$this->assertTrue($form->isSynchronized());
641-
$this->expectNoValidate();
642+
643+
$this->expectValidateValueAt(0, 'children[child]', $form->get('child'), new Form());
642644

643645
$this->validator->validate($form, new Form());
644646

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

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,8 @@ public function test2Dot5ValidationApi()
5454
$this->assertInstanceOf(FormConstraint::class, $metadata->getConstraints()[0]);
5555

5656
$this->assertSame(CascadingStrategy::NONE, $metadata->cascadingStrategy);
57-
$this->assertSame(TraversalStrategy::IMPLICIT, $metadata->traversalStrategy);
58-
$this->assertSame(CascadingStrategy::CASCADE, $metadata->getPropertyMetadata('children')[0]->cascadingStrategy);
59-
$this->assertSame(TraversalStrategy::IMPLICIT, $metadata->getPropertyMetadata('children')[0]->traversalStrategy);
57+
$this->assertSame(TraversalStrategy::NONE, $metadata->traversalStrategy);
58+
$this->assertCount(0, $metadata->getPropertyMetadata('children'));
6059
}
6160

6261
public function testDataConstraintsInvalidateFormEvenIfFieldIsNotSubmitted()
@@ -138,6 +137,33 @@ public function testFieldsValidateInSequenceWithNestedGroupsArray()
138137
$this->assertInstanceOf(Length::class, $errors[1]->getCause()->getConstraint());
139138
}
140139

140+
public function testConstraintsInDifferentGroupsOnSingleField()
141+
{
142+
$form = $this->createForm(FormType::class, null, [
143+
'validation_groups' => new GroupSequence(['group1', 'group2']),
144+
])
145+
->add('foo', TextType::class, [
146+
'constraints' => [
147+
new NotBlank([
148+
'groups' => ['group1'],
149+
]),
150+
new Length([
151+
'groups' => ['group2'],
152+
'max' => 3,
153+
]),
154+
],
155+
]);
156+
$form->submit([
157+
'foo' => 'test@example.com',
158+
]);
159+
160+
$errors = $form->getErrors(true);
161+
162+
$this->assertFalse($form->isValid());
163+
$this->assertCount(1, $errors);
164+
$this->assertInstanceOf(Length::class, $errors[0]->getCause()->getConstraint());
165+
}
166+
141167
private function createForm($type, $data = null, array $options = [])
142168
{
143169
$validator = Validation::createValidatorBuilder()

0 commit comments

Comments
 (0)