8000 [Form] Fixed handling of expanded choice lists, checkboxes and radio … · symfony/symfony@2645120 · GitHub
[go: up one dir, main page]

Skip to content

Commit 2645120

Browse files
committed
[Form] Fixed handling of expanded choice lists, checkboxes and radio buttons with empty values ("")
1 parent 07e261e commit 2645120

File tree

11 files changed

+300
-147
lines changed

11 files changed

+300
-147
lines changed

UPGRADE-2.1.md

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -167,17 +167,6 @@ UPGRADE FROM 2.0 to 2.1
167167
colons and underscores, you can restore the old behavior by setting the
168168
`index_strategy` choice field option to `ChoiceList::COPY_CHOICE`.
169169
170-
* The strategy for generating the `value` HTML attribute for choices in a
171-
choice field has changed.
172-
173-
Instead of using the choice value, a generated integer is now stored. Again,
174-
take care if your JavaScript reads this value. If your choice field is a
175-
non-expanded single-choice field, or if the choices are guaranteed not to
176-
contain the empty string '' (which is the case when you added it manually
177-
or when the field is a single-choice field and is not required), you can
178-
restore the old behavior by setting the `value_strategy` choice field option
179-
to `ChoiceList::COPY_CHOICE`.
180-
181170
* In the choice field type's template, the structure of the `choices` variable
182171
has changed.
183172

src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -442,8 +442,8 @@ public function testSubmitSingleExpanded()
442442
$this->assertSame($entity2, $field->getData());
443443
$this->assertFalse($field['1']->getData());
444444
$this->assertTrue($field['2']->getData());
445-
$this->assertSame('', $field['1']->getClientData());
446-
$this->assertSame('1', $field['2']->getClientData());
445+
$this->assertNull($field['1']->getClientData());
446+
$this->assertSame('2', $field['2']->getClientData());
447447
}
448448

449449
public function testSubmitMultipleExpanded()
@@ -462,7 +462,7 @@ public function testSubmitMultipleExpanded()
462462
'property' => 'name',
463463
));
464464

465-
$field->bind(array('1' => '1', '3' => '3'));
465+
$field->bind(array('1', '3'));
466466

467467
$expected = new ArrayCollection(array($entity1, $entity3));
468468

@@ -472,8 +472,8 @@ public function testSubmitMultipleExpanded()
472472
$this->assertFalse($field['2']->getData());
473473
$this->assertTrue($field['3']->getData());
474474
$this->assertSame('1', $field['1']->getClientData());
475-
$this->assertSame('', $field['2']->getClientData());
476-
$this->assertSame('1', $field['3']->getClientData());
475+
$this->assertNull($field['2']->getClientData());
476+
$this->assertSame('3', $field['3']->getClientData());
477477
}
478478

479479
public function testOverrideChoices()

