8000 feature #9773 [Form] Added delete_empty option to allow proper emptyD… · symfony/form@f06f3e9 · GitHub
[go: up one dir, main page]

Skip to content

Commit f06f3e9

Browse files
committed
feature #9773 [Form] Added delete_empty option to allow proper emptyData handling of collections (peterrehm)
This PR was squashed before being merged into the 2.5-dev branch (closes #9773). Discussion ---------- [Form] Added delete_empty option to allow proper emptyData handling of collections | Q | A | ------------- | --- | Bug fix? | yes | New feature? | yes/no? | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #9375 | License | MIT | Doc PR | symfony/symfony-docs#3338 This PR adresses the issue that if you have a form collection with the option required => false and you submit an empty collection so you will get a ArrayCollection that contains en element with the value null. This behaviour was introduced with the following changes from symfony/symfony#3257 In addition to the above mentioned ticket there is also a description about the same issue here: http://stackoverflow.com/questions/19474872/symfony2-form-collection-allow-add-and-allow-delete-null-error-silex With the changes of this PR the new option empty_data is introduced. With this option you will be able to ignore/delete such empty collections, so they will neither be validated nor appear as empty field in the result. The option will remove/ignore such empty collections if you add them newly and if allow_add is enabled and removes such empty collections only if allow_delete is enabled. With setting required and empty_data accordingly it will be now flexible to customize to the outcome you want to achieve. Thanks to @bschussek for the great work together - We have to discuss how to name this option so if delete or ignore is the appropriate name. Commits ------- 8bdb7a0 [Form] Added delete_empty option to allow proper emptyData handling of collections
2 parents 2f44b34 + f3f50d4 commit f06f3e9

File tree

5 files changed

+112
-4
lines changed

5 files changed

+112
-4
lines changed

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
}

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(

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
}

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(

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)
0