From 1a366bc378762cff1068bff12385021751cd1dc7 Mon Sep 17 00:00:00 2001 From: Thomas Calvet Date: Tue, 3 Mar 2020 15:10:33 +0100 Subject: [PATCH 1/2] [Form] Handle false as empty value on expanded choices --- .../Form/Extension/Core/Type/CheckboxType.php | 1 + src/Symfony/Component/Form/Form.php | 4 +- .../Extension/Core/Type/CheckboxTypeTest.php | 9 ++++ .../Extension/Core/Type/ChoiceTypeTest.php | 41 +++++++++++++++++++ 4 files changed, 54 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/Form/Extension/Core/Type/CheckboxType.php b/src/Symfony/Component/Form/Extension/Core/Type/CheckboxType.php index 19ff62c154a0..b7226768642c 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 67fd234fe961..b25afe3a9e49 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 fb2d571884ad..297b45a9d282 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 b81ecb87671a..9b2b0e64a576 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 + ]; + } } From e535e7d2ff922f1f010a96f5b43570778790e726 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=A9mi=20Sala=C3=BCn?= Date: Mon, 9 Mar 2020 19:12:25 +0100 Subject: [PATCH 2/2] [FrameworkBundle] remove redundant PHPDoc in console Descriptor and subclass The PHPDoc for some describeXXX methods in the abstract Descriptor was inacurate or redundant. --- .../Console/Descriptor/Descriptor.php | 21 ------------------- .../Console/Descriptor/JsonDescriptor.php | 21 ------------------- .../Console/Descriptor/MarkdownDescriptor.php | 21 ------------------- .../Console/Descriptor/TextDescriptor.php | 21 ------------------- .../Console/Descriptor/XmlDescriptor.php | 21 ------------------- 5 files changed, 105 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/Descriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/Descriptor.php index 23d9ccc777bd..a28ca356d88b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/Descriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/Descriptor.php @@ -100,24 +100,12 @@ protected function write($content, $decorated = false) $this->output->write($content, false, $decorated ? OutputInterface::OUTPUT_NORMAL : OutputInterface::OUTPUT_RAW); } - /** - * Describes an InputArgument instance. - */ abstract protected function describeRouteCollection(RouteCollection $routes, array $options = []); - /** - * Describes an InputOption instance. - */ abstract protected function describeRoute(Route $route, array $options = []); - /** - * Describes container parameters. - */ abstract protected function describeContainerParameters(ParameterBag $parameters, array $options = []); - /** - * Describes container tags. - */ abstract protected function describeContainerTags(ContainerBuilder $builder, array $options = []); /** @@ -138,19 +126,10 @@ abstract protected function describeContainerService($service, array $options = */ abstract protected function describeContainerServices(ContainerBuilder $builder, array $options = []); - /** - * Describes a service definition. - */ abstract protected function describeContainerDefinition(Definition $definition, array $options = []); - /** - * Describes a service alias. - */ abstract protected function describeContainerAlias(Alias $alias, array $options = [], ContainerBuilder $builder = null); - /** - * Describes a container parameter. - */ abstract protected function describeContainerParameter($parameter, array $options = []); /** diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php index 197db657f001..74d9b0c61a05 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php @@ -29,9 +29,6 @@ */ class JsonDescriptor extends Descriptor { - /** - * {@inheritdoc} - */ protected function describeRouteCollection(RouteCollection $routes, array $options = []) { $data = []; @@ -42,25 +39,16 @@ protected function describeRouteCollection(RouteCollection $routes, array $optio $this->writeData($data, $options); } - /** - * {@inheritdoc} - */ protected function describeRoute(Route $route, array $options = []) { $this->writeData($this->getRouteData($route), $options); } - /** - * {@inheritdoc} - */ protected function describeContainerParameters(ParameterBag $parameters, array $options = []) { $this->writeData($this->sortParameters($parameters), $options); } - /** - * {@inheritdoc} - */ protected function describeContainerTags(ContainerBuilder $builder, array $options = []) { $showPrivate = isset($options['show_private']) && $options['show_private']; @@ -128,17 +116,11 @@ protected function describeContainerServices(ContainerBuilder $builder, array $o $this->writeData($data, $options); } - /** - * {@inheritdoc} - */ protected function describeContainerDefinition(Definition $definition, array $options = []) { $this->writeData($this->getContainerDefinitionData($definition, isset($options['omit_tags']) && $options['omit_tags'], isset($options['show_arguments']) && $options['show_arguments']), $options); } - /** - * {@inheritdoc} - */ protected function describeContainerAlias(Alias $alias, array $options = [], ContainerBuilder $builder = null) { if (!$builder) { @@ -169,9 +151,6 @@ protected function describeCallable($callable, array $options = []) $this->writeData($this->getCallableData($callable), $options); } - /** - * {@inheritdoc} - */ protected function describeContainerParameter($parameter, array $options = []) { $key = isset($options['parameter']) ? $options['parameter'] : ''; diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php index 2c31bdc19d61..5ebaed024895 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php @@ -27,9 +27,6 @@ */ class MarkdownDescriptor extends Descriptor { - /** - * {@inheritdoc} - */ protected function describeRouteCollection(RouteCollection $routes, array $options = []) { $first = true; @@ -44,9 +41,6 @@ protected function describeRouteCollection(RouteCollection $routes, array $optio $this->write("\n"); } - /** - * {@inheritdoc} - */ protected function describeRoute(Route $route, array $options = []) { $output = '- Path: '.$route->getPath() @@ -66,9 +60,6 @@ protected function describeRoute(Route $route, array $options = []) $this->write("\n"); } - /** - * {@inheritdoc} - */ protected function describeContainerParameters(ParameterBag $parameters, array $options = []) { $this->write("Container parameters\n====================\n"); @@ -77,9 +68,6 @@ protected function describeContainerParameters(ParameterBag $parameters, array $ } } - /** - * {@inheritdoc} - */ protected function describeContainerTags(ContainerBuilder $builder, array $options = []) { $showPrivate = isset($options['show_private']) && $options['show_private']; @@ -176,9 +164,6 @@ protected function describeContainerServices(ContainerBuilder $builder, array $o } } - /** - * {@inheritdoc} - */ protected function describeContainerDefinition(Definition $definition, array $options = []) { $output = '- Class: `'.$definition->getClass().'`' @@ -240,9 +225,6 @@ protected function describeContainerDefinition(Definition $definition, array $op $this->write(isset($options['id']) ? sprintf("### %s\n\n%s\n", $options['id'], $output) : $output); } - /** - * {@inheritdoc} - */ protected function describeContainerAlias(Alias $alias, array $options = [], ContainerBuilder $builder = null) { $output = '- Service: `'.$alias.'`' @@ -264,9 +246,6 @@ protected function describeContainerAlias(Alias $alias, array $options = [], Con $this->describeContainerDefinition($builder->getDefinition((string) $alias), array_merge($options, ['id' => (string) $alias])); } - /** - * {@inheritdoc} - */ protected function describeContainerParameter($parameter, array $options = []) { $this->write(isset($options['parameter']) ? sprintf("%s\n%s\n\n%s", $options['parameter'], str_repeat('=', \strlen($options['parameter'])), $this->formatParameter($parameter)) : $parameter); diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php index f17a1bba2121..a32a2128e2c8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php @@ -33,9 +33,6 @@ */ class TextDescriptor extends Descriptor { - /** - * {@inheritdoc} - */ protected function describeRouteCollection(RouteCollection $routes, array $options = []) { $showControllers = isset($options['show_controllers']) && $options['show_controllers']; @@ -72,9 +69,6 @@ protected function describeRouteCollection(RouteCollection $routes, array $optio } } - /** - * {@inheritdoc} - */ protected function describeRoute(Route $route, array $options = []) { $tableHeaders = ['Property', 'Value']; @@ -100,9 +94,6 @@ protected function describeRoute(Route $route, array $options = []) $table->render(); } - /** - * {@inheritdoc} - */ protected function describeContainerParameters(ParameterBag $parameters, array $options = []) { $tableHeaders = ['Parameter', 'Value']; @@ -116,9 +107,6 @@ protected function describeContainerParameters(ParameterBag $parameters, array $ $options['output']->table($tableHeaders, $tableRows); } - /** - * {@inheritdoc} - */ protected function describeContainerTags(ContainerBuilder $builder, array $options = []) { $showPrivate = isset($options['show_private']) && $options['show_private']; @@ -252,9 +240,6 @@ protected function describeContainerServices(ContainerBuilder $builder, array $o $options['output']->table($tableHeaders, $tableRows); } - /** - * {@inheritdoc} - */ protected function describeContainerDefinition(Definition $definition, array $options = []) { if (isset($options['id'])) { @@ -358,9 +343,6 @@ protected function describeContainerDefinition(Definition $definition, array $op $options['output']->table($tableHeaders, $tableRows); } - /** - * {@inheritdoc} - */ protected function describeContainerAlias(Alias $alias, array $options = [], ContainerBuilder $builder = null) { $options['output']->comment(sprintf('This service is an alias for the service %s', (string) $alias)); @@ -372,9 +354,6 @@ protected function describeContainerAlias(Alias $alias, array $options = [], Con $this->describeContainerDefinition($builder->getDefinition((string) $alias), array_merge($options, ['id' => (string) $alias])); } - /** - * {@inheritdoc} - */ protected function describeContainerParameter($parameter, array $options = []) { $options['output']->table( diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php index 96b9a261ae3a..996ac11c7b3c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php @@ -29,33 +29,21 @@ */ class XmlDescriptor extends Descriptor { - /** - * {@inheritdoc} - */ protected function describeRouteCollection(RouteCollection $routes, array $options = []) { $this->writeDocument($this->getRouteCollectionDocument($routes)); } - /** - * {@inheritdoc} - */ protected function describeRoute(Route $route, array $options = []) { $this->writeDocument($this->getRouteDocument($route, isset($options['name']) ? $options['name'] : null)); } - /** - * {@inheritdoc} - */ protected function describeContainerParameters(ParameterBag $parameters, array $options = []) { $this->writeDocument($this->getContainerParametersDocument($parameters)); } - /** - * {@inheritdoc} - */ protected function describeContainerTags(ContainerBuilder $builder, array $options = []) { $this->writeDocument($this->getContainerTagsDocument($builder, isset($options['show_private']) && $options['show_private'])); @@ -81,17 +69,11 @@ protected function describeContainerServices(ContainerBuilder $builder, array $o $this->writeDocument($this->getContainerServicesDocument($builder, isset($options['tag']) ? $options['tag'] : null, isset($options['show_private']) && $options['show_private'], isset($options['show_arguments']) && $options['show_arguments'], isset($options['filter']) ? $options['filter'] : null)); } - /** - * {@inheritdoc} - */ protected function describeContainerDefinition(Definition $definition, array $options = []) { $this->writeDocument($this->getContainerDefinitionDocument($definition, isset($options['id']) ? $options['id'] : null, isset($options['omit_tags']) && $options['omit_tags'], isset($options['show_arguments']) && $options['show_arguments'])); } - /** - * {@inheritdoc} - */ protected function describeContainerAlias(Alias $alias, array $options = [], ContainerBuilder $builder = null) { $dom = new \DOMDocument('1.0', 'UTF-8'); @@ -124,9 +106,6 @@ protected function describeCallable($callable, array $options = []) $this->writeDocument($this->getCallableDocument($callable)); } - /** - * {@inheritdoc} - */ protected function describeContainerParameter($parameter, array $options = []) { $this->writeDocument($this->getContainerParameterDocument($parameter, $options));