[Form] Fixed expanded choice field to be marked invalid when unknown choices are submitted

This commit is contained in:
Bernhard Schussek 2013-05-06 00:28:29 +02:00
parent 79a214fa75
commit 6283b0e93d
8 changed files with 478 additions and 118 deletions

View File

@ -49,15 +49,11 @@ class BooleanToStringTransformer implements DataTransformerInterface
*/ */
public function transform($value) public function transform($value)
{ {
if (null === $value) {
return null;
}
if (!is_bool($value)) { if (!is_bool($value)) {
throw new TransformationFailedException('Expected a Boolean.'); throw new TransformationFailedException('Expected a Boolean.');
} }
return true === $value ? $this->trueValue : null; return $value ? $this->trueValue : null;
} }
/** /**

View File

@ -11,6 +11,7 @@
namespace Symfony\Component\Form\Extension\Core\EventListener; namespace Symfony\Component\Form\Extension\Core\EventListener;
use Symfony\Component\Form\Exception\TransformationFailedException;
use Symfony\Component\Form\FormEvents; use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormEvent; use Symfony\Component\Form\FormEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface;
@ -38,10 +39,43 @@ class FixCheckboxInputListener implements EventSubscriberInterface
public function preSubmit(FormEvent $event) public function preSubmit(FormEvent $event)
{ {
$values = (array) $event->getData(); $data = $event->getData();
$indices = $this->choiceList->getIndicesForValues($values);
$event->setData(count($indices) > 0 ? array_combine($indices, $values) : array()); if (is_array($data)) {
// Flip the submitted values for faster lookup
// It's better to flip this array than $existingValues because
// $submittedValues is generally smaller.
$submittedValues = array_flip($data);
// Since expanded choice fields are completely loaded anyway, we
// can just as well get the values again without losing performance.
$existingValues = $this->choiceList->getValues();
// Clear the data array and fill it with correct indices
$data = array();
foreach ($existingValues as $index => $value) {
if (isset($submittedValues[$value])) {
// Value was submitted
$data[$index] = $value;
unset($submittedValues[$value]);
}
}
if (count($submittedValues) > 0) {
throw new TransformationFailedException(sprintf(
'The following choices were not found: "%s"',
implode('", "', array_keys($submittedValues))
));
}
} elseif ('' === $data || null === $data) {
// Empty values are always accepted.
$data = array();
}
// Else leave the data unchanged to provoke an error during submission
$event->setData($data);
} }
/** /**

View File

@ -42,10 +42,22 @@ class FixRadioInputListener implements EventSubscriberInterface
public function preSubmit(FormEvent $event) public function preSubmit(FormEvent $event)
{ {
$value = $event->getData(); $data = $event->getData();
$index = current($this->choiceList->getIndicesForValues(array($value)));
$event->setData(false !== $index ? array($index => $value) : ($this->placeholderPresent ? array('placeholder' => '') : array())) ; // Since expanded choice fields are completely loaded anyway, we
// can just as well get the values again without losing performance.
$existingValues = $this->choiceList->getValues();
if (false !== ($index = array_search($data, $existingValues, true))) {
$data = array($index => $data);
} elseif ('' === $data || null === $data) {
// Empty values are always accepted.
$data = $this->placeholderPresent ? array('placeholder' => '') : array();
}
// Else leave the data unchanged to provoke an error during submission
$event->setData($data);
} }
/** /**

View File

@ -25,9 +25,14 @@ class CheckboxType extends AbstractType
*/ */
public function buildForm(FormBuilderInterface $builder, array $options) public function buildForm(FormBuilderInterface $builder, array $options)
{ {
$builder // Unlike in other types, where the data is NULL by default, it
->addViewTransformer(new BooleanToStringTransformer($options['value'])) // needs to be a Boolean here. setData(null) is not acceptable
; // for checkboxes and radio buttons (unless a custom model
// transformer handles this case).
// We cannot solve this case via overriding the "data" option, because
// doing so also calls setDataLocked(true).
$builder->setData(isset($options['data']) ? $options['data'] : false);
$builder->addViewTransformer(new BooleanToStringTransformer($options['value']));
} }
/** /**
@ -46,8 +51,8 @@ class CheckboxType extends AbstractType
*/ */
public function setDefaultOptions(OptionsResolverInterface $resolver) public function setDefaultOptions(OptionsResolverInterface $resolver)
{ {
$emptyData = function (FormInterface $form, $clientData) { $emptyData = function (FormInterface $form, $viewData) {
return $clientData; return $viewData;
}; };
$resolver->setDefaults(array( $resolver->setDefaults(array(

View File

@ -522,83 +522,82 @@ class Form implements \IteratorAggregate, FormInterface
$dispatcher = $this->config->getEventDispatcher(); $dispatcher = $this->config->getEventDispatcher();
// Hook to change content of the data submitted by the browser $modelData = null;
if ($dispatcher->hasListeners(FormEvents::PRE_SUBMIT)) { $normData = null;
$event = new FormEvent($this, $submittedData); $viewData = null;
$dispatcher->dispatch(FormEvents::PRE_SUBMIT, $event);
$submittedData = $event->getData();
}
// Check whether the form is compound. try {
// This check is preferable over checking the number of children, // Hook to change content of the data submitted by the browser
// since forms without children may also be compound. if ($dispatcher->hasListeners(FormEvents::PRE_SUBMIT)) {
// (think of empty collection forms) $event = new FormEvent($this, $submittedData);
if ($this->config->getCompound()) { $dispatcher->dispatch(FormEvents::PRE_SUBMIT, $event);
if (!is_array($submittedData)) { $submittedData = $event->getData();
$submittedData = array();
} }
foreach ($this->children as $name => $child) { // Check whether the form is compound.
if (array_key_exists($name, $submittedData) || $clearMissing) { // This check is preferable over checking the number of children,
$child->submit(isset($submittedData[$name]) ? $submittedData[$name] : null, $clearMissing); // since forms without children may also be compound.
unset($submittedData[$name]); // (think of empty collection forms)
} if ($this->config->getCompound()) {
} if (null === $submittedData) {
$submittedData = array();
$this->extraData = $submittedData;
}
// Forms that inherit their parents' data also are not processed,
// because then it would be too difficult to merge the changes in
// the child and the parent form. Instead, the parent form also takes
// changes in the grandchildren (i.e. children of the form that inherits
// its parent's data) into account.
// (see InheritDataAwareIterator below)
if ($this->config->getInheritData()) {
$this->submitted = true;
// When POST_SUBMIT is reached, the data is not yet updated, so pass
// NULL to prevent hard-to-debug bugs.
$dataForPostSubmit = null;
} else {
// If the form is compound, the default data in view format
// is reused. The data of the children is merged into this
// default data using the data mapper.
// If the form is not compound, the submitted data is also the data in view format.
$viewData = $this->config->getCompound() ? $this->viewData : $submittedData;
if (FormUtil::isEmpty($viewData)) {
$emptyData = $this->config->getEmptyData();
if ($emptyData instanceof \Closure) {
/* @var \Closure $emptyData */
$emptyData = $emptyData($this, $viewData);
} }
$viewData = $emptyData; if (!is_array($submittedData)) {
throw new TransformationFailedException('Compound forms expect an array or NULL on submission.');
}
foreach ($this->children as $name => $child) {
if (isset($submittedData[$name]) || $clearMissing) {
$child->submit(isset($submittedData[$name]) ? $submittedData[$name] : null, $clearMissing);
unset($submittedData[$name]);
}
}
$this->extraData = $submittedData;
} }
// Merge form data from children into existing view data // Forms that inherit their parents' data also are not processed,
// It is not necessary to invoke this method if the form has no children, // because then it would be too difficult to merge the changes in
// even if it is compound. // the child and the parent form. Instead, the parent form also takes
if (count($this->children) > 0) { // changes in the grandchildren (i.e. children of the form that inherits
// Use InheritDataAwareIterator to process children of // its parent's data) into account.
// descendants that inherit this form's data. // (see InheritDataAwareIterator below)
// These descendants will not be submitted normally (see the check if (!$this->config->getInheritData()) {
// for $this->config->getInheritData() above) // If the form is compound, the default data in view format
$iterator = new InheritDataAwareIterator($this->children); // is reused. The data of the children is merged into this
$iterator = new \RecursiveIteratorIterator($iterator); // default data using the data mapper.
$this->config->getDataMapper()->mapFormsToData($iterator, $viewData); // If the form is not compound, the submitted data is also the data in view format.
} $viewData = $this->config->getCompound() ? $this->viewData : $submittedData;
$modelData = null; if (FormUtil::isEmpty($viewData)) {
$normData = null; $emptyData = $this->config->getEmptyData();
if ($emptyData instanceof \Closure) {
/* @var \Closure $emptyData */
$emptyData = $emptyData($this, $viewData);
}
$viewData = $emptyData;
}
// Merge form data from children into existing view data
// It is not necessary to invoke this method if the form has no children,
// even if it is compound.
if (count($this->children) > 0) {
// Use InheritDataAwareIterator to process children of
// descendants that inherit this form's data.
// These descendants will not be submitted normally (see the check
// for $this->config->getInheritData() above)
$childrenIterator = new InheritDataAwareIterator($this->children);
$childrenIterator = new \RecursiveIteratorIterator($childrenIterator);
$this->config->getDataMapper()->mapFormsToData($childrenIterator, $viewData);
}
try {
// Normalize data to unified representation // Normalize data to unified representation
$normData = $this->viewToNorm($viewData); $normData = $this->viewToNorm($viewData);
// Hook to change content of the data into the normalized // Hook to change content of the data in the normalized
// representation // representation
if ($dispatcher->hasListeners(FormEvents::SUBMIT)) { if ($dispatcher->hasListeners(FormEvents::SUBMIT)) {
$event = new FormEvent($this, $normData); $event = new FormEvent($this, $normData);
@ -609,20 +608,26 @@ class Form implements \IteratorAggregate, FormInterface
// Synchronize representations - must not change the content! // Synchronize representations - must not change the content!
$modelData = $this->normToModel($normData); $modelData = $this->normToModel($normData);
$viewData = $this->normToView($normData); $viewData = $this->normToView($normData);
} catch (TransformationFailedException $e) {
$this->synchronized = false;
} }
} catch (TransformationFailedException $e) {
$this->synchronized = false;
$this->submitted = true; // If $viewData was not yet set, set it to $submittedData so that
$this->modelData = $modelData; // the erroneous data is accessible on the form.
$this->normData = $normData; // Forms that inherit data never set any data, because the getters
$this->viewData = $viewData; // forward to the parent form's getters anyway.
if (null === $viewData && !$this->config->getInheritData()) {
$dataForPostSubmit = $viewData; $viewData = $submittedData;
}
} }
$this->submitted = true;
$this->modelData = $modelData;
$this->normData = $normData;
$this->viewData = $viewData;
if ($dispatcher->hasListeners(FormEvents::POST_SUBMIT)) { if ($dispatcher->hasListeners(FormEvents::POST_SUBMIT)) {
$event = new FormEvent($this, $dataForPostSubmit); $event = new FormEvent($this, $viewData);
$dispatcher->dispatch(FormEvents::POST_SUBMIT, $event); $dispatcher->dispatch(FormEvents::POST_SUBMIT, $event);
} }

View File

@ -17,6 +17,9 @@ class BooleanToStringTransformerTest extends \PHPUnit_Framework_TestCase
{ {
const TRUE_VALUE = '1'; const TRUE_VALUE = '1';
/**
* @var BooleanToStringTransformer
*/
protected $transformer; protected $transformer;
protected function setUp() protected function setUp()
@ -33,20 +36,29 @@ class BooleanToStringTransformerTest extends \PHPUnit_Framework_TestCase
{ {
$this->assertEquals(self::TRUE_VALUE, $this->transformer->transform(true)); $this->assertEquals(self::TRUE_VALUE, $this->transformer->transform(true));
$this->assertNull($this->transformer->transform(false)); $this->assertNull($this->transformer->transform(false));
$this->assertNull($this->transformer->transform(null));
} }
public function testTransformExpectsBoolean() /**
* @expectedException \Symfony\Component\Form\Exception\TransformationFailedException
*/
public function testTransformFailsIfNull()
{ {
$this->setExpectedException('Symfony\Component\Form\Exception\TransformationFailedException'); $this->transformer->transform(null);
}
/**
* @expectedException \Symfony\Component\Form\Exception\TransformationFailedException
*/
public function testTransformFailsIfString()
{
$this->transformer->transform('1'); $this->transformer->transform('1');
} }
public function testReverseTransformExpectsString() /**
* @expectedException \Symfony\Component\Form\Exception\TransformationFailedException
*/
public function testReverseTransformFailsIfInteger()
{ {
$this->setExpectedException('Symfony\Component\Form\Exception\TransformationFailedException');
$this->transformer->reverseTransform(1); $this->transformer->reverseTransform(1);
} }

View File

@ -15,6 +15,15 @@ use Symfony\Component\Form\CallbackTransformer;
class CheckboxTypeTest extends \Symfony\Component\Form\Test\TypeTestCase class CheckboxTypeTest extends \Symfony\Component\Form\Test\TypeTestCase
{ {
public function testDataIsFalseByDefault()
{
$form = $this->factory->create('checkbox');
$this->assertFalse($form->getData());
$this->assertFalse($form->getNormData());
$this->assertNull($form->getViewData());
}
public function testPassValueToView() public function testPassValueToView()
{ {
$form = $this->factory->create('checkbox', null, array('value' => 'foobar')); $form = $this->factory->create('checkbox', null, array('value' => 'foobar'));
@ -105,58 +114,60 @@ class CheckboxTypeTest extends \Symfony\Component\Form\Test\TypeTestCase
$this->assertNull($form->getViewData()); $this->assertNull($form->getViewData());
} }
public function testBindWithEmptyValueAndFalseUnchecked() public function testSubmitWithEmptyValueAndFalseUnchecked()
{ {
$form = $this->factory->create('checkbox', null, array( $form = $this->factory->create('checkbox', null, array(
'value' => '', 'value' => '',
)); ));
$form->bind(false); $form->submit(false);
$this->assertFalse($form->getData()); $this->assertFalse($form->getData());
$this->assertNull($form->getViewData()); $this->assertNull($form->getViewData());
} }
public function testBindWithEmptyValueAndTrueChecked() public function testSubmitWithEmptyValueAndTrueChecked()
{ {
$form = $this->factory->create('checkbox', null, array( $form = $this->factory->create('checkbox', null, array(
'value' => '', 'value' => '',
)); ));
$form->bind(true); $form->submit(true);
$this->assertTrue($form->getData()); $this->assertTrue($form->getData());
$this->assertSame('', $form->getViewData()); $this->assertSame('', $form->getViewData());
} }
/** /**
* @dataProvider provideTransformedData * @dataProvider provideCustomModelTransformerData
*/ */
public function testTransformedData($data, $expected) public function testCustomModelTransformer($data, $checked)
{ {
// present a binary status field as a checkbox // present a binary status field as a checkbox
$transformer = new CallbackTransformer( $transformer = new CallbackTransformer(
function ($value) { function ($value) {
return 'expedited' == $value; return 'checked' == $value;
}, },
function ($value) { function ($value) {
return $value ? 'expedited' : 'standard'; return $value ? 'checked' : 'unchecked';
} }
); );
$form = $this->builder $form = $this->factory->createBuilder('checkbox')
->create('expedited_shipping', 'checkbox')
->addModelTransformer($transformer) ->addModelTransformer($transformer)
->getForm(); ->getForm();
$form->setData($data); $form->setData($data);
$view = $form->createView(); $view = $form->createView();
$this->assertEquals($expected, $view->vars['checked']); $this->assertSame($data, $form->getData());
$this->assertSame($checked, $form->getNormData());
$this->assertEquals($checked, $view->vars['checked']);
} }
public function provideTransformedData() public function provideCustomModelTransformerData()
{ {
return array( return array(
array('expedited', true), array('checked', true),
array('standard', false), array('unchecked', false),
); );
} }
} }

View File

@ -231,6 +231,21 @@ class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase
$this->assertEquals('b', $form->getViewData()); $this->assertEquals('b', $form->getViewData());
} }
public function testSubmitSingleNonExpandedInvalidChoice()
{
$form = $this->factory->create('choice', null, array(
'multiple' => false,
'expanded' => false,
'choices' => $this->choices,
));
$form->submit('foobar');
$this->assertNull($form->getData());
$this->assertEquals('foobar', $form->getViewData());
$this->assertFalse($form->isSynchronized());
}
public function testSubmitSingleNonExpandedObjectChoices() public function testSubmitSingleNonExpandedObjectChoices()
{ {
$form = $this->factory->create('choice', null, array( $form = $this->factory->create('choice', null, array(
@ -268,6 +283,36 @@ class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase
$this->assertEquals(array('a', 'b'), $form->getViewData()); $this->assertEquals(array('a', 'b'), $form->getViewData());
} }
public function testSubmitMultipleNonExpandedInvalidScalarChoice()
{
$form = $this->factory->create('choice', null, array(
'multiple' => true,
'expanded' => false,
'choices' => $this->choices,
));
$form->submit('foobar');
$this->assertNull($form->getData());
$this->assertEquals('foobar', $form->getViewData());
$this->assertFalse($form->isSynchronized());
}
public function testSubmitMultipleNonExpandedInvalidArrayChoice()
{
$form = $this->factory->create('choice', null, array(
'multiple' => true,
'expanded' => false,
'choices' => $this->choices,
));
$form->submit(array('a', 'foobar'));
$this->assertNull($form->getData());
$this->assertEquals(array('a', 'foobar'), $form->getViewData());
$this->assertFalse($form->isSynchronized());
}
public function testSubmitMultipleNonExpandedObjectChoices() public function testSubmitMultipleNonExpandedObjectChoices()
{ {
$form = $this->factory->create('choice', null, array( $form = $this->factory->create('choice', null, array(
@ -309,6 +354,8 @@ class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase
3 => false, 3 => false,
4 => false, 4 => false,
), $form->getViewData()); ), $form->getViewData());
$this->assertEmpty($form->getExtraData());
$this->assertTrue($form->isSynchronized());
$this->assertFalse($form[0]->getData()); $this->assertFalse($form[0]->getData());
$this->assertTrue($form[1]->getData()); $this->assertTrue($form[1]->getData());
@ -322,6 +369,34 @@ class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase
$this->assertNull($form[4]->getViewData()); $this->assertNull($form[4]->getViewData());
} }
public function testSubmitSingleExpandedRequiredInvalidChoice()
{
$form = $this->factory->create('choice', null, array(
'multiple' => false,
'expanded' => true,
'required' => true,
'choices' => $this->choices,
));
$form->submit('foobar');
$this->assertSame(null, $form->getData());
$this->assertSame('foobar', $form->getViewData());
$this->assertEmpty($form->getExtraData());
$this->assertFalse($form->isSynchronized());
$this->assertFalse($form[0]->getData());
$this->assertFalse($form[1]->getData());
$this->assertFalse($form[2]->getData());
$this->assertFalse($form[3]->getData());
$this->assertFalse($form[4]->getData());
$this->assertNull($form[0]->getViewData());
$this->assertNull($form[1]->getViewData());
$this->assertNull($form[2]->getViewData());
$this->assertNull($form[3]->getViewData());
$this->assertNull($form[4]->getViewData());
}
public function testSubmitSingleExpandedNonRequired() public function testSubmitSingleExpandedNonRequired()
{ {
$form = $this->factory->create('choice', null, array( $form = $this->factory->create('choice', null, array(
@ -342,6 +417,8 @@ class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase
4 => false, 4 => false,
'placeholder' => false, 'placeholder' => false,
), $form->getViewData()); ), $form->getViewData());
$this->assertEmpty($form->getExtraData());
$this->assertTrue($form->isSynchronized());
$this->assertFalse($form['placeholder']->getData()); $this->assertFalse($form['placeholder']->getData());
$this->assertFalse($form[0]->getData()); $this->assertFalse($form[0]->getData());
@ -357,7 +434,35 @@ class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase
$this->assertNull($form[4]->getViewData()); $this->assertNull($form[4]->getViewData());
} }
public function testSubmitSingleExpandedRequiredNothingChecked() public function testSubmitSingleExpandedNonRequiredInvalidChoice()
{
$form = $this->factory->create('choice', null, array(
'multiple' => false,
'expanded' => true,
'required' => false,
'choices' => $this->choices,
));
$form->submit('foobar');
$this->assertSame(null, $form->getData());
$this->assertSame('foobar', $form->getViewData());
$this->assertEmpty($form->getExtraData());
$this->assertFalse($form->isSynchronized());
$this->assertFalse($form[0]->getData());
$this->assertFalse($form[1]->getData());
$this->assertFalse($form[2]->getData());
$this->assertFalse($form[3]->getData());
$this->assertFalse($form[4]->getData());
$this->assertNull($form[0]->getViewData());
$this->assertNull($form[1]->getViewData());
$this->assertNull($form[2]->getViewData());
$this->assertNull($form[3]->getViewData());
$this->assertNull($form[4]->getViewData());
}
public function testSubmitSingleExpandedRequiredNull()
{ {
$form = $this->factory->create('choice', null, array( $form = $this->factory->create('choice', null, array(
'multiple' => false, 'multiple' => false,
@ -376,6 +481,8 @@ class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase
3 => false, 3 => false,
4 => false, 4 => false,
), $form->getViewData()); ), $form->getViewData());
$this->assertEmpty($form->getExtraData());
$this->assertTrue($form->isSynchronized());
$this->assertFalse($form[0]->getData()); $this->assertFalse($form[0]->getData());
$this->assertFalse($form[1]->getData()); $this->assertFalse($form[1]->getData());
@ -389,7 +496,75 @@ class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase
$this->assertNull($form[4]->getViewData()); $this->assertNull($form[4]->getViewData());
} }
public function testSubmitSingleExpandedNonRequiredNothingChecked() public function testSubmitSingleExpandedRequiredEmpty()
{
$form = $this->factory->create('choice', null, array(
'multiple' => false,
'expanded' => true,
'required' => true,
'choices' => $this->choices,
));
$form->submit('');
$this->assertNull($form->getData());
$this->assertSame(array(
0 => false,
1 => false,
2 => false,
3 => false,
4 => false,
), $form->getViewData());
$this->assertEmpty($form->getExtraData());
$this->assertTrue($form->isSynchronized());
$this->assertFalse($form[0]->getData());
$this->assertFalse($form[1]->getData());
$this->assertFalse($form[2]->getData());
$this->assertFalse($form[3]->getData());
$this->assertFalse($form[4]->getData());
$this->assertNull($form[0]->getViewData());
$this->assertNull($form[1]->getViewData());
$this->assertNull($form[2]->getViewData());
$this->assertNull($form[3]->getViewData());
$this->assertNull($form[4]->getViewData());
}
public function testSubmitSingleExpandedRequiredFalse()
{
$form = $this->factory->create('choice', null, array(
'multiple' => false,
'expanded' => true,
'required' => true,
'choices' => $this->choices,
));
$form->submit(false);
$this->assertNull($form->getData());
$this->assertSame(array(
0 => false,
1 => false,
2 => false,
3 => false,
4 => false,
), $form->getViewData());
$this->assertEmpty($form->getExtraData());
$this->assertTrue($form->isSynchronized());
$this->assertFalse($form[0]->getData());
$this->assertFalse($form[1]->getData());
$this->assertFalse($form[2]->getData());
$this->assertFalse($form[3]->getData());
$this->assertFalse($form[4]->getData());
$this->assertNull($form[0]->getViewData());
$this->assertNull($form[1]->getViewData());
$this->assertNull($form[2]->getViewData());
$this->assertNull($form[3]->getViewData());
$this->assertNull($form[4]->getViewData());
}
public function testSubmitSingleExpandedNonRequiredNull()
{ {
$form = $this->factory->create('choice', null, array( $form = $this->factory->create('choice', null, array(
'multiple' => false, 'multiple' => false,
@ -409,6 +584,8 @@ class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase
4 => false, 4 => false,
'placeholder' => true, 'placeholder' => true,
), $form->getViewData()); ), $form->getViewData());
$this->assertEmpty($form->getExtraData());
$this->assertTrue($form->isSynchronized());
$this->assertTrue($form['placeholder']->getData()); $this->assertTrue($form['placeholder']->getData());
$this->assertFalse($form[0]->getData()); $this->assertFalse($form[0]->getData());
@ -424,22 +601,44 @@ class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase
$this->assertNull($form[4]->getViewData()); $this->assertNull($form[4]->getViewData());
} }
public function testSubmitFalseToSingleExpandedRequiredDoesNotProduceExtraChildrenError() public function testSubmitSingleExpandedNonRequiredEmpty()
{ {
$form = $this->factory->create('choice', null, array( $form = $this->factory->create('choice', null, array(
'multiple' => false, 'multiple' => false,
'expanded' => true, 'expanded' => true,
'required' => true, 'required' => false,
'choices' => $this->choices, 'choices' => $this->choices,
)); ));
$form->submit(false); $form->submit('');
$this->assertEmpty($form->getExtraData());
$this->assertNull($form->getData()); $this->assertNull($form->getData());
$this->assertSame(array(
0 => false,
1 => false,
2 => false,
3 => false,
4 => false,
'placeholder' => true,
), $form->getViewData());
$this->assertEmpty($form->getExtraData());
$this->assertTrue($form->isSynchronized());
$this->assertTrue($form['placeholder']->getData());
$this->assertFalse($form[0]->getData());
$this->assertFalse($form[1]->getData());
$this->assertFalse($form[2]->getData());
$this->assertFalse($form[3]->getData());
$this->assertFalse($form[4]->getData());
$this->assertSame('', $form['placeholder']->getViewData());
$this->assertNull($form[0]->getViewData());
$this->assertNull($form[1]->getViewData());
$this->assertNull($form[2]->getViewData());
$this->assertNull($form[3]->getViewData());
$this->assertNull($form[4]->getViewData());
} }
public function testSubmitFalseToSingleExpandedNonRequiredDoesNotProduceExtraChildrenError() public function testSubmitSingleExpandedNonRequiredFalse()
{ {
$form = $this->factory->create('choice', null, array( $form = $this->factory->create('choice', null, array(
'multiple' => false, 'multiple' => false,
@ -450,8 +649,30 @@ class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase
$form->submit(false); $form->submit(false);
$this->assertEmpty($form->getExtraData());
$this->assertNull($form->getData()); $this->assertNull($form->getData());
$this->assertSame(array(
0 => false,
1 => false,
2 => false,
3 => false,
4 => false,
'placeholder' => true,
), $form->getViewData());
$this->assertEmpty($form->getExtraData());
$this->assertTrue($form->isSynchronized());
$this->assertTrue($form['placeholder']->getData());
$this->assertFalse($form[0]->getData());
$this->assertFalse($form[1]->getData());
$this->assertFalse($form[2]->getData());
$this->assertFalse($form[3]->getData());
$this->assertFalse($form[4]->getData());
$this->assertSame('', $form['placeholder']->getViewData());
$this->assertNull($form[0]->getViewData());
$this->assertNull($form[1]->getViewData());
$this->assertNull($form[2]->getViewData());
$this->assertNull($form[3]->getViewData());
$this->assertNull($form[4]->getViewData());
} }
public function testSubmitSingleExpandedWithEmptyChild() public function testSubmitSingleExpandedWithEmptyChild()
@ -539,6 +760,16 @@ class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase
$form->submit(array('a', 'c')); $form->submit(array('a', 'c'));
$this->assertSame(array('a', 'c'), $form->getData()); $this->assertSame(array('a', 'c'), $form->getData());
$this->assertSame(array(
0 => true,
1 => false,
2 => true,
3 => false,
4 => false,
), $form->getViewData());
$this->assertEmpty($form->getExtraData());
$this->assertTrue($form->isSynchronized());
$this->assertTrue($form[0]->getData()); $this->assertTrue($form[0]->getData());
$this->assertFalse($form[1]->getData()); $this->assertFalse($form[1]->getData());
$this->assertTrue($form[2]->getData()); $this->assertTrue($form[2]->getData());
@ -551,6 +782,60 @@ class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase
$this->assertNull($form[4]->getViewData()); $this->assertNull($form[4]->getViewData());
} }
public function testSubmitMultipleExpandedInvalidScalarChoice()
{
$form = $this->factory->create('choice', null, array(
'multiple' => true,
'expanded' => true,
'choices' => $this->choices,
));
$form->submit('foobar');
$this->assertNull($form->getData());
$this->assertSame('foobar', $form->getViewData());
$this->assertEmpty($form->getExtraData());
$this->assertFalse($form->isSynchronized());
$this->assertFalse($form[0]->getData());
$this->assertFalse($form[1]->getData());
$this->assertFalse($form[2]->getData());
$this->assertFalse($form[3]->getData());
$this->assertFalse($form[4]->getData());
$this->assertNull($form[0]->getViewData());
$this->assertNull($form[1]->getViewData());
$this->assertNull($form[2]->getViewData());
$this->assertNull($form[3]->getViewData());
$this->assertNull($form[4]->getViewData());
}
public function testSubmitMultipleExpandedInvalidArrayChoice()
{
$form = $this->factory->create('choice', null, array(
'multiple' => true,
'expanded' => true,
'choices' => $this->choices,
));
$form->submit(array('a', 'foobar'));
$this->assertNull($form->getData());
$this->assertSame(array('a', 'foobar'), $form->getViewData());
$this->assertEmpty($form->getExtraData());
$this->assertFalse($form->isSynchronized());
$this->assertFalse($form[0]->getData());
$this->assertFalse($form[1]->getData());
$this->assertFalse($form[2]->getData());
$this->assertFalse($form[3]->getData());
$this->assertFalse($form[4]->getData());
$this->assertNull($form[0]->getViewData());
$this->assertNull($form[1]->getViewData());
$this->assertNull($form[2]->getViewData());
$this->assertNull($form[3]->getViewData());
$this->assertNull($form[4]->getViewData());
}
public function testSubmitMultipleExpandedEmpty() public function testSubmitMultipleExpandedEmpty()
{ {
$form = $this->factory->create('choice', null, array( $form = $this->factory->create('choice', null, array(