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

Skip to content

Commit 8ab2f32

Browse files
ycerutochalasr
authored andcommitted
[Console] Invokable command adjustments
1 parent f6312d3 commit 8ab2f32

File tree

6 files changed

+198
-65
lines changed

6 files changed

+198
-65
lines changed

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

+10-12
Original file line numberDiff line numberDiff line change
@@ -22,25 +22,23 @@ class Argument
2222
{
2323
private const ALLOWED_TYPES = ['string', 'bool', 'int', 'float', 'array'];
2424

25+
private string|bool|int|float|array|null $default = null;
26+
private array|\Closure $suggestedValues;
2527
private ?int $mode = null;
2628

2729
/**
2830
* Represents a console command <argument> definition.
2931
*
30-
* If unset, the `name` and `default` values will be inferred from the parameter definition.
32+
* If unset, the `name` value will be inferred from the parameter definition.
3133
*
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
34+
* @param array<string|Suggestion>|callable(CompletionInput):list<string|Suggestion> $suggestedValues The values used for input completion
3435
*/
3536
public function __construct(
3637
public string $name = '',
3738
public string $description = '',
38-
public string|bool|int|float|array|null $default = null,
39-
public array|string $suggestedValues = [],
39+
array|callable $suggestedValues = [],
4040
) {
41-
if (\is_string($suggestedValues) && !\is_callable($suggestedValues)) {
42-
throw new \TypeError(\sprintf('Argument 4 passed to "%s()" must be either an array or a callable-string.', __METHOD__));
43-
}
41+
$this->suggestedValues = \is_callable($suggestedValues) ? $suggestedValues(...) : $suggestedValues;
4442
}
4543

4644
/**
@@ -70,13 +68,13 @@ public static function tryFrom(\ReflectionParameter $parameter): ?self
7068
$self->name = $name;
7169
}
7270

73-
$self->mode = null !== $self->default || $parameter->isDefaultValueAvailable() ? InputArgument::OPTIONAL : InputArgument::REQUIRED;
71+
$self->default = $parameter->isDefaultValueAvailable() ? $parameter->getDefaultValue() : null;
72+
73+
$self->mode = $parameter->isDefaultValueAvailable() || $parameter->allowsNull() ? InputArgument::OPTIONAL : InputArgument::REQUIRED;
7474
if ('array' === $parameterTypeName) {
7575
$self->mode |= InputArgument::IS_ARRAY;
7676
}
7777

78-
$self->default ??= $parameter->isDefaultValueAvailable() ? $parameter->getDefaultValue() : null;
79-
8078
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]])) {
8179
$self->suggestedValues = [$instance, $self->suggestedValues[1]];
8280
}
@@ -99,6 +97,6 @@ public function toInputArgument(): InputArgument
9997
*/
10098
public function resolveValue(InputInterface $input): mixed
10199
{
102-
return $input->hasArgument($this->name) ? $input->getArgument($this->name) : null;
100+
return $input->getArgument($this->name);
103101
}
104102
}

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

+31-21
Original file line numberDiff line numberDiff line change
@@ -22,28 +22,27 @@ class Option
2222
{
2323
private const ALLOWED_TYPES = ['string', 'bool', 'int', 'float', 'array'];
2424

25+
private string|bool|int|float|array|null $default = null;
26+
private array|\Closure $suggestedValues;
2527
private ?int $mode = null;
2628
private string $typeName = '';
29+
private bool $allowNull = false;
2730

2831
/**
2932
* Represents a console command --option definition.
3033
*
31-
* If unset, the `name` and `default` values will be inferred from the parameter definition.
34+
* If unset, the `name` value will be inferred from the parameter definition.
3235
*
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
36+
* @param array|string|null $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts
37+
* @param array<string|Suggestion>|callable(CompletionInput):list<string|Suggestion> $suggestedValues The values used for input completion
3638
*/
3739
public function __construct(
3840
public string $name = '',
3941
public array|string|null $shortcut = null,
4042
public string $description = '',
41-
public string|bool|int|float|array|null $default = null,
42-
public array|string $suggestedValues = [],
43+
array|callable $suggestedValues = [],
4344
) {
44-
if (\is_string($suggestedValues) && !\is_callable($suggestedValues)) {
45-
throw new \TypeError(\sprintf('Argument 5 passed to "%s()" must be either an array or a callable-string.', __METHOD__));
46-
}
45+
$this->suggestedValues = \is_callable($suggestedValues) ? $suggestedValues(...) : $suggestedValues;
4746
}
4847

4948
/**
@@ -69,25 +68,29 @@ public static function tryFrom(\ReflectionParameter $parameter): ?self
6968
throw new LogicException(\sprintf('The type "%s" of parameter "$%s" is not supported as a command option. Only "%s" types are allowed.', $self->typeName, $name, implode('", "', self::ALLOWED_TYPES)));
7069
}
7170

71+
if (!$parameter->isDefaultValueAvailable()) {
72+
throw new LogicException(\sprintf('The option parameter "$%s" must declare a default value.', $name));
73+
}
74+
7275
if (!$self->name) {
7376
$self->name = $name;
7477
}
7578

79+
$self->default = $parameter->getDefaultValue();
80+
$self->allowNull = $parameter->allowsNull();
81+
7682
if ('bool' === $self->typeName) {
77-
$self->mode = InputOption::VALUE_NONE | InputOption::VALUE_NEGATABLE;
83+
$self->mode = InputOption::VALUE_NONE;
84+
if (false !== $self->default) {
85+
$self->mode |= InputOption::VALUE_NEGATABLE;
86+
}
7887
} else {
79-
$self->mode = null !== $self->default || $parameter->isDefaultValueAvailable() ? InputOption::VALUE_OPTIONAL : InputOption::VALUE_REQUIRED;
88+
$self->mode = $self->allowNull ? InputOption::VALUE_OPTIONAL : InputOption::VALUE_REQUIRED;
8089
if ('array' === $self->typeName) {
8190
$self->mode |= InputOption::VALUE_IS_ARRAY;
8291
}
8392
}
8493

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-
}
90-
9194
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]])) {
9295
$self->suggestedValues = [$instance, $self->suggestedValues[1]];
9396
}
@@ -100,20 +103,27 @@ public static function tryFrom(\ReflectionParameter $parameter): ?self
100103
*/
101104
public function toInputOption(): InputOption
102105
{
106+
$default = InputOption::VALUE_NONE === (InputOption::VALUE_NONE & $this->mode) ? null : $this->default;
103107
$suggestedValues = \is_callable($this->suggestedValues) ? ($this->suggestedValues)(...) : $this->suggestedValues;
104108

105-
return new InputOption($this->name, $this->shortcut, $this->mode, $this->description, $this->default, $suggestedValues);
109+
return new InputOption($this->name, $this->shortcut, $this->mode, $this->description, $default, $suggestedValues);
106110
}
107111

108112
/**
109113
* @internal
110114
*/
111115
public function resolveValue(InputInterface $input): mixed
112116
{
113-
if ('bool' === $this->typeName) {
114-
return $input->hasOption($this->name) && null !== $input->getOption($this->name) ? $input->getOption($this->name) : ($this->default ?? false);
117+
$value = $input->getOption($this->name);
118+
119+
if ('bool' !== $this->typeName) {
120+
return $value;
121+
}
122+
123+
if ($this->allowNull && null === $value) {
124+
return null;
115125
}
116126

117-
return $input->hasOption($this->name) ? $input->getOption($this->name) : null;
127+
return $value ?? $this F438 ->default;
118128
}
119129
}

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

+4-19
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,10 @@ public function __construct(?string $name = null)
100100
$this->setDescription(static::getDefaultDescription() ?? '');
101101
}
102102

