diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/BooleanToStringTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/BooleanToStringTransformer.php index 95e7332d3e..f8e3b5bfcb 100644 --- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/BooleanToStringTransformer.php +++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/BooleanToStringTransformer.php @@ -49,15 +49,11 @@ class BooleanToStringTransformer implements DataTransformerInterface */ public function transform($value) { - if (null === $value) { - return null; - } - if (!is_bool($value)) { throw new TransformationFailedException('Expected a Boolean.'); } - return true === $value ? $this->trueValue : null; + return $value ? $this->trueValue : null; } /** diff --git a/src/Symfony/Component/Form/Extension/Core/EventListener/FixCheckboxInputListener.php b/src/Symfony/Component/Form/Extension/Core/EventListener/FixCheckboxInputListener.php index 1f62e060ff..8e72b04137 100644 --- a/src/Symfony/Component/Form/Extension/Core/EventListener/FixCheckboxInputListener.php +++ b/src/Symfony/Component/Form/Extension/Core/EventListener/FixCheckboxInputListener.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Form\Extension\Core\EventListener; +use Symfony\Component\Form\Exception\TransformationFailedException; use Symfony\Component\Form\FormEvents; use Symfony\Component\Form\FormEvent; use Symfony\Component\EventDispatcher\EventSubscriberInterface; @@ -38,10 +39,43 @@ class FixCheckboxInputListener implements EventSubscriberInterface public function preSubmit(FormEvent $event) { - $values = (array) $event->getData(); - $indices = $this->choiceList->getIndicesForValues($values); + $data = $event->getData(); - $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); } /** diff --git a/src/Symfony/Component/Form/Extension/Core/EventListener/FixRadioInputListener.php b/src/Symfony/Component/Form/Extension/Core/EventListener/FixRadioInputListener.php index bf03792fed..925585db40 100644 --- a/src/Symfony/Component/Form/Extension/Core/EventListener/FixRadioInputListener.php +++ b/src/Symfony/Component/Form/Extension/Core/EventListener/FixRadioInputListener.php @@ -42,10 +42,22 @@ class FixRadioInputListener implements EventSubscriberInterface public function preSubmit(FormEvent $event) { - $value = $event->getData(); - $index = current($this->choiceList->getIndicesForValues(array($value))); + $data = $event->getData(); - $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); } /** diff --git a/src/Symfony/Component/Form/Extension/Core/Type/CheckboxType.php b/src/Symfony/Component/Form/Extension/Core/Type/CheckboxType.php index 214e581aff..3952bb3316 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/CheckboxType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/CheckboxType.php @@ -25,9 +25,14 @@ class CheckboxType extends AbstractType */ public function buildForm(FormBuilderInterface $builder, array $options) { - $builder - ->addViewTransformer(new BooleanToStringTransformer($options['value'])) - ; + // Unlike in other types, where the data is NULL by default, it + // 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) { - $emptyData = function (FormInterface $form, $clientData) { - return $clientData; + $emptyData = function (FormInterface $form, $viewData) { + return $viewData; }; $resolver->setDefaults(array( diff --git a/src/Symfony/Component/Form/Form.php b/src/Symfony/Component/Form/Form.php index 5399cc99cc..f3324986c8 100644 --- a/src/Symfony/Component/Form/Form.php +++ b/src/Symfony/Component/Form/Form.php @@ -522,83 +522,82 @@ class Form implements \IteratorAggregate, FormInterface $dispatcher = $this->config->getEventDispatcher(); - // Hook to change content of the data submitted by the browser - if ($dispatcher->hasListeners(FormEvents::PRE_SUBMIT)) { - $event = new FormEvent($this, $submittedData); - $dispatcher->dispatch(FormEvents::PRE_SUBMIT, $event); - $submittedData = $event->getData(); - } + $modelData = null; + $normData = null; + $viewData = null; - // Check whether the form is compound. - // This check is preferable over checking the number of children, - // since forms without children may also be compound. - // (think of empty collection forms) - if ($this->config->getCompound()) { - if (!is_array($submittedData)) { - $submittedData = array(); + try { + // Hook to change content of the data submitted by the browser + if ($dispatcher->hasListeners(FormEvents::PRE_SUBMIT)) { + $event = new FormEvent($this, $submittedData); + $dispatcher->dispatch(FormEvents::PRE_SUBMIT, $event); + $submittedData = $event->getData(); } - foreach ($this->children as $name => $child) { - if (array_key_exists($name, $submittedData) || $clearMissing) { - $child->submit(isset($submittedData[$name]) ? $submittedData[$name] : null, $clearMissing); - unset($submittedData[$name]); - } - } - - $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); + // Check whether the form is compound. + // This check is preferable over checking the number of children, + // since forms without children may also be compound. + // (think of empty collection forms) + if ($this->config->getCompound()) { + if (null === $submittedData) { + $submittedData = array(); } - $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 - // 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) - $iterator = new InheritDataAwareIterator($this->children); - $iterator = new \RecursiveIteratorIterator($iterator); - $this->config->getDataMapper()->mapFormsToData($iterator, $viewData); - } + // 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()) { + // 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; - $modelData = null; - $normData = null; + if (FormUtil::isEmpty($viewData)) { + $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 $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 if ($dispatcher->hasListeners(FormEvents::SUBMIT)) { $event = new FormEvent($this, $normData); @@ -609,20 +608,26 @@ class Form implements \IteratorAggregate, FormInterface // Synchronize representations - must not change the content! $modelData = $this->normToModel($normData); $viewData = $this->normToView($normData); - } catch (TransformationFailedException $e) { - $this->synchronized = false; } + } catch (TransformationFailedException $e) { + $this->synchronized = false; - $this->submitted = true; - $this->modelData = $modelData; - $this->normData = $normData; - $this->viewData = $viewData; - - $dataForPostSubmit = $viewData; + // If $viewData was not yet set, set it to $submittedData so that + // the erroneous data is accessible on the form. + // Forms that inherit data never set any data, because the getters + // forward to the parent form's getters anyway. + if (null === $viewData && !$this->config->getInheritData()) { + $viewData = $submittedData; + } } + $this->submitted = true; + $this->modelData = $modelData; + $this->normData = $normData; + $this->viewData = $viewData; + if ($dispatcher->hasListeners(FormEvents::POST_SUBMIT)) { - $event = new FormEvent($this, $dataForPostSubmit); + $event = new FormEvent($this, $viewData); $dispatcher->dispatch(FormEvents::POST_SUBMIT, $event); } diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/BooleanToStringTransformerTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/BooleanToStringTransformerTest.php index 41f8f956d7..4f6238aee0 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/BooleanToStringTransformerTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/BooleanToStringTransformerTest.php @@ -17,6 +17,9 @@ class BooleanToStringTransformerTest extends \PHPUnit_Framework_TestCase { const TRUE_VALUE = '1'; + /** + * @var BooleanToStringTransformer + */ protected $transformer; protected function setUp() @@ -33,20 +36,29 @@ class BooleanToStringTransformerTest extends \PHPUnit_Framework_TestCase { $this->assertEquals(self::TRUE_VALUE, $this->transformer->transform(true)); $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'); } - public function testReverseTransformExpectsString() + /** + * @expectedException \Symfony\Component\Form\Exception\TransformationFailedException + */ + public function testReverseTransformFailsIfInteger() { - $this->setExpectedException('Symfony\Component\Form\Exception\TransformationFailedException'); - $this->transformer->reverseTransform(1); } diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/CheckboxTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/CheckboxTypeTest.php index c782adab04..9437bd7eea 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/CheckboxTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/CheckboxTypeTest.php @@ -15,6 +15,15 @@ use Symfony\Component\Form\CallbackTransformer; 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() { $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()); } - public function testBindWithEmptyValueAndFalseUnchecked() + public function testSubmitWithEmptyValueAndFalseUnchecked() { $form = $this->factory->create('checkbox', null, array( 'value' => '', )); - $form->bind(false); + $form->submit(false); $this->assertFalse($form->getData()); $this->assertNull($form->getViewData()); } - public function testBindWithEmptyValueAndTrueChecked() + public function testSubmitWithEmptyValueAndTrueChecked() { $form = $this->factory->create('checkbox', null, array( 'value' => '', )); - $form->bind(true); + $form->submit(true); $this->assertTrue($form->getData()); $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 $transformer = new CallbackTransformer( function ($value) { - return 'expedited' == $value; + return 'checked' == $value; }, function ($value) { - return $value ? 'expedited' : 'standard'; + return $value ? 'checked' : 'unchecked'; } ); - $form = $this->builder - ->create('expedited_shipping', 'checkbox') + $form = $this->factory->createBuilder('checkbox') ->addModelTransformer($transformer) ->getForm(); + $form->setData($data); $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( - array('expedited', true), - array('standard', false), + array('checked', true), + array('unchecked', false), ); } } diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php index 219e8181c0..ee40e74268 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php @@ -231,6 +231,21 @@ class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase $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() { $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()); } + 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() { $form = $this->factory->create('choice', null, array( @@ -309,6 +354,8 @@ class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase 3 => false, 4 => false, ), $form->getViewData()); + $this->assertEmpty($form->getExtraData()); + $this->assertTrue($form->isSynchronized()); $this->assertFalse($form[0]->getData()); $this->assertTrue($form[1]->getData()); @@ -322,6 +369,34 @@ class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase $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() { $form = $this->factory->create('choice', null, array( @@ -342,6 +417,8 @@ class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase 4 => false, 'placeholder' => false, ), $form->getViewData()); + $this->assertEmpty($form->getExtraData()); + $this->assertTrue($form->isSynchronized()); $this->assertFalse($form['placeholder']->getData()); $this->assertFalse($form[0]->getData()); @@ -357,7 +434,35 @@ class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase $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( 'multiple' => false, @@ -376,6 +481,8 @@ class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase 3 => false, 4 => false, ), $form->getViewData()); + $this->assertEmpty($form->getExtraData()); + $this->assertTrue($form->isSynchronized()); $this->assertFalse($form[0]->getData()); $this->assertFalse($form[1]->getData()); @@ -389,7 +496,75 @@ class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase $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( 'multiple' => false, @@ -409,6 +584,8 @@ class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase 4 => false, 'placeholder' => true, ), $form->getViewData()); + $this->assertEmpty($form->getExtraData()); + $this->assertTrue($form->isSynchronized()); $this->assertTrue($form['placeholder']->getData()); $this->assertFalse($form[0]->getData()); @@ -424,22 +601,44 @@ class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase $this->assertNull($form[4]->getViewData()); } - public function testSubmitFalseToSingleExpandedRequiredDoesNotProduceExtraChildrenError() + public function testSubmitSingleExpandedNonRequiredEmpty() { $form = $this->factory->create('choice', null, array( 'multiple' => false, 'expanded' => true, - 'required' => true, + 'required' => false, 'choices' => $this->choices, )); - $form->submit(false); + $form->submit(''); - $this->assertEmpty($form->getExtraData()); $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( 'multiple' => false, @@ -450,8 +649,30 @@ class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase $form->submit(false); - $this->assertEmpty($form->getExtraData()); $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() @@ -539,6 +760,16 @@ class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase $form->submit(array('a', 'c')); $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->assertFalse($form[1]->getData()); $this->assertTrue($form[2]->getData()); @@ -551,6 +782,60 @@ class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase $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() { $form = $this->factory->create('choice', null, array(