Skip to content

Commit 8bdb7a0

Browse files
peterrehmfabpot
authored andcommitted
[Form] Added delete_empty option to allow proper emptyData handling of collections
1 parent efcca3e commit 8bdb7a0

File tree

5 files changed

+112
-4
lines changed

5 files changed

+112
-4
lines changed

src/Symfony/Component/Form/Extension/Core/EventListener/ResizeFormListener.php

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,18 @@ class ResizeFormListener implements EventSubscriberInterface
4545
*/
4646
protected $allowDelete;
4747

48-
public function __construct($type, array $options = array(), $allowAdd = false, $allowDelete = false)
48+
/**
49+
* @var bool
50+
*/
51+
private $deleteEmpty;
52+
53+
public function __construct($type, array $options = array(), $allowAdd = false, $allowDelete = false, $deleteEmpty = false)
4954
{
5055
$this->type = $type;
5156
$this->allowAdd = $allowAdd;
5257
$this->allowDelete = $allowDelete;
5358
$this->options = $options;
59+
$this->deleteEmpty = $deleteEmpty;
5460
}
5561

5662
public static function getSubscribedEvents()
@@ -126,8 +132,13 @@ public function preSubmit(FormEvent $event)
126132
public function onSubmit(FormEvent $event)
127133
{
128134
$form = $event->getForm();
135+
$previousData = $event->getForm()->getData();
129136
$data = $event->getData();
130137

138+
// At this point, $data is an array or an array-like object that already contains the
139+
// new entries, which were added by the data mapper. The data mapper ignores existing
140+
// entries, so we need to manually unset removed entries in the collection.
141+
131142
if (null === $data) {
132143
$data = array();
133144
}
@@ -136,10 +147,23 @@ public function onSubmit(FormEvent $event)
136147
throw new UnexpectedTypeException($data, 'array or (\Traversable and \ArrayAccess)');
137148
}
138149

150+
if ($this->deleteEmpty) {
151+
foreach ($form as $name => $child) {
152+
$isNew = !isset($previousData[$name]);
153+
154+
// $isNew can only be true if allowAdd is true, so we don't
155+
// need to check allowAdd again
156+
if ($child->isEmpty() && ($isNew || $this->allowDelete)) {
157+
unset($data[$name]);
158+
$form->remove($name);
159+
}
160+
}
161+
}
162+
139163
// The data mapper only adds, but does not remove items, so do this
140164
// here
141165
if ($this->allowDelete) {
142-
foreach ($data as $name => $child) {
166+
foreach ($data as $name => $childData) {
143167
if (!$form->has($name)) {
144168
unset($data[$name]);
145169
}

src/Symfony/Component/Form/Extension/Core/Type/CollectionType.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ public function buildForm(FormBuilderInterface $builder, array $options)
3737
$options['type'],
3838
$options['options'],
3939
$options['allow_add'],
40-
$options['allow_delete']
40+
$options['allow_delete'],
41+
$options['delete_empty']
4142
);
4243

4344
$builder->addEventSubscriber($resizeListener);
@@ -86,6 +87,7 @@ public function setDefaultOptions(OptionsResolverInterface $resolver)
8687
'prototype_name' => '__name__',
8788
'type' => 'text',
8889
'options' => array(),
90+
'delete_empty' => false,
8991
));
9092