src/Symfony/Component/Form/Extension/Core/DataTransformer/BooleanToStringTransformer.php

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,22 @@
2222
*/
2323
class BooleanToStringTransformer implements DataTransformerInterface
2424
{
25+
/**
26+
* The value emitted upon transform if the input is true
27+
* @var string
28+
*/
29+
private $trueValue;
30+
31+
/**
32+
* Sets the value emitted upon transform if the input is true.
33+
*
34+
* @param string $trueValue
35+
*/
36+
public function __construct($trueValue)
37+
{
38+
$this->trueValue = $trueValue;
39+
}
40+
2541
/**
2642
* Transforms a Boolean into a string.
2743
*
@@ -34,14 +50,14 @@ class BooleanToStringTransformer implements DataTransformerInterface
3450
public function transform($value)
3551
{
3652
if (null === $value) {
37-
return '';
53+
return null;
3854
}
3955

4056
if (!is_bool($value)) {
4157
throw new UnexpectedTypeException($value, 'Boolean');
4258
}
4359

44-
return true === $value ? '1' : '';
60+
return true === $value ? $this->trueValue : null;
4561
}
4662

4763
/**
@@ -63,7 +79,7 @@ public function reverseTransform($value)
6379
throw new UnexpectedTypeException($value, 'string');
6480
}
6581

66-
return '' !== $value;
82+
return true;
6783
}
6884

6985
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Form\Extension\Core\EventListener;
13+
14+
use Symfony\Component\Form\FormEvents;
15+
use Symfony\Component\Form\Event\FilterDataEvent;
16+
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
17+
use Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface;
18+
19+
/**
20+
* Takes care of converting the input from a list of checkboxes to a correctly
21+
* indexed array.
22+
*
23+
* @author Bernhard Schussek <bernhard.schussek@symfony-project.com>
24+
*/
25+
class FixCheckboxInputListener implements EventSubscriberInterface
26+
{
27+
private $choiceList;
28+
29+
/**
30+
* Constructor.
31+
*
32+
* @param ChoiceListInterface $choiceList
33+
*/
34+
public function __construct(ChoiceListInterface $choiceList)
35+
{
36+
$this->choiceList = $choiceList;
37+
}
38+
39+
public function onBindClientData(FilterDataEvent $event)
40+
{
41+
$values = (array) $event->getData();
42+
$indices = $this->choiceList->getIndicesForValues($values);
43+
44+
$event->setData(array_combine($indices, $values));
45+
}
46+
47+
static public function getSubscribedEvents()
48+
{
49+
return array(FormEvents::BIND_CLIENT_DATA => 'onBindClientData');
50+
}
51+
}

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class CheckboxType extends AbstractType
2525
public function buildForm(FormBuilder $builder, array $options)
2626
{
2727
$builder
28-
->appendClientTransformer(new BooleanToStringTransformer())
28+
->appendClientTransformer(new BooleanToStringTransformer($options['value']))
2929
->setAttribute('value', $options['value 10670 '])
3030
;
3131
}
@@ -37,7 +37,7 @@ public function buildView(FormView $view, FormInterface $form)
3737
{
3838
$view
3939
->set('value', $form->getAttribute('value'))
40-
->set('checked', (Boolean) $form->getClientData())
40+
->set('checked', null !== $form->getClientData())
4141
;
4242
}
4343

@@ -48,6 +48,9 @@ public function getDefaultOptions(array $options)
4848
{
4949
return array(
5050
'value' => '1',
51+
'empty_data' => function (FormInterface $form, $clientData) {
52+
return $clientData;
53+
},
5154
);
5255
}
5356

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,14 @@
1414
use Symfony\Component\Form\AbstractType;
1515
use Symfony\Component\Form\FormBuilder;
1616
use Symfony\Component\Form\FormInterface;
17+
use Symfony\Component\Form\FormView;
1718
use Symfony\Component\Form\Exception\FormException;
1819
use Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceList;
1920
use Symfony\Component\Form\Extension\Core\ChoiceList\SimpleChoiceList;
2021
use Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface;
2122
use Symfony\Component\Form\Extension\Core\EventListener\FixRadioInputListener;
23+
use Symfony\Component\Form\Extension\Core\EventListener\FixCheckboxInputListener;
2224
use Symfony\Component\Form\Extension\Core\EventListener\MergeCollectionListener;
23-
use Symfony\Component\Form\FormView;
2425
use Symfony\Component\Form\Extension\Core\DataTransformer\ChoiceToValueTransformer;
2526
use Symfony\Component\Form\Extension\Core\DataTransformer\ChoiceToBooleanArrayTransformer;
2627
use Symfony\Component\Form\Extension\Core\DataTransformer\ChoicesToValuesTransformer;
@@ -81,7 +82,10 @@ public function buildForm(FormBuilder $builder, array $options)
8182

8283
if ($options['expanded']) {
8384
if ($options['multiple']) {
84-
$builder->appendClientTransformer(new ChoicesToBooleanArrayTransformer($options['choice_list']));
85+
$builder
86+
->appendClientTransformer(new ChoicesToBooleanArrayTransformer($options['choice_list']))
87+
->addEventSubscriber(new FixCheckboxInputListener($options['choice_list']), 10)
88+
;
8589
} else {
8690
$builder
8791
->appendClientTransformer(new ChoiceToBooleanArrayTransformer($options['choice_list']))
@@ -155,7 +159,7 @@ public function getDefaultOptions(array $options)
155159
'choice_list' => null,
156160
'choices' => null,
157161
'preferred_choices' => array(),
158-
'value_strategy' => ChoiceList::GENERATE,
162+
'value_strategy' => ChoiceList::COPY_CHOICE,
159163
'index_strategy' => ChoiceList::GENERATE,
160164
'empty_data' => $multiple || $expanded ? array() : '',
161165
'empty_value' => $multiple || $expanded || !isset($options['empty_value']) ? null : '',

src/Symfony/Component/Form/Form.php

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -470,7 +470,11 @@ public function bind($clientData)
470470
return $this;
471471
}
472472

473-
if (is_scalar($clientData) || null === $clientData) {
473+
// Don't convert NULL to a string here in order to determine later
474+
// whether an empty value has been submitted or whether no value has
475+
// been submitted at all. This is important for processing checkboxes
476+
// and radio buttons with empty values.
477+
if (is_scalar($clientData)) {
474478
$clientData = (string) $clientData;
475479
}
476480

@@ -522,11 +526,13 @@ public function bind($clientData)
522526
}
523527

524528
if (null === $clientData || '' === $clientData) {
525-
$clientData = $this->emptyData;
529+
$emptyData = $this->emptyData;
526530

527-
if ($clientData instanceof \Closure) {
528-
$clientData = $clientData($this);
531+
if ($emptyData instanceof \Closure) {
532+
$emptyData = $emptyData($this, $clientData);
529533
}
534+
535+
$clientData = $emptyData;
530536
}
531537

532538
// Merge form data from children into existing client data

0 commit comments

Comments
 (0)
0