ソースを参照

[Form] Added support for virtual field groups

Bernhard Schussek 14 年 前
コミット
ba422e8472

+ 21 - 4
src/Symfony/Component/Form/FieldGroup.php

@@ -13,7 +13,6 @@ namespace Symfony\Component\Form;
 
 use Symfony\Component\Form\Exception\AlreadyBoundException;
 use Symfony\Component\Form\Exception\UnexpectedTypeException;
-use Symfony\Component\Form\Iterator\RecursiveFieldsWithPropertyPathIterator;
 
 /**
  * FieldGroup represents an array of widgets bind to names and values.
@@ -34,6 +33,16 @@ class FieldGroup extends Field implements \IteratorAggregate, FieldGroupInterfac
      */
     protected $extraFields = array();
 
+    /**
+     * @inheritDoc
+     */
+    public function __construct($key, array $options = array())
+    {
+        $this->addOption('virtual', false);
+
+        parent::__construct($key, $options);
+    }
+
     /**
      * Clones this group
      */
@@ -317,7 +326,7 @@ class FieldGroup extends Field implements \IteratorAggregate, FieldGroupInterfac
      */
     protected function updateFromObject(&$objectOrArray)
     {
-        $iterator = new RecursiveFieldsWithPropertyPathIterator($this);
+        $iterator = new RecursiveFieldIterator($this);
         $iterator = new \RecursiveIteratorIterator($iterator);
 
         foreach ($iterator as $field) {
@@ -336,7 +345,7 @@ class FieldGroup extends Field implements \IteratorAggregate, FieldGroupInterfac
      */
     protected function updateObject(&$objectOrArray)
     {
-        $iterator = new RecursiveFieldsWithPropertyPathIterator($this);
+        $iterator = new RecursiveFieldIterator($this);
         $iterator = new \RecursiveIteratorIterator($iterator);
 
         foreach ($iterator as $field) {
@@ -357,6 +366,14 @@ class FieldGroup extends Field implements \IteratorAggregate, FieldGroupInterfac
         return $data;
     }
 
+    /**
+     * @inheritDoc
+     */
+    public function isVirtual()
+    {
+        return $this->getOption('virtual');
+    }
+
     /**
      * Returns whether this form was bound with extra fields
      *
@@ -407,7 +424,7 @@ class FieldGroup extends Field implements \IteratorAggregate, FieldGroupInterfac
                     return;
                 }
             } else if ($type === self::DATA_ERROR) {
-                $iterator = new RecursiveFieldsWithPropertyPathIterator($this);
+                $iterator = new RecursiveFieldIterator($this);
                 $iterator = new \RecursiveIteratorIterator($iterator);
 
                 foreach ($iterator as $field) {

+ 23 - 0
src/Symfony/Component/Form/FieldGroupInterface.php

@@ -18,4 +18,27 @@ namespace Symfony\Component\Form;
  */
 interface FieldGroupInterface extends FieldInterface, \ArrayAccess, \Traversable, \Countable
 {
+    /**
+     * Returns whether this field group is virtual
+     *
+     * Virtual field groups are skipped when mapping property paths of a form
+     * tree to an object.
+     *
+     * Example:
+     *
+     * <code>
+     * $group = new FieldGroup('address');
+     * $group->add(new TextField('street'));
+     * $group->add(new TextField('postal_code'));
+     * $form->add($group);
+     * </code>
+     *
+     * If $group is non-virtual, the fields "street" and "postal_code"
+     * are mapped to the property paths "address.street" and
+     * "address.postal_code". If $group is virtual though, the fields are
+     * mapped directly to "street" and "postal_code".
+     *
+     * @return boolean  Whether the group is virtual
+     */
+    public function isVirtual();
 }

+ 12 - 5
src/Symfony/Component/Form/Iterator/RecursiveFieldsWithPropertyPathIterator.php

@@ -1,6 +1,6 @@
 <?php
 
-namespace Symfony\Component\Form\Iterator;
+namespace Symfony\Component\Form;
 
 /*
  * This file is part of the Symfony framework.
@@ -11,9 +11,15 @@ namespace Symfony\Component\Form\Iterator;
  * with this source code in the file LICENSE.
  */
 
-use Symfony\Component\Form\FieldGroupInterface;
-
-class RecursiveFieldsWithPropertyPathIterator extends \IteratorIterator implements \RecursiveIterator
+/**
+ * Iterator that traverses fields of a field group
+ *
+ * If the iterator encounters a virtual field group, it enters the field
+ * group and traverses its children as well.
+ *
+ * @author Bernhard Schussek <bernhard.schussek@symfony-project.com>
+ */
+class RecursiveFieldIterator extends \IteratorIterator implements \RecursiveIterator
 {
     public function __construct(FieldGroupInterface $group)
     {
@@ -27,6 +33,7 @@ class RecursiveFieldsWithPropertyPathIterator extends \IteratorIterator implemen
 
     public function hasChildren()
     {
-        return $this->current() instanceof FieldGroupInterface && $this->current()->getPropertyPath() === null;
+        return $this->current() instanceof FieldGroupInterface
+                && $this->current()->isVirtual();
     }
 }

+ 54 - 4
tests/Symfony/Tests/Component/Form/FieldGroupTest.php

@@ -322,7 +322,7 @@ class FieldGroupTest extends \PHPUnit_Framework_TestCase
         $group->addError($error, $path->getIterator(), FieldGroup::DATA_ERROR);
     }
 
-    public function testAddErrorMapsErrorsOntoFieldsInAnonymousGroups()
+    public function testAddErrorMapsErrorsOntoFieldsInVirtualGroups()
     {
         $error = new FieldError('Message');
 
@@ -339,9 +339,9 @@ class FieldGroupTest extends \PHPUnit_Framework_TestCase
                     ->with($this->equalTo($error), $this->equalTo($expectedPathIterator), $this->equalTo(FieldGroup::DATA_ERROR));
 
         $group = new TestFieldGroup('author');
-        $group2 = new TestFieldGroup('anonymous', array('property_path' => null));
-        $group2->add($field);
-        $group->add($group2);
+        $nestedGroup = new TestFieldGroup('nested', array('virtual' => true));
+        $nestedGroup->add($field);
+        $group->add($nestedGroup);
 
         $path = new PropertyPath('address');
 
@@ -469,6 +469,56 @@ class FieldGroupTest extends \PHPUnit_Framework_TestCase
         $group->setData($originalAuthor);
     }
 
+    /**
+     * The use case for this test are groups whose fields should be mapped
+     * directly onto properties of the form's object.
+     *
+     * Example:
+     *
+     * <code>
+     * $dateRangeField = new FieldGroup('dateRange');
+     * $dateRangeField->add(new DateField('startDate'));
+     * $dateRangeField->add(new DateField('endDate'));
+     * $form->add($dateRangeField);
+     * </code>
+     *
+     * If $dateRangeField is not virtual, the property "dateRange" must be
+     * present on the form's object. In this property, an object or array
+     * with the properties "startDate" and "endDate" is expected.
+     *
+     * If $dateRangeField is virtual though, it's children are mapped directly
+     * onto the properties "startDate" and "endDate" of the form's object.
+     */
+    public function testSetDataSkipsVirtualFieldGroups()
+    {
+        $author = new Author();
+        $author->firstName = 'Foo';
+
+        $group = new TestFieldGroup('author');
+        $nestedGroup = new TestFieldGroup('personal_data', array(
+            'virtual' => true,
+        ));
+
+        // both fields are in the nested group but receive the object of the
+        // top-level group because the nested group is virtual
+        $field = $this->createMockField('firstName');
+        $field->expects($this->once())
+                    ->method('updateFromProperty')
+                    ->with($this->equalTo($author));
+
+        $nestedGroup->add($field);
+
+        $field = $this->createMockField('lastName');
+        $field->expects($this->once())
+                    ->method('updateFromProperty')
+                    ->with($this->equalTo($author));
+
+        $nestedGroup->add($field);
+
+        $group->add($nestedGroup);
+        $group->setData($author);
+    }
+
     public function testSetDataThrowsAnExceptionIfArgumentIsNotObjectOrArray()
     {
         $group = new TestFieldGroup('author');