Skip to content
This repository
Browse code

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

:+1:
  • Loading branch information...
commit b8d8cab1f98a8b3491be57109f9de1ee5a802363 2 parents efada56 + 0c70a41
Fabien Potencier authored January 16, 2012
2  src/Symfony/Component/Form/Extension/Validator/Type/FieldTypeValidatorExtension.php
@@ -38,6 +38,7 @@ public function buildForm(FormBuilder $builder, array $options)
38 38
         $builder
39 39
             ->setAttribute('validation_groups', $options['validation_groups'])
40 40
             ->setAttribute('validation_constraint', $options['validation_constraint'])
  41
+            ->setAttribute('cascade_validation', $options['cascade_validation'])
41 42
             ->addValidator(new DelegatingValidator($this->validator));
42 43
     }
43 44
 
@@ -46,6 +47,7 @@ public function getDefaultOptions(array $options)
46 47
         return array(
47 48
             'validation_groups' => null,
48 49
             'validation_constraint' => null,
  50
+            'cascade_validation' => false,
49 51
         );
50 52
     }
51 53
 
23  src/Symfony/Component/Form/Extension/Validator/Validator/DelegatingValidator.php
@@ -127,6 +127,29 @@ static public function validateFormData(FormInterface $form, ExecutionContext $c
127 127
         }
128 128
     }
129 129
 
  130
+    static public function validateFormChildren(FormInterface $form, ExecutionContext $context)
  131
+    {
  132
+        if ($form->getAttribute('cascade_validation')) {
  133
+            $propertyPath = $context->getPropertyPath();
  134
+            $graphWalker = $context->getGraphWalker();
  135
+
  136
+            // The Execute constraint is called on class level, so we need to
  137
+            // set the property manually
  138
+            $context->setCurrentProperty('children');
  139
+
  140
+            // Adjust the property path accordingly
  141
+            if (!empty($propertyPath)) {
  142
+                $propertyPath .= '.';
  143
+            }
  144
+
  145
+            $propertyPath .= 'children';
  146
+
  147
+            foreach (self::getFormValidationGroups($form) as $group) {
  148
+                $graphWalker->walkReference($form->getChildren(), $group, $propertyPath, true);
  149
+            }
  150
+        }
  151
+    }
  152
+
130 153
     static protected function getFormValidationGroups(FormInterface $form)
131 154
     {
132 155
         $groups = null;
7  src/Symfony/Component/Form/Resources/config/validation.xml
@@ -10,9 +10,10 @@
10 10
         <value>Symfony\Component\Form\Extension\Validator\Validator\DelegatingValidator</value>
11 11
         <value>validateFormData</value>
12 12
       </value>
  13
+      <value>
  14
+        <value>Symfony\Component\Form\Extension\Validator\Validator\DelegatingValidator</value>
  15
+        <value>validateFormChildren</value>
  16
+      </value>
13 17
     </constraint>
14  
-    <property name="children">
15  
-      <constraint name="Valid" />
16  
-    </property>
17 18
   </class>
18 19
 </constraint-mapping>
75  tests/Symfony/Tests/Component/Form/Extension/Validator/Validator/DelegatingValidatorTest.php
@@ -798,6 +798,81 @@ public function testValidateFormDataDoesNotWalkScalars()
798 798
         DelegatingValidator::validateFormData($form, $context);
799 799
     }
800 800
 
  801
+    public function testValidateFormChildren()
  802
+    {
  803
+        $graphWalker = $this->getMockGraphWalker();
  804
+        $metadataFactory = $this->getMockMetadataFactory();
  805
+        $context = new ExecutionContext('Root', $graphWalker, $metadataFactory);
  806
+        $form = $this->getBuilder()
  807
+            ->setAttribute('cascade_validation', true)
  808
+            ->setAttribute('validation_groups', array('group1', 'group2'))
  809
+            ->getForm();
  810
+        $form->add($this->getForm('firstName'));
  811
+
  812
+        $graphWalker->expects($this->at(0))
  813
+            ->method('walkReference')
  814
+            ->with($form->getChildren(), 'group1', 'children', true);
  815
+        $graphWalker->expects($this->at(1))
  816
+            ->method('walkReference')
  817
+            ->with($form->getChildren(), 'group2', 'children', true);
  818
+
  819
+        DelegatingValidator::validateFormChildren($form, $context);
  820
+    }
  821
+
  822
+    public function testValidateFormChildrenAppendsPropertyPath()
  823
+    {
  824
+        $graphWalker = $this->getMockGraphWalker();
  825
+        $metadataFactory = $this->getMockMetadataFactory();
  826
+        $context = new ExecutionContext('Root', $graphWalker, $metadataFactory);
  827
+        $context->setPropertyPath('path');
  828
+        $form = $this->getBuilder()
  829
+            ->setAttribute('cascade_validation', true)
  830
+            ->getForm();
  831
+        $form->add($this->getForm('firstName'));
  832
+
  833
+        $graphWalker->expects($this->once())
  834
+            ->method('walkReference')
  835
+            ->with($form->getChildren(), 'Default', 'path.children', true);
  836
+
  837
+        DelegatingValidator::validateFormChildren($form, $context);
  838
+    }
  839
+
  840
+    public function testValidateFormChildrenSetsCurrentPropertyToData()
  841
+    {
  842
+        $graphWalker = $this->getMockGraphWalker();
  843
+        $metadataFactory = $this->getMockMetadataFactory();
  844
+        $context = new ExecutionContext('Root', $graphWalker, $metadataFactory);
  845
+        $form = $this->getBuilder()
  846
+            ->setAttribute('cascade_validation', true)
  847
+            ->getForm();
  848
+        $form->add($this->getForm('firstName'));
  849
+        $test = $this;
  850
+
  851
+        $graphWalker->expects($this->once())
  852
+            ->method('walkReference')
  853
+            ->will($this->returnCallback(function () use ($context, $test) {
  854
+                $test->assertEquals('children', $context->getCurrentProperty());
  855
+            }));
  856
+
  857
+        DelegatingValidator::validateFormChildren($form, $context);
  858
+    }
  859
+
  860
+    public function testValidateFormChildrenDoesNothingIfDisabled()
  861
+    {
  862
+        $graphWalker = $this->getMockGraphWalker();
  863
+        $metadataFactory = $this->getMockMetadataFactory();
  864
+        $context = new ExecutionContext('Root', $graphWalker, $metadataFactory);
  865
+        $form = $this->getBuilder()
  866
+            ->setAttribute('cascade_validation', false)
  867
+            ->getForm();
  868
+        $form->add($this->getForm('firstName'));
  869
+
  870
+        $graphWalker->expects($this->never())
  871
+            ->method('walkReference');
  872
+
  873
+        DelegatingValidator::validateFormChildren($form, $context);
  874
+    }
  875
+
801 876
     public function testValidateIgnoresNonRoot()
802 877
     {
803 878
         $form = $this->getMockForm();

0 notes on commit b8d8cab

Gladhon

It would be very nice, if the default options are configurable. Because it would be very easy to fix backwards compatible breaks here.

Bernhard Schussek

You can override the validator extension with a custom one. Create an extension of the class where you override getDefaultOptions() and register it in the DIC using the same tag as the class bundled in the core. Then it should be used instead AFAIK.

Please sign in to comment.
Something went wrong with that request. Please try again.