merged branch bschussek/optional-form-child-validation (PR #3128)

Commits
-------

0c70a41 [Form] Made validation of form children configurable. Set the option "cascade_validation" to `true` if you need it.

Discussion
----------

[Form] Made validation of form children configurable

Bug fix: yes
Feature addition: yes
Backwards compatibility break: yes
Symfony2 tests pass: yes
Fixes the following tickets: #797
Todo: adapt documentation

![Travis Build Status](https://secure.travis-ci.org/bschussek/symfony.png?branch=optional-form-child-validation)

Child forms now aren't validated anymore by default. This is not a problem as long as @Valid constraints are properly put in your model. If you want to enable cascading validation, for example when there is no connection between the parent and the child model, you can set the option "cascade_validation" in the parent form to true.

This change is not backwards compatible, but from my estimation the break should not affect many applications.

---------------------------------------------------------------------------

by kriswallsmith at 2012-01-16T19:59:25Z

👍
This commit is contained in:
Fabien Potencier 2012-01-16 21:51:28 +01:00
commit b8d8cab1f9
4 changed files with 104 additions and 3 deletions

View File

@ -38,6 +38,7 @@ class FieldTypeValidatorExtension extends AbstractTypeExtension
$builder
->setAttribute('validation_groups', $options['validation_groups'])
->setAttribute('validation_constraint', $options['validation_constraint'])
->setAttribute('cascade_validation', $options['cascade_validation'])
->addValidator(new DelegatingValidator($this->validator));
}
@ -46,6 +47,7 @@ class FieldTypeValidatorExtension extends AbstractTypeExtension
return array(
'validation_groups' => null,
'validation_constraint' => null,
'cascade_validation' => false,
);
}

View File

@ -127,6 +127,29 @@ class DelegatingValidator implements FormValidatorInterface
}
}
static public function validateFormChildren(FormInterface $form, ExecutionContext $context)
{
if ($form->getAttribute('cascade_validation')) {
$propertyPath = $context->getPropertyPath();
$graphWalker = $context->getGraphWalker();
// The Execute constraint is called on class level, so we need to
// set the property manually
$context->setCurrentProperty('children');
// Adjust the property path accordingly
if (!empty($propertyPath)) {
$propertyPath .= '.';
}
$propertyPath .= 'children';
foreach (self::getFormValidationGroups($form) as $group) {
$graphWalker->walkReference($form->getChildren(), $group, $propertyPath, true);
}
}
}
static protected function getFormValidationGroups(FormInterface $form)
{
$groups = null;

View File

@ -10,9 +10,10 @@
<value>Symfony\Component\Form\Extension\Validator\Validator\DelegatingValidator</value>
<value>validateFormData</value>
</value>
<value>
<value>Symfony\Component\Form\Extension\Validator\Validator\DelegatingValidator</value>
<value>validateFormChildren</value>
</value>
</constraint>
<property name="children">
<constraint name="Valid" />
</property>
</class>
</constraint-mapping>

View File

@ -798,6 +798,81 @@ class DelegatingValidatorTest extends \PHPUnit_Framework_TestCase
DelegatingValidator::validateFormData($form, $context);
}
public function testValidateFormChildren()
{
$graphWalker = $this->getMockGraphWalker();
$metadataFactory = $this->getMockMetadataFactory();
$context = new ExecutionContext('Root', $graphWalker, $metadataFactory);
$form = $this->getBuilder()
->setAttribute('cascade_validation', true)
->setAttribute('validation_groups', array('group1', 'group2'))
->getForm();
$form->add($this->getForm('firstName'));
$graphWalker->expects($this->at(0))
->method('walkReference')
->with($form->getChildren(), 'group1', 'children', true);
$graphWalker->expects($this->at(1))
->method('walkReference')
->with($form->getChildren(), 'group2', 'children', true);
DelegatingValidator::validateFormChildren($form, $context);
}
public function testValidateFormChildrenAppendsPropertyPath()
{
$graphWalker = $this->getMockGraphWalker();
$metadataFactory = $this->getMockMetadataFactory();
$context = new ExecutionContext('Root', $graphWalker, $metadataFactory);
$context->setPropertyPath('path');
$form = $this->getBuilder()
->setAttribute('cascade_validation', true)
->getForm();
$form->add($this->getForm('firstName'));
$graphWalker->expects($this->once())
->method('walkReference')
->with($form->getChildren(), 'Default', 'path.children', true);
DelegatingValidator::validateFormChildren($form, $context);
}
public function testValidateFormChildrenSetsCurrentPropertyToData()
{
$graphWalker = $this->getMockGraphWalker();
$metadataFactory = $this->getMockMetadataFactory();
$context = new ExecutionContext('Root', $graphWalker, $metadataFactory);
$form = $this->getBuilder()
->setAttribute('cascade_validation', true)
->getForm();
$form->add($this->getForm('firstName'));
$test = $this;
$graphWalker->expects($this->once())
->method('walkReference')
->will($this->returnCallback(function () use ($context, $test) {
$test->assertEquals('children', $context->getCurrentProperty());
}));
DelegatingValidator::validateFormChildren($form, $context);
}
public function testValidateFormChildrenDoesNothingIfDisabled()
{
$graphWalker = $this->getMockGraphWalker();
$metadataFactory = $this->getMockMetadataFactory();
$context = new ExecutionContext('Root', $graphWalker, $metadataFactory);
$form = $this->getBuilder()
->setAttribute('cascade_validation', false)
->getForm();
$form->add($this->getForm('firstName'));
$graphWalker->expects($this->never())
->method('walkReference');
DelegatingValidator::validateFormChildren($form, $context);
}
public function testValidateIgnoresNonRoot()
{
$form = $this->getMockForm();