Browse Source

[Form] Implemented error bubbling

Bernhard Schussek 14 years ago
parent
commit
c1abf08d9c

+ 23 - 6
src/Symfony/Component/Form/Form.php

@@ -83,6 +83,7 @@ class Form implements \IteratorAggregate, FormInterface
 
     private $dataMapper;
     private $errors = array();
+    private $errorBubbling;
     private $name = '';
     private $parent;
     private $bound = false;
@@ -110,7 +111,8 @@ class Form implements \IteratorAggregate, FormInterface
         FormRendererInterface $renderer = null, DataTransformerInterface $clientTransformer = null,
         DataTransformerInterface $normTransformer = null,
         DataMapperInterface $dataMapper = null, array $validators = array(),
-        $required = false, $readOnly = false, array $attributes = array())
+        $required = false, $readOnly = false, $errorBubbling = false,
+        array $attributes = array())
     {
         foreach ($validators as $validator) {
             if (!$validator instanceof FormValidatorInterface) {
@@ -128,6 +130,7 @@ class Form implements \IteratorAggregate, FormInterface
         $this->required = $required;
         $this->readOnly = $readOnly;
         $this->attributes = $attributes;
+        $this->errorBubbling = $errorBubbling;
 
         if ($renderer) {
             $renderer->setForm($this);
@@ -136,11 +139,11 @@ class Form implements \IteratorAggregate, FormInterface
         $this->setData(null);
     }
 
-    /**
-     * Cloning is not supported
-     */
-    private function __clone()
+    public function __clone()
     {
+        foreach ($this->children as $key => $child) {
+            $this->children[$key] = clone $child;
+        }
     }
 
     /**
@@ -414,7 +417,21 @@ class Form implements \IteratorAggregate, FormInterface
      */
     public function addError(FormError $error)
     {
-        $this->errors[] = $error;
+        if ($this->parent && $this->errorBubbling) {
+            $this->parent->addError($error);
+        } else {
+            $this->errors[] = $error;
+        }
+    }
+
+    /**
+     * Returns whether errors bubble up to the parent
+     *
+     * @return Boolean
+     */
+    public function getErrorBubbling()
+    {
+        return $this->errorBubbling;
     }
 
     /**

+ 15 - 0
src/Symfony/Component/Form/FormBuilder.php

@@ -61,6 +61,8 @@ class FormBuilder
 
     private $dataMapper;
 
+    private $errorBubbling = false;
+
     public function __construct(EventDispatcherInterface $dispatcher)
     {
         $this->dispatcher = $dispatcher;
@@ -148,6 +150,18 @@ class FormBuilder
         return $this->required;
     }
 
+    public function setErrorBubbling($errorBubbling)
+    {
+        $this->errorBubbling = $errorBubbling;
+
+        return $this;
+    }
+
+    public function getErrorBubbling()
+    {
+        return $this->errorBubbling;
+    }
+
     public function addValidator(FormValidatorInterface $validator)
     {
         $this->validators[] = $validator;
@@ -511,6 +525,7 @@ class FormBuilder
             $this->getValidators(),
             $this->getRequired(),
             $this->getReadOnly(),
+            $this->getErrorBubbling(),
             $this->getAttributes()
         );
 

+ 0 - 2
src/Symfony/Component/Form/Type/CsrfType.php

@@ -36,8 +36,6 @@ class CsrfType extends AbstractType
             ->addValidator(new CallbackValidator(
                 function (FormInterface $field) use ($csrfProvider, $pageId) {
                     if (!$csrfProvider->isCsrfTokenValid($pageId, $field->getData())) {
-                        // FIXME this error is currently not displayed
-                        // it needs to be passed up to the form
                         $field->addError(new FormError('The CSRF token is invalid. Please try to resubmit the form'));
                     }
                 }

+ 2 - 0
src/Symfony/Component/Form/Type/FieldType.php

@@ -54,6 +54,7 @@ class FieldType extends AbstractType
 
         $builder->setRequired($options['required'])
             ->setReadOnly($options['read_only'])
+            ->setErrorBubbling($options['error_bubbling'])
             ->setAttribute('by_reference', $options['by_reference'])
             ->setAttribute('property_path', $options['property_path'])
             ->setAttribute('validation_groups', $options['validation_groups'])
@@ -80,6 +81,7 @@ class FieldType extends AbstractType
             'property_path' => false,
             'by_reference' => true,
             'validation_groups' => true,
+            'error_bubbling' => false,
         );
     }
 

+ 3 - 0
src/Symfony/Component/Form/Type/FormType.php

@@ -46,6 +46,9 @@ class FormType extends AbstractType
             'csrf_provider' => null,
             'validation_groups' => null,
             'virtual' => false,
+            // Errors in forms bubble by default, so that form errors will
+            // end up as global errors in the root form
+            'error_bubbling' => true,
         );
     }
 

+ 2 - 0
src/Symfony/Component/Form/Type/HiddenType.php

@@ -19,6 +19,8 @@ class HiddenType extends AbstractType
     {
         return array(
             'template' => 'hidden',
+            // Pass errors to the parent
+            'error_bubbling' => true,
         );
     }
 

+ 58 - 0
tests/Symfony/Tests/Component/Form/FormTest.php

@@ -0,0 +1,58 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Tests\Component\Form;
+
+use Symfony\Component\Form\Form;
+use Symfony\Component\Form\FormBuilder;
+use Symfony\Component\Form\FormError;
+
+class FormTest extends \PHPUnit_Framework_TestCase
+{
+    private $dispatcher;
+
+    private $builder;
+
+    private $form;
+
+    protected function setUp()
+    {
+        $this->dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
+        $this->builder = new FormBuilder($this->dispatcher);
+        $this->form = $this->builder->getForm();
+    }
+
+    public function testErrorsBubbleUpIfEnabled()
+    {
+        $error = new FormError('Error!');
+        $parent = $this->form;
+        $form = $this->builder->setErrorBubbling(true)->getForm();
+
+        $form->setParent($parent);
+        $form->addError($error);
+
+        $this->assertEquals(array(), $form->getErrors());
+        $this->assertEquals(array($error), $parent->getErrors());
+    }
+
+    public function testErrorsDontBubbleUpIfDisabled()
+    {
+        $error = new FormError('Error!');
+        $parent = $this->form;
+        $form = $this->builder->setErrorBubbling(false)->getForm();
+
+        $form->setParent($parent);
+        $form->addError($error);
+
+        $this->assertEquals(array($error), $form->getErrors());
+        $this->assertEquals(array(), $parent->getErrors());
+    }
+}