diff --git a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php index d37610982871..489d5d77d72e 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php @@ -160,6 +160,22 @@ public function buildForm(FormBuilderInterface $builder, array $options) // transformation is merged back into the original collection $builder->addEventSubscriber(new MergeCollectionListener(true, true)); } + + // To avoid issues when the submitted choices are arrays (i.e. array to string conversions), + // we have to ensure that all elements of the submitted choice data are strings or null. + $builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) { + $data = $event->getData(); + + if (!is_array($data)) { + return; + } + + foreach ($data as $v) { + if (null !== $v && !is_string($v)) { + throw new TransformationFailedException('All choices submitted must be NULL or strings.'); + } + } + }, 256); } /** @@ -505,8 +521,8 @@ private function createChoiceListView(ChoiceListInterface $choiceList, array $op * "choice_label" closure by default. * * @param array|\Traversable $choices The choice labels indexed by choices - * @param object $choiceLabels The object that receives the choice labels - * indexed by generated keys. + * @param object $choiceLabels the object that receives the choice labels + * indexed by generated keys * @param int $nextKey The next generated key * * @return array The choices in a normalized array with labels replaced by generated keys 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 b675d5d6eee0..10adffbecc26 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php @@ -14,9 +14,10 @@ use Symfony\Component\Form\ChoiceList\View\ChoiceGroupView; use Symfony\Component\Form\ChoiceList\View\ChoiceView; use Symfony\Component\Form\Extension\Core\ChoiceList\ObjectChoiceList; +use Symfony\Component\Form\Test\TypeTestCase; use Symfony\Component\Form\Tests\Fixtures\ChoiceSubType; -class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase +class ChoiceTypeTest extends TypeTestCase { private $choices = array( 'Bernhard' => 'a', @@ -2283,4 +2284,30 @@ public function testCustomChoiceTypeDoesNotInheritChoiceLabels() // In this case the 'choice_label' closure returns null and not the closure from the first choice type. $this->assertNull($form->get('subChoice')->getConfig()->getOption('choice_label')); } + + /** + * @dataProvider invalidNestedValueTestMatrix + */ + public function testSubmitInvalidNestedValue($multiple, $expanded, $submissionData) + { + $form = $this->factory->create('choice', null, array( + 'choices' => $this->choices, + 'multiple' => $multiple, + 'expanded' => $expanded, + )); + + $form->submit($submissionData); + $this->assertFalse($form->isSynchronized()); + $this->assertEquals('All choices submitted must be NULL or strings.', $form->getTransformationFailure()->getMessage()); + } + + public function invalidNestedValueTestMatrix() + { + return array( + 'non-multiple, non-expanded' => array(false, false, array(array())), + 'non-multiple, expanded' => array(false, true, array(array())), + 'multiple, non-expanded' => array(true, false, array(array())), + 'multiple, expanded' => array(true, true, array(array())), + ); + } }