Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 4e96f0a

Browse filesBrowse files
committed
Invokable command adjustments
1 parent f6312d3 commit 4e96f0a
Copy full SHA for 4e96f0a

File tree

5 files changed

+98
-19
lines changed
Filter options

5 files changed

+98
-19
lines changed

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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
+1-1
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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Console/Attribute/Argument.php
+2-2
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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Console/Attribute/Option.php
+11-10
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,18 +76,18 @@ 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
}
83-
}
8483

85-
if (InputOption::VALUE_NONE === (InputOption::VALUE_NONE & $self->mode)) {
86-
$self->default = null;
87-
} else {
88-
$self->default ??= $parameter->isDefaultValueAvailable() ? $parameter->getDefaultValue() : null;
84+
if (null === $self->default && !$parameter->allowsNull() && !$parameter->isDefaultValueAvailable()) {
85+
throw new LogicException(\sprintf('The parameter "$%s" must allow null or have a default value when it is not a boolean option.', $name));
86+
}
8987
}
9088

89+
$self->default ??= $parameter->isDefaultValueAvailable() ? $parameter->getDefaultValue() : null;
90+
9191
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]])) {
9292
$self->suggestedValues = [$instance, $self->suggestedValues[1]];
9393
}
@@ -100,9 +100,10 @@ public static function tryFrom(\ReflectionParameter $parameter): ?self
100100
*/
101101
public function toInputOption(): InputOption
102102
{
103+
$default = InputOption::VALUE_NONE === (InputOption::VALUE_NONE & $this->mode) ? null : $this->default;
103104
$suggestedValues = \is_callable($this->suggestedValues) ? ($this->suggestedValues)(...) : $this->suggestedValues;
104105

105-
return new InputOption($this->name, $this->shortcut, $this->mode, $this->description, $this->default, $suggestedValues);
106+
return new InputOption($this->name, $this->shortcut, $this->mode, $this->description, $default, $suggestedValues);
106107
}
107108

108109
/**

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Console/Command/InvokableCommand.php
+2-2
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
}
@@ -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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Console/Tests/Command/InvokableCommandTest.php
+82-4
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
{
@@ -61,7 +64,7 @@ public function testCommandInputOptionDefinition()
6164
{
6265
$command = new Command('foo');
6366
$command->setCode(function (
64-
#[Option(name: 'idle')] int $timeout,
67+
#[Option(name: 'idle')] ?int $timeout,
6568
#[Option(default: 'USER_TYPE')] string $type,
6669
#[Option(shortcut: 'v')] bool $verbose = false,
6770
#[Option(description: 'User groups')] array $groups = [],
@@ -71,12 +74,13 @@ public function testCommandInputOptionDefinition()
7174
$timeoutInputOption = $command->getDefinition()->getOption('idle');
7275
self::assertSame('idle', $timeoutInputOption->getName());
7376
self::assertNull($timeoutInputOption->getShortcut());
74-
self::assertTrue($timeoutInputOption->isValueRequired());
77+
self::assertFalse($timeoutInputOption->isValueRequired());
78+
self::assertTrue($timeoutInputOption->isValueOptional());
7579
self::assertNull($timeoutInputOption->getDefault());
7680

7781
$typeInputOption = $command->getDefinition()->getOption('type');
7882
self::assertSame('type', $typeInputOption->getName());
79-
self::assertFalse($typeInputOption->isValueRequired());
83+
self::assertTrue($typeInputOption->isValueRequired());
8084
self::assertSame('USER_TYPE', $typeInputOption->getDefault());
8185

8286
$verboseInputOption = $command->getDefinition()->getOption('verbose');
@@ -94,7 +98,7 @@ public function testCommandInputOptionDefinition()
9498

9599
$rolesInputOption = $command->getDefinition()->getOption('roles');
96100
self::assertSame('roles', $rolesInputOption->getName());
97-
self::assertFalse($rolesInputOption->isValueRequired());
101+
self::assertTrue($rolesInputOption->isValueRequired());
98102
self::assertTrue($rolesInputOption->isArray());
99103
self::assertSame(['ROLE_USER'], $rolesInputOption->getDefault());
100104
self::assertTrue($rolesInputOption->hasCompletion());
@@ -124,6 +128,80 @@ public function testInvalidOptionType()
124128
$command->getDefinition();
125129
}
126130

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

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.