9193
$resolver->setNormalizers(array(

src/Symfony/Component/Form/Form.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -899,7 +899,9 @@ public function remove($name)
899899
}
900900

901901
if (isset($this->children[$name])) {
902-
$this->children[$name]->setParent(null);
902+
if (!$this->children[$name]->isSubmitted()) {
903+
$this->children[$name]->setParent(null);
904+
}
903905

904906
unset($this->children[$name]);
905907
}

src/Symfony/Component/Form/Tests/Extension/Core/Type/CollectionTypeTest.php

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
namespace Symfony\Component\Form\Tests\Extension\Core\Type;
1313

1414
use Symfony\Component\Form\Form;
15+
use Symfony\Component\Form\Tests\Fixtures\Author;
16+
use Symfony\Component\Form\Tests\Fixtures\AuthorType;
1517

1618
class CollectionTypeTest extends \Symfony\Component\Form\Test\TypeTestCase
1719
{
@@ -88,6 +90,78 @@ public function testResizedDownIfSubmittedWithMissingDataAndAllowDelete()
8890
$this->assertEquals(array('foo@foo.com'), $form->getData());
8991
}
9092

93+
public function testResizedDownIfSubmittedWithEmptyDataAndDeleteEmpty()
94+
{
95+
$form = $this->factory->create('collection', null, array(
96+
'type' => 'text',
97+
'allow_delete' => true,
98+
'delete_empty' => true,
99+
));
100+
101+
$form->setData(array('foo@foo.com', 'bar@bar.com'));
102+
$form->submit(array('foo@foo.com', ''));
103+
104+
$this->assertTrue($form->has('0'));
105+
$this->assertFalse($form->has('1'));
106+
$this->assertEquals('foo@foo.com', $form[0]->getData());
107+
$this->assertEquals(array('foo@foo.com'), $form->getData());
108+
}
109+
110+
public function testDontAddEmptyDataIfDeleteEmpty()
111+
{
112+
$form = $this->factory->create('collection', null, array(
113+
'type' => 'text',
114+
'allow_add' => true,
115+
'delete_empty' => true,
116+
));
117+
118+
$form->setData(array('foo@foo.com'));
119+
$form->submit(array('foo@foo.com', ''));
120+
121+
$this->assertTrue($form->has('0'));
122+
$this->assertFalse($form->has('1'));
123+
$this->assertEquals('foo@foo.com', $form[0]->getData());
124+
$this->assertEquals(array('foo@foo.com'), $form->getData());
125+
}
126+
127+
public function testNoDeleteEmptyIfDeleteNotAllowed()
128+
{
129+
$form = $this->factory->create('collection', null, array(
130+
'type' => 'text',
131+
'allow_delete' => false,
132+
'delete_empty' => true,
133+
));
134+
135+
$form->setData(array('foo@foo.com'));
136+
$form->submit(array(''));
137+
138+
$this->assertTrue($form->has('0'));
139+
$this->assertEquals('', $form[0]->getData());
140+
}
141+
142+
public function testResizedDownIfSubmittedWithCompoundEmptyDataAndDeleteEmpty()
143+
{
144+
$form = $this->factory->create('collection', null, array(
145+
'type' => new AuthorType(),
146+
// If the field is not required, no new Author will be created if the
147+
// form is completely empty
148+
'options' => array('required' => false),
149+
'allow_add' => true,
150+
'delete_empty' => true,
151+
));
152+
153+
$form->setData(array(new Author('first', 'last')));
154+
$form->submit(array(
155+
array('firstName' => 's_first', 'lastName' => 's_last'),
156+
array('firstName' => '', 'lastName' => ''),
157+
));
158+
159+
$this->assertTrue($form->has('0'));
160+
$this->assertFalse($form->has('1'));
161+
$this->assertEquals(new Author('s_first', 's_last'), $form[0]->getData());
162+
$this->assertEquals(array(new Author('s_first', 's_last')), $form->getData());
163+
}
164+
91165
public function testNotResizedIfSubmittedWithExtraData()
92166
{
93167
$form = $this->factory->create('collection', null, array(

src/Symfony/Component/Form/Tests/Fixtures/Author.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ class Author
2121

2222
private $privateProperty;
2323

24+
public function __construct($firstName = null, $lastName = null)
25+
{
26+
$this->firstName = $firstName;
27+
$this->lastName = $lastName;
28+
}
29+
2430
public function setLastName($lastName)
2531
{
2632
$this->lastName = $lastName;

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy