diff --git a/CHANGELOG-2.1.md b/CHANGELOG-2.1.md index 237c87e881..98f8eed04c 100644 --- a/CHANGELOG-2.1.md +++ b/CHANGELOG-2.1.md @@ -195,6 +195,9 @@ To get the diff between two versions, go to https://github.com/symfony/symfony/c * choice fields now throw a FormException if neither the "choices" nor the "choice_list" option is set * the radio type is now a child of the checkbox type + * the Collection, Choice (with multiple selection) and Entity (with multiple + selection) types now make use of addXxx() and removeXxx() methods in your + model ### HttpFoundation diff --git a/src/Symfony/Bridge/Doctrine/Form/EventListener/MergeCollectionListener.php b/src/Symfony/Bridge/Doctrine/Form/EventListener/MergeDoctrineCollectionListener.php similarity index 59% rename from src/Symfony/Bridge/Doctrine/Form/EventListener/MergeCollectionListener.php rename to src/Symfony/Bridge/Doctrine/Form/EventListener/MergeDoctrineCollectionListener.php index 39c3b11f21..d3e567d69a 100644 --- a/src/Symfony/Bridge/Doctrine/Form/EventListener/MergeCollectionListener.php +++ b/src/Symfony/Bridge/Doctrine/Form/EventListener/MergeDoctrineCollectionListener.php @@ -24,11 +24,13 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface; * * @see Doctrine\Common\Collections\Collection */ -class MergeCollectionListener implements EventSubscriberInterface +class MergeDoctrineCollectionListener implements EventSubscriberInterface { static public function getSubscribedEvents() { - return array(FormEvents::BIND_NORM_DATA => 'onBindNormData'); + // Higher priority than core MergeCollectionListener so that this one + // is called before + return array(FormEvents::BIND_NORM_DATA => array('onBindNormData', 10)); } public function onBindNormData(FilterDataEvent $event) @@ -36,25 +38,10 @@ class MergeCollectionListener implements EventSubscriberInterface $collection = $event->getForm()->getData(); $data = $event->getData(); - if (!$collection) { - $collection = $data; - } elseif (count($data) === 0) { + // If all items were removed, call clear which has a higher + // performance on persistent collections + if ($collection && count($data) === 0) { $collection->clear(); - } else { - // merge $data into $collection - foreach ($collection as $entity) { - if (!$data->contains($entity)) { - $collection->removeElement($entity); - } else { - $data->removeElement($entity); - } - } - - foreach ($data as $entity) { - $collection->add($entity); - } } - - $event->setData($collection); } } diff --git a/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php b/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php index 0937c1c001..1c634fb5b0 100644 --- a/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php +++ b/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php @@ -16,7 +16,7 @@ use Doctrine\Common\Persistence\ObjectManager; use Symfony\Component\Form\FormBuilder; use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityChoiceList; use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityLoaderInterface; -use Symfony\Bridge\Doctrine\Form\EventListener\MergeCollectionListener; +use Symfony\Bridge\Doctrine\Form\EventListener\MergeDoctrineCollectionListener; use Symfony\Bridge\Doctrine\Form\DataTransformer\CollectionToArrayTransformer; use Symfony\Component\Form\AbstractType; @@ -36,7 +36,7 @@ abstract class DoctrineType extends AbstractType { if ($options['multiple']) { $builder - ->addEventSubscriber(new MergeCollectionListener()) + ->addEventSubscriber(new MergeDoctrineCollectionListener()) ->prependClientTransformer(new CollectionToArrayTransformer()) ; } diff --git a/src/Symfony/Component/Form/Extension/Core/EventListener/MergeCollectionListener.php b/src/Symfony/Component/Form/Extension/Core/EventListener/MergeCollectionListener.php new file mode 100644 index 0000000000..7bfab6888f --- /dev/null +++ b/src/Symfony/Component/Form/Extension/Core/EventListener/MergeCollectionListener.php @@ -0,0 +1,158 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\EventListener; + +use Symfony\Component\Form\Util\FormUtil; + +use Symfony\Component\Form\FormEvents; +use Symfony\Component\Form\Event\FilterDataEvent; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Form\Exception\UnexpectedTypeException; + +/** + * @author Bernhard Schussek + */ +class MergeCollectionListener implements EventSubscriberInterface +{ + /** + * Whether elements may be added to the collection + * @var Boolean + */ + private $allowAdd; + + /** + * Whether elements may be removed from the collection + * @var Boolean + */ + private $allowDelete; + + public function __construct($allowAdd = false, $allowDelete = false) + { + $this->allowAdd = $allowAdd; + $this->allowDelete = $allowDelete; + } + + static public function getSubscribedEvents() + { + return array(FormEvents::BIND_NORM_DATA => 'onBindNormData'); + } + + public function onBindNormData(FilterDataEvent $event) + { + $originalData = $event->getForm()->getData(); + $form = $event->getForm(); + $data = $event->getData(); + $parentData = $form->hasParent() ? $form->getParent()->getData() : null; + $adder = null; + $remover = null; + + if (null === $data) { + $data = array(); + } + + if (!is_array($data) && !($data instanceof \Traversable && $data instanceof \ArrayAccess)) { + throw new UnexpectedTypeException($data, 'array or (\Traversable and \ArrayAccess)'); + } + + if (null !== $originalData && !is_array($originalData) && !($originalData instanceof \Traversable && $originalData instanceof \ArrayAccess)) { + throw new UnexpectedTypeException($originalData, 'array or (\Traversable and \ArrayAccess)'); + } + + // Check if the parent has matching methods to add/remove items + if (is_object($parentData)) { + $plural = ucfirst($form->getName()); + $singulars = (array) FormUtil::singularify($plural); + $reflClass = new \ReflectionClass($parentData); + + foreach ($singulars as $singular) { + $adderName = 'add' . $singular; + $removerName = 'remove' . $singular; + + if ($reflClass->hasMethod($adderName) && $reflClass->hasMethod($removerName)) { + $adder = $reflClass->getMethod($adderName); + $remover = $reflClass->getMethod($removerName); + + if ($adder->isPublic() && $adder->getNumberOfRequiredParameters() === 1 + && $remover->isPublic() && $remover->getNumberOfRequiredParameters() === 1) { + + // We found a public, one-parameter add and remove method + break; + } + + // False alert + $adder = null; + $remover = null; + } + } + } + + // Check which items are in $data that are not in $originalData and + // vice versa + $itemsToDelete = array(); + $itemsToAdd = is_object($data) ? clone $data : $data; + + if ($originalData) { + foreach ($originalData as $originalKey => $originalItem) { + foreach ($data as $key => $item) { + if ($item === $originalItem) { + // Item found, next original item + unset($itemsToAdd[$key]); + continue 2; + } + } + + // Item not found, remember for deletion + $itemsToDelete[$originalKey] = $originalItem; + } + } + + if ($adder && $remover) { + // If methods to add and to remove exist, call them now, if allowed + if ($this->allowDelete) { + foreach ($itemsToDelete as $item) { + $remover->invoke($parentData, $item); + } + } + + if ($this->allowAdd) { + foreach ($itemsToAdd as $item) { + $adder->invoke($parentData, $item); + } + } + } elseif (!$originalData) { + // No original data was set. Set it if allowed + if ($this->allowAdd) { + $originalData = $data; + } + } else { + // Original data is an array-like structure + // Add and remove items in the original variable + if ($this->allowDelete) { + foreach ($itemsToDelete as $key => $item) { + unset($originalData[$key]); + } + } + + if ($this->allowAdd) { + foreach ($itemsToAdd as $key => $item) { + if (!isset($originalData[$key])) { + $originalData[$key] = $item; + } else { + $originalData[] = $item; + } + } + } + } + + $event->setData($originalData); + } +} diff --git a/src/Symfony/Component/Form/Extension/Core/EventListener/ResizeFormListener.php b/src/Symfony/Component/Form/Extension/Core/EventListener/ResizeFormListener.php index 30159bf637..5ac3994ff9 100644 --- a/src/Symfony/Component/Form/Extension/Core/EventListener/ResizeFormListener.php +++ b/src/Symfony/Component/Form/Extension/Core/EventListener/ResizeFormListener.php @@ -66,7 +66,8 @@ class ResizeFormListener implements EventSubscriberInterface return array( FormEvents::PRE_SET_DATA => 'preSetData', FormEvents::PRE_BIND => 'preBind', - FormEvents::BIND_NORM_DATA => 'onBindNormData', + // (MergeCollectionListener, MergeDoctrineCollectionListener) + FormEvents::BIND_NORM_DATA => array('onBindNormData', 50), ); } diff --git a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php index 088043eacb..e629d37747 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php @@ -19,6 +19,7 @@ use Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceList; use Symfony\Component\Form\Extension\Core\ChoiceList\SimpleChoiceList; use Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface; use Symfony\Component\Form\Extension\Core\EventListener\FixRadioInputListener; +use Symfony\Component\Form\Extension\Core\EventListener\MergeCollectionListener; use Symfony\Component\Form\FormView; use Symfony\Component\Form\Extension\Core\DataTransformer\ChoiceToValueTransformer; use Symfony\Component\Form\Extension\Core\DataTransformer\ChoiceToBooleanArrayTransformer; @@ -80,7 +81,10 @@ class ChoiceType extends AbstractType if ($options['expanded']) { if ($options['multiple']) { - $builder->appendClientTransformer(new ChoicesToBooleanArrayTransformer($options['choice_list'])); + $builder + ->appendClientTransformer(new ChoicesToBooleanArrayTransformer($options['choice_list'])) + ->addEventSubscriber(new MergeCollectionListener(true, true)) + ; } else { $builder ->appendClientTransformer(new ChoiceToBooleanArrayTransformer($options['choice_list'])) @@ -89,12 +93,14 @@ class ChoiceType extends AbstractType } } else { if ($options['multiple']) { - $builder->appendClientTransformer(new ChoicesToValuesTransformer($options['choice_list'])); + $builder + ->appendClientTransformer(new ChoicesToValuesTransformer($options['choice_list'])) + ->addEventSubscriber(new MergeCollectionListener(true, true)) + ; } else { $builder->appendClientTransformer(new ChoiceToValueTransformer($options['choice_list'])); } } - } /** diff --git a/src/Symfony/Component/Form/Extension/Core/Type/CollectionType.php b/src/Symfony/Component/Form/Extension/Core/Type/CollectionType.php index 20ac8345f1..748370a007 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/CollectionType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/CollectionType.php @@ -16,6 +16,7 @@ use Symfony\Component\Form\FormBuilder; use Symfony\Component\Form\FormView; use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\Extension\Core\EventListener\ResizeFormListener; +use Symfony\Component\Form\Extension\Core\EventListener\MergeCollectionListener; class CollectionType extends AbstractType { @@ -29,7 +30,7 @@ class CollectionType extends AbstractType $builder->setAttribute('prototype', $prototype->getForm()); } - $listener = new ResizeFormListener( + $resizeListener = new ResizeFormListener( $builder->getFormFactory(), $options['type'], $options['options'], @@ -37,8 +38,14 @@ class CollectionType extends AbstractType $options['allow_delete'] ); + $mergeListener = new MergeCollectionListener( + $options['allow_add'], + $options['allow_delete'] + ); + $builder - ->addEventSubscriber($listener) + ->addEventSubscriber($resizeListener) + ->addEventSubscriber($mergeListener) ->setAttribute('allow_add', $options['allow_add']) ->setAttribute('allow_delete', $options['allow_delete']) ; diff --git a/src/Symfony/Component/Form/Util/FormUtil.php b/src/Symfony/Component/Form/Util/FormUtil.php index 284f02e1e7..9596796add 100644 --- a/src/Symfony/Component/Form/Util/FormUtil.php +++ b/src/Symfony/Component/Form/Util/FormUtil.php @@ -83,8 +83,8 @@ abstract class FormUtil // hooves (hoof), dwarves (dwarf), elves (elf), leaves (leaf) array('sev', 3, true, true, 'f'), - // axes (axis) - array('sexa', 4, false, false, 'axis'), + // axes (axis), axes (ax), axes (axe) + array('sexa', 4, false, false, array('ax', 'axe', 'axis')), // indexes (index), matrixes (matrix) array('sex', 3, true, false, 'x'), diff --git a/tests/Symfony/Tests/Bridge/Doctrine/Form/Type/EntityTypeTest.php b/tests/Symfony/Tests/Bridge/Doctrine/Form/Type/EntityTypeTest.php index 8c4f65abb1..20d759d579 100644 --- a/tests/Symfony/Tests/Bridge/Doctrine/Form/Type/EntityTypeTest.php +++ b/tests/Symfony/Tests/Bridge/Doctrine/Form/Type/EntityTypeTest.php @@ -344,13 +344,13 @@ class EntityTypeTest extends TypeTestCase 'property' => 'name', )); - $existing = new ArrayCollection(array($entity2)); + $existing = new ArrayCollection(array(0 => $entity2)); $field->setData($existing); $field->bind(array('1', '3')); - // entry with index 0 was removed - $expected = new ArrayCollection(array(1 => $entity1, 2 => $entity3)); + // entry with index 0 ($entity2) was replaced + $expected = new ArrayCollection(array(0 => $entity1, 1 => $entity3)); $this->assertTrue($field->isSynchronized()); $this->assertEquals($expected, $field->getData()); @@ -406,8 +406,8 @@ class EntityTypeTest extends TypeTestCase $field->setData($existing); $field->bind(array('0', '2')); - // entry with index 0 was removed - $expected = new ArrayCollection(array(1 => $entity1, 2 => $entity3)); + // entry with index 0 ($entity2) was replaced + $expected = new ArrayCollection(array(0 => $entity1, 1 => $entity3)); $this->assertTrue($field->isSynchronized()); $this->assertEquals($expected, $field->getData()); diff --git a/tests/Symfony/Tests/Component/Form/Extension/Core/EventListener/MergeCollectionListenerArrayObjectTest.php b/tests/Symfony/Tests/Component/Form/Extension/Core/EventListener/MergeCollectionListenerArrayObjectTest.php new file mode 100644 index 0000000000..96cece7532 --- /dev/null +++ b/tests/Symfony/Tests/Component/Form/Extension/Core/EventListener/MergeCollectionListenerArrayObjectTest.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Tests\Component\Form\Extension\Core\EventListener; + +class MergeCollectionListenerArrayObjectTest extends MergeCollectionListenerTest +{ + protected function getData(array $data) + { + return new \ArrayObject($data); + } +} diff --git a/tests/Symfony/Tests/Component/Form/Extension/Core/EventListener/MergeCollectionListenerArrayTest.php b/tests/Symfony/Tests/Component/Form/Extension/Core/EventListener/MergeCollectionListenerArrayTest.php new file mode 100644 index 0000000000..c150658bb4 --- /dev/null +++ b/tests/Symfony/Tests/Component/Form/Extension/Core/EventListener/MergeCollectionListenerArrayTest.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Tests\Component\Form\Extension\Core\EventListener; + +class MergeCollectionListenerArrayTest extends MergeCollectionListenerTest +{ + protected function getData(array $data) + { + return $data; + } +} diff --git a/tests/Symfony/Tests/Component/Form/Extension/Core/EventListener/MergeCollectionListenerCustomArrayObjectTest.php b/tests/Symfony/Tests/Component/Form/Extension/Core/EventListener/MergeCollectionListenerCustomArrayObjectTest.php new file mode 100644 index 0000000000..59dec2aa0b --- /dev/null +++ b/tests/Symfony/Tests/Component/Form/Extension/Core/EventListener/MergeCollectionListenerCustomArrayObjectTest.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Tests\Component\Form\Extension\Core\EventListener; + +/** +* This class is a hand written simplified version of PHP native `ArrayObject` +* class, to show that it behaves differently than the PHP native implementation. +*/ +class MergeCollectionListenerCustomArrayObjectTest_CustomArrayObject implements \ArrayAccess, \IteratorAggregate, \Countable, \Serializable +{ + private $array; + + public function __construct(array $array = null) + { + $this->array = (array) ($array ?: array()); + } + + public function offsetExists($offset) + { + return array_key_exists($offset, $this->array); + } + + public function offsetGet($offset) + { + return $this->array[$offset]; + } + + public function offsetSet($offset, $value) + { + if (null === $offset) { + $this->array[] = $value; + } else { + $this->array[$offset] = $value; + } + } + + public function offsetUnset($offset) + { + if (array_key_exists($offset, $this->array)) { + unset($this->array[$offset]); + } + } + + public function getIterator() + { + return new \ArrayIterator($this->array); + } + + public function count() + { + return count($this->array); + } + + public function serialize() + { + return serialize($this->array); + } + + public function unserialize($serialized) + { + $this->array = (array) unserialize((string) $serialized); + } +} + +class MergeCollectionListenerCustomArrayObjectTest extends MergeCollectionListenerTest +{ + protected function getData(array $data) + { + $class = __CLASS__ . '_CustomArrayObject'; + + return new $class($data); + } +} diff --git a/tests/Symfony/Tests/Component/Form/Extension/Core/EventListener/MergeCollectionListenerTest.php b/tests/Symfony/Tests/Component/Form/Extension/Core/EventListener/MergeCollectionListenerTest.php new file mode 100644 index 0000000000..4f6463ca84 --- /dev/null +++ b/tests/Symfony/Tests/Component/Form/Extension/Core/EventListener/MergeCollectionListenerTest.php @@ -0,0 +1,326 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Tests\Component\Form\Extension\Core\EventListener; + +use Symfony\Component\Form\Event\DataEvent; +use Symfony\Component\Form\Event\FilterDataEvent; +use Symfony\Component\Form\Extension\Core\EventListener\MergeCollectionListener; +use Symfony\Component\Form\FormBuilder; + +class MergeCollectionListenerTest_Car +{ + // In the test, use a name that FormUtil can't uniquely singularify + public function addAxis($axis) {} + + public function removeAxis($axis) {} +} + +abstract class MergeCollectionListenerTest extends \PHPUnit_Framework_TestCase +{ + private $dispatcher; + private $factory; + private $form; + + public function setUp() + { + $this->dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface'); + $this->factory = $this->getMock('Symfony\Component\Form\FormFactoryInterface'); + $this->form = $this->getForm('axes'); + } + + protected function tearDown() + { + $this->dispatcher = null; + $this->factory = null; + $this->form = null; + } + + protected function getBuilder($name = 'name') + { + return new FormBuilder($name, $this->factory, $this->dispatcher); + } + + protected function getForm($name = 'name') + { + return $this->getBuilder($name)->getForm(); + } + + protected function getMockForm() + { + return $this->getMock('Symfony\Tests\Component\Form\FormInterface'); + } + + abstract protected function getData(array $data); + + public function testAddExtraEntriesIfAllowAdd() + { + $originalData = $this->getData(array(1 => 'second')); + $newData = $this->getData(array(0 => 'first', 1 => 'second', 2 => 'third')); + + $this->form->setData($originalData); + + $event = new FilterDataEvent($this->form, $newData); + $listener = new MergeCollectionListener(true, false); + $listener->onBindNormData($event); + + // The original object was modified + if (is_object($originalData)) { + $this->assertSame($originalData, $event->getData()); + } + + // The original object matches the new object + $this->assertEquals($newData, $event->getData()); + } + + public function testAddExtraEntriesIfAllowAddDontOverwriteExistingIndices() + { + $originalData = $this->getData(array(1 => 'first')); + $newData = $this->getData(array(0 => 'first', 1 => 'second')); + + $this->form->setData($originalData); + + $event = new FilterDataEvent($this->form, $newData); + $listener = new MergeCollectionListener(true, false); + $listener->onBindNormData($event); + + // The original object was modified + if (is_object($originalData)) { + $this->assertSame($originalData, $event->getData()); + } + + // The original object matches the new object + $this->assertEquals($this->getData(array(1 => 'first', 2 => 'second')), $event->getData()); + } + + public function testDoNothingIfNotAllowAdd() + { + $originalDataArray = array(1 => 'second'); + $originalData = $this->getData($originalDataArray); + $newData = $this->getData(array(0 => 'first', 1 => 'second', 2 => 'third')); + + $this->form->setData($originalData); + + $event = new FilterDataEvent($this->form, $newData); + $listener = new MergeCollectionListener(false, false); + $listener->onBindNormData($event); + + // We still have the original object + if (is_object($originalData)) { + $this->assertSame($originalData, $event->getData()); + } + + // Nothing was removed + $this->assertEquals($this->getData($originalDataArray), $event->getData()); + } + + public function testRemoveMissingEntriesIfAllowDelete() + { + $originalData = $this->getData(array(0 => 'first', 1 => 'second', 2 => 'third')); + $newData = $this->getData(array(1 => 'second')); + + $this->form->setData($originalData); + + $event = new FilterDataEvent($this->form, $newData); + $listener = new MergeCollectionListener(false, true); + $listener->onBindNormData($event); + + // The original object was modified + if (is_object($originalData)) { + $this->assertSame($originalData, $event->getData()); + } + + // The original object matches the new object + $this->assertEquals($newData, $event->getData()); + } + + public function testDoNothingIfNotAllowDelete() + { + $originalDataArray = array(0 => 'first', 1 => 'second', 2 => 'third'); + $originalData = $this->getData($originalDataArray); + $newData = $this->getData(array(1 => 'second')); + + $this->form->setData($originalData); + + $event = new FilterDataEvent($this->form, $newData); + $listener = new MergeCollectionListener(false, false); + $listener->onBindNormData($event); + + // We still have the original object + if (is_object($originalData)) { + $this->assertSame($originalData, $event->getData()); + } + + // Nothing was removed + $this->assertEquals($this->getData($originalDataArray), $event->getData()); + } + + /** + * @expectedException Symfony\Component\Form\Exception\UnexpectedTypeException + */ + public function testRequireArrayOrTraversable() + { + $newData = 'no array or traversable'; + $event = new FilterDataEvent($this->form, $newData); + $listener = new MergeCollectionListener(false, false); + $listener->onBindNormData($event); + } + + public function testDealWithNullData() + { + $originalData = $this->getData(array(0 => 'first', 1 => 'second', 2 => 'third')); + $newData = null; + + $this->form->setData($originalData); + + $event = new FilterDataEvent($this->form, $newData); + $listener = new MergeCollectionListener(false, false); + $listener->onBindNormData($event); + + $this->assertSame($originalData, $event->getData()); + } + + public function testDealWithNullOriginalDataIfAllowAdd() + { + $originalData = null; + $newData = $this->getData(array(0 => 'first', 1 => 'second', 2 => 'third')); + + $this->form->setData($originalData); + + $event = new FilterDataEvent($this->form, $newData); + $listener = new MergeCollectionListener(true, false); + $listener->onBindNormData($event); + + $this->assertSame($newData, $event->getData()); + } + + public function testDontDealWithNullOriginalDataIfNotAllowAdd() + { + $originalData = null; + $newData = $this->getData(array(0 => 'first', 1 => 'second', 2 => 'third')); + + $this->form->setData($originalData); + + $event = new FilterDataEvent($this->form, $newData); + $listener = new MergeCollectionListener(false, false); + $listener->onBindNormData($event); + + $this->assertNull($event->getData()); + } + + public function testCallAdderIfAllowAdd() + { + $parentData = $this->getMock(__CLASS__ . '_Car'); + $parentForm = $this->getForm('article'); + $parentForm->setData($parentData); + $parentForm->add($this->form); + + $originalData = $this->getData(array(1 => 'second')); + $newData = $this->getData(array(0 => 'first', 1 => 'second', 2 => 'third')); + + $this->form->setData($originalData); + + $parentData->expects($this->at(0)) + ->method('addAxis') + ->with('first'); + $parentData->expects($this->at(1)) + ->method('addAxis') + ->with('third'); + + $event = new FilterDataEvent($this->form, $newData); + $listener = new MergeCollectionListener(true, false); + $listener->onBindNormData($event); + + // The original object was modified + if (is_object($originalData)) { + $this->assertSame($originalData, $event->getData()); + } + } + + public function testDontCallAdderIfNotAllowAdd() + { + $parentData = $this->getMock(__CLASS__ . '_Car'); + $parentForm = $this->getForm('article'); + $parentForm->setData($parentData); + $parentForm->add($this->form); + + $originalData = $this->getData(array(1 => 'second')); + $newData = $this->getData(array(0 => 'first', 1 => 'second', 2 => 'third')); + + $this->form->setData($originalData); + + $parentData->expects($this->never()) + ->method('addAxis'); + + $event = new FilterDataEvent($this->form, $newData); + $listener = new MergeCollectionListener(false, false); + $listener->onBindNormData($event); + + // The original object was modified + if (is_object($originalData)) { + $this->assertSame($originalData, $event->getData()); + } + } + + public function testCallRemoverIfAllowDelete() + { + $parentData = $this->getMock(__CLASS__ . '_Car'); + $parentForm = $this->getForm('article'); + $parentForm->setData($parentData); + $parentForm->add($this->form); + + $originalData = $this->getData(array(0 => 'first', 1 => 'second', 2 => 'third')); + $newData = $this->getData(array(1 => 'second')); + + $this->form->setData($originalData); + + $parentData->expects($this->at(0)) + ->method('removeAxis') + ->with('first'); + $parentData->expects($this->at(1)) + ->method('removeAxis') + ->with('third'); + + $event = new FilterDataEvent($this->form, $newData); + $listener = new MergeCollectionListener(false, true); + $listener->onBindNormData($event); + + // The original object was modified + if (is_object($originalData)) { + $this->assertSame($originalData, $event->getData()); + } + } + + public function testDontCallRemoverIfNotAllowDelete() + { + $parentData = $this->getMock(__CLASS__ . '_Car'); + $parentForm = $this->getForm('article'); + $parentForm->setData($parentData); + $parentForm->add($this->form); + + $originalData = $this->getData(array(0 => 'first', 1 => 'second', 2 => 'third')); + $newData = $this->getData(array(1 => 'second')); + + $this->form->setData($originalData); + + $parentData->expects($this->never()) + ->method('removeAxis'); + + $event = new FilterDataEvent($this->form, $newData); + $listener = new MergeCollectionListener(false, false); + $listener->onBindNormData($event); + + // The original object was modified + if (is_object($originalData)) { + $this->assertSame($originalData, $event->getData()); + } + } +} diff --git a/tests/Symfony/Tests/Component/Form/Extension/Core/Type/CollectionTypeTest.php b/tests/Symfony/Tests/Component/Form/Extension/Core/Type/CollectionTypeTest.php index 615a1d4fdc..5a082f4701 100644 --- a/tests/Symfony/Tests/Component/Form/Extension/Core/Type/CollectionTypeTest.php +++ b/tests/Symfony/Tests/Component/Form/Extension/Core/Type/CollectionTypeTest.php @@ -13,7 +13,7 @@ namespace Symfony\Tests\Component\Form\Extension\Core\Type; use Symfony\Component\Form\Form; -class CollectionFormTest extends TypeTestCase +class CollectionTypeTest extends TypeTestCase { public function testContainsNoFieldByDefault() { @@ -80,12 +80,12 @@ class CollectionFormTest extends TypeTestCase 'allow_delete' => true, )); $form->setData(array('foo@foo.com', 'bar@bar.com')); - $form->bind(array('foo@bar.com')); + $form->bind(array('foo@foo.com')); $this->assertTrue($form->has('0')); $this->assertFalse($form->has('1')); - $this->assertEquals('foo@bar.com', $form[0]->getData()); - $this->assertEquals(array('foo@bar.com'), $form->getData()); + $this->assertEquals('foo@foo.com', $form[0]->getData()); + $this->assertEquals(array('foo@foo.com'), $form->getData()); } public function testNotResizedIfBoundWithExtraData() @@ -108,13 +108,13 @@ class CollectionFormTest extends TypeTestCase 'allow_add' => true, )); $form->setData(array('foo@bar.com')); - $form->bind(array('foo@foo.com', 'bar@bar.com')); + $form->bind(array('foo@bar.com', 'bar@bar.com')); $this->assertTrue($form->has('0')); $this->assertTrue($form->has('1')); - $this->assertEquals('foo@foo.com', $form[0]->getData()); + $this->assertEquals('foo@bar.com', $form[0]->getData()); $this->assertEquals('bar@bar.com', $form[1]->getData()); - $this->assertEquals(array('foo@foo.com', 'bar@bar.com'), $form->getData()); + $this->assertEquals(array('foo@bar.com', 'bar@bar.com'), $form->getData()); } public function testAllowAddButNoPrototype() diff --git a/tests/Symfony/Tests/Component/Form/Util/FormUtilTest.php b/tests/Symfony/Tests/Component/Form/Util/FormUtilTest.php index 5fc1d0736c..bacba206be 100644 --- a/tests/Symfony/Tests/Component/Form/Util/FormUtilTest.php +++ b/tests/Symfony/Tests/Component/Form/Util/FormUtilTest.php @@ -87,7 +87,7 @@ class FormUtilTest extends \PHPUnit_Framework_TestCase array('alumni', 'alumnus'), array('funguses', array('fungus', 'funguse', 'fungusis')), array('fungi', 'fungus'), - array('axes', 'axis'), + array('axes', array('ax', 'axe', 'axis')), array('appendices', array('appendex', 'appendix')), array('indices', array('index', 'indix')), array('indexes', 'index'), @@ -123,7 +123,6 @@ class FormUtilTest extends \PHPUnit_Framework_TestCase array('houses', array('hous', 'house', 'housis')), array('arches', array('arch', 'arche')), array('atlases', array('atlas', 'atlase', 'atlasis')), -// array('axes', 'axe'), array('batches', array('batch', 'batche')), array('bushes', array('bush', 'bushe')), array('buses', array('bus', 'buse', 'busis')),