diff --git a/src/Symfony/Component/Form/Extension/Validator/Type/FieldTypeValidatorExtension.php b/src/Symfony/Component/Form/Extension/Validator/Type/FieldTypeValidatorExtension.php index 259510137480..1cd0b10f595f 100644 --- a/src/Symfony/Component/Form/Extension/Validator/Type/FieldTypeValidatorExtension.php +++ b/src/Symfony/Component/Form/Extension/Validator/Type/FieldTypeValidatorExtension.php @@ -38,6 +38,7 @@ public function buildForm(FormBuilder $builder, array $options) $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 @@ public function getDefaultOptions(array $options) return array( 'validation_groups' => null, 'validation_constraint' => null, + 'cascade_validation' => false, ); } diff --git a/src/Symfony/Component/Form/Extension/Validator/Validator/DelegatingValidator.php b/src/Symfony/Component/Form/Extension/Validator/Validator/DelegatingValidator.php index a8b06724024f..2b7c099403d7 100644 --- a/src/Symfony/Component/Form/Extension/Validator/Validator/DelegatingValidator.php +++ b/src/Symfony/Component/Form/Extension/Validator/Validator/DelegatingValidator.php @@ -127,6 +127,29 @@ static public function validateFormData(FormInterface $form, ExecutionContext $c } } + 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; diff --git a/src/Symfony/Component/Form/Resources/config/validation.xml b/src/Symfony/Component/Form/Resources/config/validation.xml index 5512560c6d4e..7bd69402c094 100644 --- a/src/Symfony/Component/Form/Resources/config/validation.xml +++ b/src/Symfony/Component/Form/Resources/config/validation.xml @@ -10,9 +10,10 @@ Symfony\Component\Form\Extension\Validator\Validator\DelegatingValidator validateFormData + + Symfony\Component\Form\Extension\Validator\Validator\DelegatingValidator + validateFormChildren + - - - diff --git a/tests/Symfony/Tests/Component/Form/Extension/Validator/Validator/DelegatingValidatorTest.php b/tests/Symfony/Tests/Component/Form/Extension/Validator/Validator/DelegatingValidatorTest.php index 22701304a62e..1d16892dc67d 100644 --- a/tests/Symfony/Tests/Component/Form/Extension/Validator/Validator/DelegatingValidatorTest.php +++ b/tests/Symfony/Tests/Component/Form/Extension/Validator/Validator/DelegatingValidatorTest.php @@ -798,6 +798,81 @@ public function testValidateFormDataDoesNotWalkScalars() 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();