From 41a5462f0d478c8668696762c0419d656825e303 Mon Sep 17 00:00:00 2001 From: Kevin Bond Date: Thu, 23 Jan 2025 09:53:28 -0500 Subject: [PATCH] [Console] `#[Option]` rules & restrictions --- .../Component/Console/Attribute/Option.php | 12 +++++ .../Tests/Command/InvokableCommandTest.php | 47 +++++++++++++++---- 2 files changed, 50 insertions(+), 9 deletions(-) diff --git a/src/Symfony/Component/Console/Attribute/Option.php b/src/Symfony/Component/Console/Attribute/Option.php index 099c7d0c23149..4aea4831e9ac6 100644 --- a/src/Symfony/Component/Console/Attribute/Option.php +++ b/src/Symfony/Component/Console/Attribute/Option.php @@ -80,6 +80,18 @@ public static function tryFrom(\ReflectionParameter $parameter): ?self $self->default = $parameter->getDefaultValue(); $self->allowNull = $parameter->allowsNull(); + if ('bool' === $self->typeName && $self->allowNull && \in_array($self->default, [true, false], true)) { + throw new LogicException(\sprintf('The option parameter "$%s" must not be nullable when it has a default boolean value.', $name)); + } + + if ('string' === $self->typeName && null === $self->default) { + throw new LogicException(\sprintf('The option parameter "$%s" must not have a default of null.', $name)); + } + + if ('array' === $self->typeName && $self->allowNull) { + throw new LogicException(\sprintf('The option parameter "$%s" must not be nullable.', $name)); + } + if ('bool' === $self->typeName) { $self->mode = InputOption::VALUE_NONE; if (false !== $self->default) { diff --git a/src/Symfony/Component/Console/Tests/Command/InvokableCommandTest.php b/src/Symfony/Component/Console/Tests/Command/InvokableCommandTest.php index d355c44ce5f9b..88f1b78701e0a 100644 --- a/src/Symfony/Component/Console/Tests/Command/InvokableCommandTest.php +++ b/src/Symfony/Component/Console/Tests/Command/InvokableCommandTest.php @@ -261,13 +261,15 @@ public function testNonBinaryInputOptions(array $parameters, array $expected) { $command = new Command('foo'); $command->setCode(function ( - #[Option] ?string $a = null, - #[Option] ?string $b = 'b', - #[Option] ?array $c = [], + #[Option] string $a = '', + #[Option] ?string $b = '', + #[Option] array $c = [], + #[Option] array $d = ['a', 'b'], ) use ($expected): int { $this->assertSame($expected[0], $a); $this->assertSame($expected[1], $b); $this->assertSame($expected[2], $c); + $this->assertSame($expected[3], $d); return 0; }); @@ -277,22 +279,49 @@ public function testNonBinaryInputOptions(array $parameters, array $expected) public static function provideNonBinaryInputOptions(): \Generator { - yield 'defaults' => [[], [null, 'b', []]]; - yield 'with-value' => [['--a' => 'x', '--b' => 'y', '--c' => ['z']], ['x', 'y', ['z']]]; - yield 'without-value' => [['--a' => null, '--b' => null, '--c' => null], [null, null, null]]; + yield 'defaults' => [[], ['', '', [], ['a', 'b']]]; + yield 'with-value' => [['--a' => 'x', '--b' => 'y', '--c' => ['z'], '--d' => ['c', 'd']], ['x', 'y', ['z'], ['c', 'd']]]; + yield 'without-value' => [['--b' => null], ['', null, [], ['a', 'b']]]; } - public function testInvalidOptionDefinition() + /** + * @dataProvider provideInvalidOptionDefinitions + */ + public function testInvalidOptionDefinition(callable $code, string $expectedMessage) { $command = new Command('foo'); - $command->setCode(function (#[Option] string $a) {}); + $command->setCode($code); $this->expectException(LogicException::class); - $this->expectExceptionMessage('The option parameter "$a" must declare a default value.'); + $this->expectExceptionMessage($expectedMessage); $command->getDefinition(); } + public static function provideInvalidOptionDefinitions(): \Generator + { + yield 'no-default' => [ + function (#[Option] string $a) {}, + 'The option parameter "$a" must declare a default value.', + ]; + yield 'nullable-bool-default-true' => [ + function (#[Option] ?bool $a = true) {}, + 'The option parameter "$a" must not be nullable when it has a default boolean value.', + ]; + yield 'nullable-bool-default-false' => [ + function (#[Option] ?bool $a = false) {}, + 'The option parameter "$a" must not be nullable when it has a default boolean value.', + ]; + yield 'nullable-string' => [ + function (#[Option] ?string $a = null) {}, + 'The option parameter "$a" must not have a default of null.', + ]; + yield 'nullable-array' => [ + function (#[Option] ?array $a = null) {}, + 'The option parameter "$a" must not be nullable.', + ]; + } + public function testInvalidRequiredValueOptionEvenWithDefault() { $command = new Command('foo');