diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/Descriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/Descriptor.php index cbb01dc6cf4b..fb89768bf5f3 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/Descriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/Descriptor.php @@ -94,24 +94,12 @@ protected function write(string $content, bool $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 = []); /** @@ -132,24 +120,12 @@ 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 = []); - /** - * Describes container environment variables. - */ abstract protected function describeContainerEnvVars(array $envs, array $options = []); /** @@ -305,9 +281,6 @@ protected function sortByPriority(array $tag): array return $tag; } - /** - * Gets class description from a docblock. - */ public static function getClassDescription(string $class, string &$resolvedClass = null): string { $resolvedClass = $class; diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php index 9d1e97d5f7f2..b9e5bad0f5c5 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php @@ -30,9 +30,6 @@ */ class JsonDescriptor extends Descriptor { - /** - * {@inheritdoc} - */ protected function describeRouteCollection(RouteCollection $routes, array $options = []) { $data = []; @@ -43,25 +40,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 = []) { $showHidden = isset($options['show_hidden']) && $options['show_hidden']; @@ -77,9 +65,6 @@ protected function describeContainerTags(ContainerBuilder $builder, array $optio $this->writeData($data, $options); } - /** - * {@inheritdoc} - */ protected function describeContainerService($service, array $options = [], ContainerBuilder $builder = null) { if (!isset($options['id'])) { @@ -95,9 +80,6 @@ protected function describeContainerService($service, array $options = [], Conta } } - /** - * {@inheritdoc} - */ protected function describeContainerServices(ContainerBuilder $builder, array $options = []) { $serviceIds = isset($options['tag']) && $options['tag'] @@ -131,17 +113,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) { @@ -156,25 +132,16 @@ protected function describeContainerAlias(Alias $alias, array $options = [], Con ); } - /** - * {@inheritdoc} - */ protected function describeEventDispatcherListeners(EventDispatcherInterface $eventDispatcher, array $options = []) { $this->writeData($this->getEventDispatcherListenersData($eventDispatcher, \array_key_exists('event', $options) ? $options['event'] : null), $options); } - /** - * {@inheritdoc} - */ 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'] : ''; @@ -182,17 +149,11 @@ protected function describeContainerParameter($parameter, array $options = []) $this->writeData([$key => $parameter], $options); } - /** - * {@inheritdoc} - */ protected function describeContainerEnvVars(array $envs, array $options = []) { throw new LogicException('Using the JSON format to debug environment variables is not supported.'); } - /** - * Writes data as json. - */ private function writeData(array $data, array $options) { $flags = isset($options['json_encoding']) ? $options['json_encoding'] : 0; diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php index ab4f0567b76a..a04402796b4c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php @@ -28,9 +28,6 @@ */ class MarkdownDescriptor extends Descriptor { - /** - * {@inheritdoc} - */ protected function describeRouteCollection(RouteCollection $routes, array $options = []) { $first = true; @@ -45,9 +42,6 @@ protected function describeRouteCollection(RouteCollection $routes, array $optio $this->write("\n"); } - /** - * {@inheritdoc} - */ protected function describeRoute(Route $route, array $options = []) { $output = '- Path: '.$route->getPath() @@ -71,9 +65,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"); @@ -82,9 +73,6 @@ protected function describeContainerParameters(ParameterBag $parameters, array $ } } - /** - * {@inheritdoc} - */ protected function describeContainerTags(ContainerBuilder $builder, array $options = []) { $showHidden = isset($options['show_hidden']) && $options['show_hidden']; @@ -99,9 +87,6 @@ protected function describeContainerTags(ContainerBuilder $builder, array $optio } } - /** - * {@inheritdoc} - */ protected function describeContainerService($service, array $options = [], ContainerBuilder $builder = null) { if (!isset($options['id'])) { @@ -119,9 +104,6 @@ protected function describeContainerService($service, array $options = [], Conta } } - /** - * {@inheritdoc} - */ protected function describeContainerServices(ContainerBuilder $builder, array $options = []) { $showHidden = isset($options['show_hidden']) && $options['show_hidden']; @@ -183,9 +165,6 @@ protected function describeContainerServices(ContainerBuilder $builder, array $o } } - /** - * {@inheritdoc} - */ protected function describeContainerDefinition(Definition $definition, array $options = []) { $output = ''; @@ -246,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.'`' @@ -270,25 +246,16 @@ 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); } - /** - * {@inheritdoc} - */ protected function describeContainerEnvVars(array $envs, array $options = []) { throw new LogicException('Using the markdown format to debug environment variables is not supported.'); } - /** - * {@inheritdoc} - */ protected function describeEventDispatcherListeners(EventDispatcherInterface $eventDispatcher, array $options = []) { $event = \array_key_exists('event', $options) ? $options['event'] : null; @@ -322,9 +289,6 @@ protected function describeEventDispatcherListeners(EventDispatcherInterface $ev } } - /** - * {@inheritdoc} - */ protected function describeCallable($callable, array $options = []) { $string = ''; diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php index 37e2117bc6cd..45c995a46737 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php @@ -43,9 +43,6 @@ public function __construct(FileLinkFormatter $fileLinkFormatter = null) $this->fileLinkFormatter = $fileLinkFormatter; } - /** - * {@inheritdoc} - */ protected function describeRouteCollection(RouteCollection $routes, array $options = []) { $showControllers = isset($options['show_controllers']) && $options['show_controllers']; @@ -83,9 +80,6 @@ protected function describeRouteCollection(RouteCollection $routes, array $optio } } - /** - * {@inheritdoc} - */ protected function describeRoute(Route $route, array $options = []) { $tableHeaders = ['Property', 'Value']; @@ -112,9 +106,6 @@ protected function describeRoute(Route $route, array $options = []) $table->render(); } - /** - * {@inheritdoc} - */ protected function describeContainerParameters(ParameterBag $parameters, array $options = []) { $tableHeaders = ['Parameter', 'Value']; @@ -128,9 +119,6 @@ protected function describeContainerParameters(ParameterBag $parameters, array $ $options['output']->table($tableHeaders, $tableRows); } - /** - * {@inheritdoc} - */ protected function describeContainerTags(ContainerBuilder $builder, array $options = []) { $showHidden = isset($options['show_hidden']) && $options['show_hidden']; @@ -147,9 +135,6 @@ protected function describeContainerTags(ContainerBuilder $builder, array $optio } } - /** - * {@inheritdoc} - */ protected function describeContainerService($service, array $options = [], ContainerBuilder $builder = null) { if (!isset($options['id'])) { @@ -171,9 +156,6 @@ protected function describeContainerService($service, array $options = [], Conta } } - /** - * {@inheritdoc} - */ protected function describeContainerServices(ContainerBuilder $builder, array $options = []) { $showHidden = isset($options['show_hidden']) && $options['show_hidden']; @@ -263,9 +245,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'])) { @@ -374,9 +353,6 @@ protected function describeContainerDefinition(Definition $definition, array $op $options['output']->table($tableHeaders, $tableRows); } - /** - * {@inheritdoc} - */ protected function describeContainerAlias(Alias $alias, array $options = [], ContainerBuilder $builder = null) { if ($alias->isPublic()) { @@ -392,9 +368,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( @@ -405,9 +378,6 @@ protected function describeContainerParameter($parameter, array $options = []) ]); } - /** - * {@inheritdoc} - */ protected function describeContainerEnvVars(array $envs, array $options = []) { $dump = new Dumper($this->output); @@ -470,9 +440,6 @@ protected function describeContainerEnvVars(array $envs, array $options = []) } } - /** - * {@inheritdoc} - */ protected function describeEventDispatcherListeners(EventDispatcherInterface $eventDispatcher, array $options = []) { $event = \array_key_exists('event', $options) ? $options['event'] : null; @@ -497,9 +464,6 @@ protected function describeEventDispatcherListeners(EventDispatcherInterface $ev } } - /** - * {@inheritdoc} - */ protected function describeCallable($callable, array $options = []) { $this->writeText($this->formatCallable($callable), $options); diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php index 16e79f45f4cb..a7f348e76d21 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php @@ -31,41 +31,26 @@ */ 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_hidden']) && $options['show_hidden'])); } - /** - * {@inheritdoc} - */ protected function describeContainerService($service, array $options = [], ContainerBuilder $builder = null) { if (!isset($options['id'])) { @@ -75,25 +60,16 @@ protected function describeContainerService($service, array $options = [], Conta $this->writeDocument($this->getContainerServiceDocument($service, $options['id'], $builder, isset($options['show_arguments']) && $options['show_arguments'])); } - /** - * {@inheritdoc} - */ protected function describeContainerServices(ContainerBuilder $builder, array $options = []) { $this->writeDocument($this->getContainerServicesDocument($builder, isset($options['tag']) ? $options['tag'] : null, isset($options['show_hidden']) && $options['show_hidden'], 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'); @@ -110,41 +86,26 @@ protected function describeContainerAlias(Alias $alias, array $options = [], Con $this->writeDocument($dom); } - /** - * {@inheritdoc} - */ protected function describeEventDispatcherListeners(EventDispatcherInterface $eventDispatcher, array $options = []) { $this->writeDocument($this->getEventDispatcherListenersDocument($eventDispatcher, \array_key_exists('event', $options) ? $options['event'] : null)); } - /** - * {@inheritdoc} - */ protected function describeCallable($callable, array $options = []) { $this->writeDocument($this->getCallableDocument($callable)); } - /** - * {@inheritdoc} - */ protected function describeContainerParameter($parameter, array $options = []) { $this->writeDocument($this->getContainerParameterDocument($parameter, $options)); } - /** - * {@inheritdoc} - */ protected function describeContainerEnvVars(array $envs, array $options = []) { throw new LogicException('Using the XML format to debug environment variables is not supported.'); } - /** - * Writes DOM document. - */ private function writeDocument(\DOMDocument $dom) { $dom->formatOutput = true; diff --git a/src/Symfony/Component/Form/Extension/Core/Type/CheckboxType.php b/src/Symfony/Component/Form/Extension/Core/Type/CheckboxType.php index 2b29c5ad9bf1..b1605b50dce8 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'], $options['false_values'])); + $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 c4f0413dd350..23e21eacd2b4 100644 --- a/src/Symfony/Component/Form/Form.php +++ b/src/Symfony/Component/Form/Form.php @@ -731,7 +731,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 265e5e2b09a5..8391805b2266 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/CheckboxTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/CheckboxTypeTest.php @@ -220,4 +220,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 f417c234af4c..e82ab917c590 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php @@ -2049,4 +2049,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 + ]; + } }