[Form] Added support for virtual field groups
This commit is contained in:
parent
8b843e2662
commit
ba422e8472
@ -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) {
|
||||
|
@ -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();
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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');
|
||||
|
Reference in New Issue
Block a user