103+
if (\is_callable($this)) {
104+
$this->code = new InvokableCommand($this, $this(...));
105+
}
106+
103107
$this->configure();
104108
}
105109

@@ -164,9 +168,6 @@ public function isEnabled(): bool
164168
*/
165169
protected function configure()
166170
{
167-
if (!$this->code && \is_callable($this)) {
168-
$this->code = new InvokableCommand($this, $this(...));
169-
}
170171
}
171172

172173
/**
@@ -312,22 +313,6 @@ public function complete(CompletionInput $input, CompletionSuggestions $suggesti
312313
*/
313314
public function setCode(callable $code): static
314315
{
315-
if ($code instanceof \Closure) {
316-
$r = new \ReflectionFunction($code);
317-
if (null === $r->getClosureThis()) {
318-
set_error_handler(static function () {});
319-
try {
320-
if ($c = \Closure::bind($code, $this)) {
321-
$code = $c;
322-
}
323-
} finally {
324-
restore_error_handler();
325-
}
326-
}
327-
} else {
328-
$code = $code(...);
329-
}
330-
331316
$this->code = new InvokableCommand($this, $code, triggerDeprecations: true);
332317

333318
return $this;

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

+28-4
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,16 @@
3030
*/
3131
class InvokableCommand
3232
{
33+
private readonly \Closure $code;
3334
private readonly \ReflectionFunction $reflection;
3435

3536
public function __construct(
3637
private readonly Command $command,
37-
private readonly \Closure $code,
38+
callable $code,
3839
private readonly bool $triggerDeprecations = false,
3940
) {
40-
$this->reflection = new \ReflectionFunction($code);
41+
$this->code = $this->getClosure($code);
42+
$this->reflection = new \ReflectionFunction($this->code);
4143
}
4244

4345
/**
@@ -49,7 +51,7 @@ public function __invoke(InputInterface $input, OutputInterface $output): int
4951

5052
if (null !== $statusCode && !\is_int($statusCode)) {
5153
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()));
54+
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()));
5355

5456
return 0;
5557
}
@@ -77,6 +79,28 @@ public function configure(InputDefinition $definition): void
7779
}
7880
}
7981

82+
private function getClosure(callable $code): \Closure
83+
{
84+
if (!$code instanceof \Closure) {
85+
return $code(...);
86+
}
87+
88+
if (null !== (new \ReflectionFunction($code))->getClosureThis()) {
89+
return $code;
90+
}
91+
92+
set_error_handler(static function () {});
93+
try {
94+
if ($c = \Closure::bind($code, $this->command)) {
95+
$code = $c;
96+
}
97+
} finally {
98+
restore_error_handler();
99+
}
100+
101+
return $code;
102+
}
103+
80104
private function getParameters(InputInterface $input, OutputInterface $output): array
81105
{
82106
$parameters = [];
@@ -97,7 +121,7 @@ private function getParameters(InputInterface $input, OutputInterface $output):
97121

98122
if (!$type instanceof \ReflectionNamedType) {
99123
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()));
124+
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()));
101125

102126
continue;
103127
}

0 commit comments

Comments
 (0)
0