[Form] Implemented MergeCollectionListener which calls addXxx() and removeXxx() in your model if found

The listener is used by the Collection type as well as the Choice and Entity type (with multiple
selection). The effect is that you can have for example this model:

    class Article
    {
        public function addTag($tag) { ... }
        public function removeTag($tag) { ... }
        public function getTags($tag) { ... }
    }

You can create a form for the article with a field "tags" of either type "collection" or "choice"
(or "entity"). The field will correctly use the three methods of the model for displaying and
editing tags.
This commit is contained in:
Bernhard Schussek 2012-02-01 18:52:43 +01:00
parent 7837f50c73
commit 49d1464b43
15 changed files with 653 additions and 44 deletions

View File

@ -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

View File

@ -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);
}
}

View File

@ -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())
;
}

View File

@ -0,0 +1,158 @@
<?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\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 <bschussek@gmail.com>
*/
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);
}
}

View File

@ -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),
);
}

View File

@ -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']));
}
}
}
/**

View File

@ -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'])
;

View File

@ -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'),

View File

@ -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());

View File

@ -0,0 +1,20 @@
<?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\Extension\Core\EventListener;
class MergeCollectionListenerArrayObjectTest extends MergeCollectionListenerTest
{
protected function getData(array $data)
{
return new \ArrayObject($data);
}
}

View File

@ -0,0 +1,20 @@
<?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\Extension\Core\EventListener;
class MergeCollectionListenerArrayTest extends MergeCollectionListenerTest
{
protected function getData(array $data)
{
return $data;
}
}

View File

@ -0,0 +1,82 @@
<?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\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);
}
}

View File

@ -0,0 +1,326 @@
<?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\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());
}
}
}

View File

@ -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()

View File

@ -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')),