Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Form] validate subforms in all validation groups #36865

Merged
merged 1 commit into from May 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -63,12 +63,16 @@ public function validate($form, Constraint $formConstraint)
/** @var Constraint[] $constraints */
$constraints = $config->getOption('constraints', []);

$hasChildren = $form->count() > 0;

if ($hasChildren && $form->isRoot()) {
$this->resolvedGroups = new \SplObjectStorage();
}

if ($groups instanceof GroupSequence) {
// Validate the data, the form AND nested fields in sequence
$violationsCount = $this->context->getViolations()->count();
$fieldPropertyPath = \is_object($data) ? 'children[%s]' : 'children%s';
$hasChildren = $form->count() > 0;
$this->resolvedGroups = $hasChildren ? new \SplObjectStorage() : null;

foreach ($groups->groups as $group) {
if ($validateDataGraph) {
Expand All @@ -86,20 +90,18 @@ public function validate($form, Constraint $formConstraint)
// sequence recursively, thus some fields could fail
// in different steps without breaking early enough
$this->resolvedGroups[$field] = (array) $group;
$validator->atPath(sprintf($fieldPropertyPath, $field->getPropertyPath()))->validate($field, $formConstraint);
$fieldFormConstraint = new Form();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is needed for now to prevent hash collisions (see also #36415).

$validator->atPath(sprintf($fieldPropertyPath, $field->getPropertyPath()))->validate($field, $fieldFormConstraint);
}
}

if ($violationsCount < $this->context->getViolations()->count()) {
break;
}
}

if ($hasChildren) {
// destroy storage at the end of the sequence to avoid memory leaks
$this->resolvedGroups = null;
}
} else {
$fieldPropertyPath = \is_object($data) ? 'children[%s]' : 'children%s';

if ($validateDataGraph) {
$validator->atPath('data')->validate($data, null, $groups);
}
Expand All @@ -125,6 +127,19 @@ public function validate($form, Constraint $formConstraint)
}
}
}

foreach ($form->all() as $field) {
if ($field->isSubmitted()) {
$this->resolvedGroups[$field] = $groups;
$fieldFormConstraint = new Form();
$validator->atPath(sprintf($fieldPropertyPath, $field->getPropertyPath()))->validate($field, $fieldFormConstraint);
}
}
}

if ($hasChildren && $form->isRoot()) {
// destroy storage to avoid memory leaks
$this->resolvedGroups = new \SplObjectStorage();
}
} elseif (!$form->isSynchronized()) {
$childrenSynchronized = true;
Expand Down
Expand Up @@ -13,7 +13,7 @@

use Symfony\Component\Form\AbstractExtension;
use Symfony\Component\Form\Extension\Validator\Constraints\Form;
use Symfony\Component\Validator\Constraints\Valid;
use Symfony\Component\Validator\Constraints\Traverse;
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Validator\ValidatorInterface;

Expand All @@ -37,7 +37,7 @@ public function __construct(ValidatorInterface $validator)

/* @var $metadata ClassMetadata */
$metadata->addConstraint(new Form());
$metadata->addPropertyConstraint('children', new Valid());
$metadata->addConstraint(new Traverse(false));

$this->validator = $validator;
}
Expand Down
6 changes: 3 additions & 3 deletions src/Symfony/Component/Form/Resources/config/validation.xml
Expand Up @@ -6,8 +6,8 @@

<class name="Symfony\Component\Form\Form">
<constraint name="Symfony\Component\Form\Extension\Validator\Constraints\Form" />
<property name="children">
<constraint name="Valid" />
</property>
<constraint name="Symfony\Component\Validator\Constraints\Traverse">
<option name="traverse">false</option>
</constraint>
</class>
</constraint-mapping>
Expand Up @@ -615,7 +615,8 @@ public function testViolationIfExtraData()

$this->assertTrue($form->isSubmitted());
$this->assertTrue($form->isSynchronized());
$this->expectNoValidate();

$this->expectValidateValueAt(0, 'children[child]', $form->get('child'), new Form());

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

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

$this->assertTrue($form->isSubmitted());
$this->assertTrue($form->isSynchronized());
$this->expectNoValidate();

$this->expectValidateValueAt(0, 'children[child]', $form->get('child'), new Form());

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

Expand Down
Expand Up @@ -54,9 +54,8 @@ public function test2Dot5ValidationApi()
$this->assertInstanceOf(FormConstraint::class, $metadata->getConstraints()[0]);

$this->assertSame(CascadingStrategy::NONE, $metadata->cascadingStrategy);
$this->assertSame(TraversalStrategy::IMPLICIT, $metadata->traversalStrategy);
$this->assertSame(CascadingStrategy::CASCADE, $metadata->getPropertyMetadata('children')[0]->cascadingStrategy);
$this->assertSame(TraversalStrategy::IMPLICIT, $metadata->getPropertyMetadata('children')[0]->traversalStrategy);
$this->assertSame(TraversalStrategy::NONE, $metadata->traversalStrategy);
$this->assertCount(0, $metadata->getPropertyMetadata('children'));
}

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

public function testConstraintsInDifferentGroupsOnSingleField()
{
$form = $this->createForm(FormType::class, null, [
'validation_groups' => new GroupSequence(['group1', 'group2']),
])
->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());
}

private function createForm($type, $data = null, array $options = [])
{
$validator = Validation::createValidatorBuilder()
Expand Down