diff --git a/src/Symfony/Component/Form/Form.php b/src/Symfony/Component/Form/Form.php index bcab8afdc2..556c190d8e 100644 --- a/src/Symfony/Component/Form/Form.php +++ b/src/Symfony/Component/Form/Form.php @@ -536,7 +536,10 @@ class Form implements \IteratorAggregate, FormInterface $submittedData = array(); } - foreach ($this->children as $name => $child) { + for (reset($this->children); false !== current($this->children); next($this->children)) { + $child = current($this->children); + $name = key($this->children); + if (array_key_exists($name, $submittedData) || $clearMissing) { $child->submit(isset($submittedData[$name]) ? $submittedData[$name] : null, $clearMissing); unset($submittedData[$name]); @@ -762,7 +765,7 @@ class Form implements \IteratorAggregate, FormInterface /** * {@inheritdoc} */ - public function all() + public function &all() { return $this->children; } @@ -833,7 +836,8 @@ class Form implements \IteratorAggregate, FormInterface $child->setParent($this); if (!$this->lockSetData && $this->defaultDataSet && !$this->config->getInheritData()) { - $childrenIterator = new InheritDataAwareIterator(array($child)); + $children = array($child); + $childrenIterator = new InheritDataAwareIterator($children); $childrenIterator = new \RecursiveIteratorIterator($childrenIterator); $this->config->getDataMapper()->mapDataToForms($viewData, $childrenIterator); } diff --git a/src/Symfony/Component/Form/Tests/CompoundFormTest.php b/src/Symfony/Component/Form/Tests/CompoundFormTest.php index 4c5cfb9a48..61f91963c1 100644 --- a/src/Symfony/Component/Form/Tests/CompoundFormTest.php +++ b/src/Symfony/Component/Form/Tests/CompoundFormTest.php @@ -399,6 +399,34 @@ class CompoundFormTest extends AbstractFormTest $form->setData('foo'); } + public function testSubmitSupportsDynamicAdditionAndRemovalOfChildren() + { + $child = $this->getMockForm('child'); + $childToBeRemoved = $this->getMockForm('removed'); + $childToBeAdded = $this->getMockForm('added'); + + $this->form->add($child); + $this->form->add($childToBeRemoved); + + $form = $this->form; + + $child->expects($this->once()) + ->method('submit') + ->will($this->returnCallback(function () use ($form, $childToBeAdded) { + $form->remove('removed'); + $form->add($childToBeAdded); + })); + + $childToBeRemoved->expects($this->never()) + ->method('submit'); + + $childToBeAdded->expects($this->once()) + ->method('submit'); + + // pass NULL to all children + $this->form->submit(array()); + } + public function testSubmitMapsSubmittedChildrenOntoExistingViewData() { $test = $this; diff --git a/src/Symfony/Component/Form/Tests/Util/InheritDataAwareIteratorTest.php b/src/Symfony/Component/Form/Tests/Util/InheritDataAwareIteratorTest.php new file mode 100644 index 0000000000..19a0940bc0 --- /dev/null +++ b/src/Symfony/Component/Form/Tests/Util/InheritDataAwareIteratorTest.php @@ -0,0 +1,122 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Tests\Util; + +use Symfony\Component\Form\Util\InheritDataAwareIterator; + +/** + * @author Bernhard Schussek + */ +class InheritDataAwareIteratorTest extends \PHPUnit_Framework_TestCase +{ + public function testSupportDynamicModification() + { + $form = $this->getMockForm('form'); + $formToBeAdded = $this->getMockForm('added'); + $formToBeRemoved = $this->getMockForm('removed'); + + $forms = array('form' => $form, 'removed' => $formToBeRemoved); + $iterator = new InheritDataAwareIterator($forms); + + $iterator->rewind(); + $this->assertTrue($iterator->valid()); + $this->assertSame('form', $iterator->key()); + $this->assertSame($form, $iterator->current()); + + // dynamic modification + unset($forms['removed']); + $forms['added'] = $formToBeAdded; + + // continue iteration + $iterator->next(); + $this->assertTrue($iterator->valid()); + $this->assertSame('added', $iterator->key()); + $this->assertSame($formToBeAdded, $iterator->current()); + + // end of array + $iterator->next(); + $this->assertFalse($iterator->valid()); + } + + public function testSupportDynamicModificationInRecursiveCall() + { + $inheritingForm = $this->getMockForm('inheriting'); + $form = $this->getMockForm('form'); + $formToBeAdded = $this->getMockForm('added'); + $formToBeRemoved = $this->getMockForm('removed'); + + $inheritingForm->getConfig()->expects($this->any()) + ->method('getInheritData') + ->will($this->returnValue(true)); + + $inheritingForm->add($form); + $inheritingForm->add($formToBeRemoved); + + $forms = array('inheriting' => $inheritingForm); + $iterator = new InheritDataAwareIterator($forms); + + $iterator->rewind(); + $this->assertTrue($iterator->valid()); + $this->assertSame('inheriting', $iterator->key()); + $this->assertSame($inheritingForm, $iterator->current()); + $this->assertTrue($iterator->hasChildren()); + + // enter nested iterator + $nestedIterator = $iterator->getChildren(); + $this->assertSame('form', $nestedIterator->key()); + $this->assertSame($form, $nestedIterator->current()); + $this->assertFalse($nestedIterator->hasChildren()); + + // dynamic modification + $inheritingForm->remove('removed'); + $inheritingForm->add($formToBeAdded); + + // continue iteration - nested iterator discovers change in the form + $nestedIterator->next(); + $this->assertTrue($nestedIterator->valid()); + $this->assertSame('added', $nestedIterator->key()); + $this->assertSame($formToBeAdded, $nestedIterator->current()); + + // end of array + $nestedIterator->next(); + $this->assertFalse($nestedIterator->valid()); + } + + /** + * @param string $name + * + * @return \PHPUnit_Framework_MockObject_MockObject + */ + protected function getMockForm($name = 'name') + { + $config = $this->getMock('Symfony\Component\Form\FormConfigInterface'); + + $config->expects($this->any()) + ->method('getName') + ->will($this->returnValue($name)); + $config->expects($this->any()) + ->method('getCompound') + ->will($this->returnValue(true)); + $config->expects($this->any()) + ->method('getDataMapper') + ->will($this->returnValue($this->getMock('Symfony\Component\Form\DataMapperInterface'))); + $config->expects($this->any()) + ->method('getEventDispatcher') + ->will($this->returnValue($this->getMock('Symfony\Component\EventDispatcher\EventDispatcher'))); + + return $this->getMockBuilder('Symfony\Component\Form\Form') + ->setConstructorArgs(array($config)) + ->disableArgumentCloning() + ->setMethods(array('getViewData')) + ->getMock(); + } +} diff --git a/src/Symfony/Component/Form/Util/InheritDataAwareIterator.php b/src/Symfony/Component/Form/Util/InheritDataAwareIterator.php index 5c2c5fadcc..ba157b7d18 100644 --- a/src/Symfony/Component/Form/Util/InheritDataAwareIterator.php +++ b/src/Symfony/Component/Form/Util/InheritDataAwareIterator.php @@ -12,24 +12,17 @@ namespace Symfony\Component\Form\Util; /** - * Iterator that returns only forms from a form tree that do not inherit their - * parent data. + * Iterator that traverses an array of forms. * - * If the iterator encounters a form that inherits its parent data, it enters - * the form and traverses its children as well. + * Contrary to \ArrayIterator, this iterator recognizes changes in the original + * array during iteration. + * + * You can wrap the iterator into a {@link \RecursiveIterator} in order to + * enter any child form that inherits its parent's data and iterate the children + * of that form as well. * * @author Bernhard Schussek */ class InheritDataAwareIterator extends VirtualFormAwareIterator { - /** - * Creates a new iterator. - * - * @param \Symfony\Component\Form\FormInterface[] $forms An array - */ - public function __construct(array $forms) - { - // Skip the deprecation error - \ArrayIterator::__construct($forms); - } } diff --git a/src/Symfony/Component/Form/Util/VirtualFormAwareIterator.php b/src/Symfony/Component/Form/Util/VirtualFormAwareIterator.php index 24fdc8bb9f..ecf82492f5 100644 --- a/src/Symfony/Component/Form/Util/VirtualFormAwareIterator.php +++ b/src/Symfony/Component/Form/Util/VirtualFormAwareIterator.php @@ -12,39 +12,93 @@ namespace Symfony\Component\Form\Util; /** - * Iterator that returns only forms from a form tree that do not inherit their - * parent data. + * Iterator that traverses an array of forms. * - * If the iterator encounters a form that inherits its parent data, it enters - * the form and traverses its children as well. + * Contrary to \ArrayIterator, this iterator recognizes changes in the original + * array during iteration. + * + * You can wrap the iterator into a {@link \RecursiveIterator} in order to + * enter any child form that inherits its parent's data and iterate the children + * of that form as well. * * @author Bernhard Schussek * * @deprecated Deprecated since version 2.3, to be removed in 3.0. Use * {@link InheritDataAwareIterator} instead. */ -class VirtualFormAwareIterator extends \ArrayIterator implements \RecursiveIterator +class VirtualFormAwareIterator implements \RecursiveIterator { + /** + * @var \Symfony\Component\Form\FormInterface[] + */ + private $forms; + /** * Creates a new iterator. * - * @param \Symfony\Component\Form\FormInterface[] $forms An array + * @param \Symfony\Component\Form\FormInterface[] $forms An array of forms */ - public function __construct(array $forms) + public function __construct(array &$forms) { // Uncomment this as soon as the deprecation note should be shown // trigger_error('VirtualFormAwareIterator is deprecated since version 2.3 and will be removed in 3.0. Use InheritDataAwareIterator instead.', E_USER_DEPRECATED); - parent::__construct($forms); + $this->forms = &$forms; } + /** + *{@inheritdoc} + */ + public function current() + { + return current($this->forms); + } + + /** + *{@inheritdoc} + */ + public function next() + { + next($this->forms); + } + + /** + *{@inheritdoc} + */ + public function key() + { + return key($this->forms); + } + + /** + *{@inheritdoc} + */ + public function valid() + { + return null !== key($this->forms); + } + + /** + *{@inheritdoc} + */ + public function rewind() + { + reset($this->forms); + } + + /** + *{@inheritdoc} + */ public function getChildren() { return new static($this->current()->all()); } + /** + *{@inheritdoc} + */ public function hasChildren() { - return $this->current()->getConfig()->getInheritData(); + return (bool) $this->current()->getConfig()->getInheritData(); } }