diff --git a/src/Symfony/Component/Form/Extension/Core/Type/CheckboxType.php b/src/Symfony/Component/Form/Extension/Core/Type/CheckboxType.php index 19ff62c154a04..b7226768642c7 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/CheckboxType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/CheckboxType.php @@ -33,6 +33,7 @@ public function buildForm(FormBuilderInterface $builder, array $options) // doing so also calls setDataLocked(true). $builder->setData(isset($options['data']) ? $options['data'] : false); $builder->addViewTransformer(new BooleanToStringTransformer($options['value'])); + $builder->setAttribute('_false_is_empty', true); // @internal - A boolean flag to treat false as empty, see Form::isEmpty() - Do not rely on it, it will be removed in Symfony 5.1. } /** diff --git a/src/Symfony/Component/Form/Form.php b/src/Symfony/Component/Form/Form.php index 67fd234fe9612..b25afe3a9e494 100644 --- a/src/Symfony/Component/Form/Form.php +++ b/src/Symfony/Component/Form/Form.php @@ -725,7 +725,9 @@ public function isEmpty() // arrays, countables ((\is_array($this->modelData) || $this->modelData instanceof \Countable) && 0 === \count($this->modelData)) || // traversables that are not countable - ($this->modelData instanceof \Traversable && 0 === iterator_count($this->modelData)); + ($this->modelData instanceof \Traversable && 0 === iterator_count($this->modelData)) || + // @internal - Do not rely on it, it will be removed in Symfony 5.1. + (false === $this->modelData && $this->config->getAttribute('_false_is_empty')); } /** 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 fb2d571884ad0..297b45a9d282d 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/CheckboxTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/CheckboxTypeTest.php @@ -190,4 +190,13 @@ public function testSubmitNullUsesDefaultEmptyData($emptyData = 'empty', $expect $this->assertSame($expectedData, $form->getNormData()); $this->assertSame($expectedData, $form->getData()); } + + public function testSubmitNullIsEmpty() + { + $form = $this->factory->create(static::TESTED_TYPE); + + $form->submit(null); + + $this->assertTrue($form->isEmpty()); + } } 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 b81ecb87671ab..9b2b0e64a576e 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php @@ -2046,4 +2046,45 @@ public function provideTrimCases() 'Multiple expanded' => [true, true], ]; } + + /** + * @dataProvider expandedIsEmptyWhenNoRealChoiceIsSelectedProvider + */ + public function testExpandedIsEmptyWhenNoRealChoiceIsSelected($expected, $submittedData, $multiple, $required, $placeholder) + { + $options = [ + 'expanded' => true, + 'choices' => [ + 'foo' => 'bar', + ], + 'multiple' => $multiple, + 'required' => $required, + ]; + + if (!$multiple) { + $options['placeholder'] = $placeholder; + } + + $form = $this->factory->create(static::TESTED_TYPE, null, $options); + + $form->submit($submittedData); + + $this->assertSame($expected, $form->isEmpty()); + } + + public function expandedIsEmptyWhenNoRealChoiceIsSelectedProvider() + { + // Some invalid cases are voluntarily not tested: + // - multiple with placeholder + // - required with placeholder + return [ + 'Nothing submitted / single / not required / without a placeholder -> should be empty' => [true, null, false, false, null], + 'Nothing submitted / single / not required / with a placeholder -> should not be empty' => [false, null, false, false, 'ccc'], // It falls back on the placeholder + 'Nothing submitted / single / required / without a placeholder -> should be empty' => [true, null, false, true, null], + 'Nothing submitted / single / required / with a placeholder -> should be empty' => [true, null, false, true, 'ccc'], + 'Nothing submitted / multiple / not required / without a placeholder -> should be empty' => [true, null, true, false, null], + 'Nothing submitted / multiple / required / without a placeholder -> should be empty' => [true, null, true, true, null], + 'Placeholder submitted / single / not required / with a placeholder -> should not be empty' => [false, '', false, false, 'ccc'], // The placeholder is a selected value + ]; + } }