8000 Invokable command adjustments · symfony/symfony@d740cfa · GitHub
[go: up one dir, main page]

Skip to content

Commit d740cfa

Browse files
committed
Invokable command adjustments
1 parent f6312d3 commit d740cfa

File tree

5 files changed

+91
-17
lines changed

5 files changed

+91
-17
lines changed

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -610,7 +610,7 @@ public function load(array $configs, ContainerBuilder $container): void
610610
$container->registerForAutoconfiguration(AssetCompilerInterface::class)
611611
->addTag('asset_mapper.compiler');
612612
$container->registerAttributeForAutoconfiguration(AsCommand::class, static function (ChildDefinition $definition, AsCommand $attribute, \ReflectionClass $reflector): void {
613-
$definition->addTag('console.command', ['command' => $attribute->name, 'description' => $attribute->description ?? $reflector->getName()]);
613+
$definition->addTag('console.command', ['command' => $attribute->name, 'description' => $attribute->description]);
614614
});
615615
$container->registerForAutoconfiguration(Command::class)
616616
->addTag('console.command');

src/Symfony/Component/Console/Attribute/Argument.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ class Argument
2929
*
3030
* If unset, the `name` and `default` values will be inferred from the parameter definition.
3131
*
32-
* @param string|bool|int|float|array|null $default The default value (for InputArgument::OPTIONAL mode only)
33-
* @param array|callable-string(CompletionInput):list<string|Suggestion> $suggestedValues The values used for input completion
32+
* @param string|bool|int|float|array|null $default The default value (for InputArgument::OPTIONAL mode only)
33+
* @param array<string|Suggestion>|callable-string(CompletionInput):list<string|Suggestion> $suggestedValues The values used for input completion
3434
*/
3535
public function __construct(
3636
public string $name = '',

src/Symfony/Component/Console/Attribute/Option.php

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ class Option
3030
*
3131
* If unset, the `name` and `default` values will be inferred from the parameter definition.
3232
*
33-
* @param array|string|null $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts
34-
* @param scalar|array|null $default The default value (must be null for self::VALUE_NONE)
35-
* @param array|callable-string(CompletionInput):list<string|Suggestion> $suggestedValues The values used for input completion
33+
* @param array|string|null $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts
34+
* @param scalar|array|null $default The default value (must be null for self::VALUE_NONE)
35+
* @param array<string|Suggestion>|callable-string(CompletionInput):list<string|Suggestion> $suggestedValues The values used for input completion
3636
*/
3737
public function __construct(
3838
public string $name = '',
@@ -76,17 +76,13 @@ public static function tryFrom(\ReflectionParameter $parameter): ?self
7676
if ('bool' === $self->typeName) {
7777
$self->mode = InputOption::VALUE_NONE | InputOption::VALUE_NEGATABLE;
7878
} else {
79-
$self->mode = null !== $self->default || $parameter->isDefaultValueAvailable() ? InputOption::VALUE_OPTIONAL : InputOption::VALUE_REQUIRED;
79+
$self->mode = $parameter->allowsNull() ? InputOption::VALUE_OPTIONAL : InputOption::VALUE_REQUIRED;
8080
if ('array' === $self->typeName) {
8181
$self->mode |= InputOption::VALUE_IS_ARRAY;
8282
}
8383
}
8484

85-
if (InputOption::VALUE_NONE === (InputOption::VALUE_NONE & $self->mode)) {
86-
$self->default = null;
87-
} else {
88-
$self->default ??= $parameter->isDefaultValueAvailable() ? $parameter->getDefaultValue() : null;
89-
}
85+
$self->default ??= $parameter->isDefaultValueAvailable() ? $parameter->getDefaultValue() : null;
9086

9187
if (\is_array($self->suggestedValues) && !\is_callable($self->suggestedValues) && 2 === \count($self->suggestedValues) && ($instance = $parameter->getDeclaringFunction()->getClosureThis()) && $instance::class === $self->suggestedValues[0] && \is_callable([$instance, $self->suggestedValues[1]])) {
9288
$self->suggestedValues = [$instance, $self->suggestedValues[1]];
@@ -100,9 +96,10 @@ public static function tryFrom(\ReflectionParameter $parameter): ?self
10096
*/
10197
public function toInputOption(): InputOption
10298
{
99+
$default = InputOption::VALUE_NONE === (InputOption::VALUE_NONE & $this->mode) ? null : $this->default;
103100
$suggestedValues = \is_callable($this->suggestedValues) ? ($this->suggestedValues)(...) : $this->suggestedValues;
104101

105-
return new InputOption($this->name, $this->shortcut, $this->mode, $this->description, $this->default, $suggestedValues);
102+
return new InputOption($this->name, $this->shortcut, $this->mode, $this->description, $default, $suggestedValues);
106103
}
107104

108105
/**

src/Symfony/Component/Console/Command/InvokableCommand.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public function __invoke(InputInterface $input, OutputInterface $output): int
4949

5050
if (null !== $statusCode && !\is_int($statusCode)) {
5151
if ($this->triggerDeprecations) {
52-
trigger_deprecation('symfony/console', '7.3', \sprintf('Returning a non-integer value from the command "%s" is deprecated and will throw an exception in PHP 8.0.', $this->command->getName()));
52+
trigger_deprecation('symfony/console', '7.3', \sprintf('Returning a non-integer value from the command "%s" is deprecated and will throw an exception in Symfony 8.0.', $this->command->getName()));
5353

5454
return 0;
5555
}
@@ F438 -97,7 +97,7 @@ private function getParameters(InputInterface $input, OutputInterface $output):
9797

9898
if (!$type instanceof \ReflectionNamedType) {
9999
if ($this->triggerDeprecations) {
100-
trigger_deprecation('symfony/console', '7.3', \sprintf('Omitting the type declaration for the parameter "$%s" is deprecated and will throw an exception in PHP 8.0.', $parameter->getName()));
100+
trigger_deprecation('symfony/console', '7.3', \sprintf('Omitting the type declaration for the parameter "$%s" is deprecated and will throw an exception in Symfony 8.0.', $parameter->getName()));
101101

102102
continue;
103103
}

src/Symfony/Component/Console/Tests/Command/InvokableCommandTest.php

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@
1818
use Symfony\Component\Console\Completion\CompletionInput;
1919
use Symfony\Component\Console\Completion\CompletionSuggestions;
2020
use Symfony\Component\Console\Completion\Suggestion;
21+
use Symfony\Component\Console\Exception\InvalidOptionException;
2122
use Symfony\Component\Console\Exception\LogicException;
23+
use Symfony\Component\Console\Input\ArrayInput;
24+
use Symfony\Component\Console\Output\NullOutput;
2225

2326
class InvokableCommandTest extends TestCase
2427
{
@@ -76,7 +79,7 @@ public function testCommandInputOptionDefinition()
7679

7780
$typeInputOption = $command->getDefinition()->getOption('type');
7881
self::assertSame('type', $typeInputOption->getName());
79-
self::assertFalse($typeInputOption->isValueRequired());
82+
self::assertTrue($typeInputOption->isValueRequired());
8083
self::assertSame('USER_TYPE', $typeInputOption->getDefault());
8184

8285
$verboseInputOption = $command->getDefinition()->getOption('verbose');
@@ -94,7 +97,7 @@ public function testCommandInputOptionDefinition()
9497

9598
$rolesInputOption = $command->getDefinition()->getOption('roles');
9699
self::assertSame('roles', $rolesInputOption->getName());
97-
self::assertFalse($rolesInputOption->isValueRequired());
100+
self::assertTrue($rolesInputOption->isValueRequired());
98101
self::assertTrue($rolesInputOption->isArray());
99102
self::assertSame(['ROLE_USER'], $rolesInputOption->getDefault());
100103
self::assertTrue($rolesInputOption->hasCompletion());
@@ -124,6 +127,80 @@ public function testInvalidOptionType()
124127
$command->getDefinition();
125128
}
126129

130+
/**
131+
* @dataProvider provideBinaryInputOptions
132+
*/
133+
public function testBinaryInputOptions(array $parameters, array $expected)
134+
{
135+
$command = new Command('foo');
136+
$command->setCode(function (
137+
#[Option] bool $a,
138+
#[Option] bool $b = true,
139+
#[Option] bool $c = false,
140+
) use ($expected) {
141+
$this->assertSame($expected[0], $a);
142+
$this->assertSame($expected[1], $b);
143+
$this->assertSame($expected[2], $c);
144+
});
145+
146+
$command->run(new ArrayInput($parameters), new NullOutput());
147+
}
148+
149+
public static function provideBinaryInputOptions(): \Generator
150+
{
151+
yield 'defaults' => [[], [false, true, false]];
152+
yield 'positive' => [['--a' => null, '--b' => null, '--c' => null], [true, true, true]];
153+
yield 'negative' => [['--no-a' => null, '--no-b' => null, '--no-c' => null], [false, false, false]];
154+
}
155+
156+
/**
157+
* @dataProvider provideNonBinaryInputOptions
158+
*/
159+
public function testNonBinaryInputOptions(array $parameters, array $expected)
160+
{
161+
$command = new Command('foo');
162+
$command->setCode(function (
163+
#[Option] ?string $a,
164+
#[Option] ?string $b = 'b',
165+
#[Option] ?array $c = [],
166+
) use ($expected) {
167+
$this->assertSame($expected[0], $a);
168+
$this->assertSame($expected[1], $b);
169+
$this->assertSame($expected[2], $c);
170+
});
171+
172+
$command->run(new ArrayInput($parameters), new NullOutput());
173+
}
174+
175+
public static function provideNonBinaryInputOptions(): \Generator
176+
{
177+
yield 'defaults' => [[], [null, 'b', []]];
178+
yield 'value-required' => [['--a' => 'x', '--b' => 'y', '--c' => ['z']], ['x', 'y', ['z']]];
179+
yield 'value-optional' => [['--a' => null, '--b' => null, '--c' => null], [null, null, null]];
180+
}
181+
182+
public function testInvalidRequiredValueOption()
183+
{
184+
$command = new Command('foo');
185+
$command->setCode(function (#[Option] string $a) {});
186+
187+
$this->expectException(InvalidOptionException::class);
188+
$this->expectExceptionMessage('The "--a" option requires a value.');
189+
190+
$command->run(new ArrayInput(['--a' => null]), new NullOutput());
191+
}
192+
193+
public function testInvalidRequiredValueOptionEvenWithDefault()
194+
{
195+
$command = new Command('foo');
196+
$command->setCode(function (#[Option] string $a = 'a') {});
197+
198+
$this->expectException(InvalidOptionException::class);
199+
$this->expectExceptionMessage('The "--a" option requires a value.');
200+
201+
$command->run(new ArrayInput(['--a' => null]), new NullOutput());
202+
}
203+
127204
public function getSuggestedRoles(CompletionInput $input): array
128205
{
129206
return ['ROLE_ADMIN', 'ROLE_USER'];

0 commit comments

Comments
 (0)
0