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 b684e7e

Browse filesBrowse files
committed
feature #59493 [Console] Invokable command adjustments (yceruto)
This PR was squashed before being merged into the 7.3 branch. Discussion ---------- [Console] Invokable command adjustments | Q | A | ------------- | --- | Branch? | 7.3 | Bug fix? | no | New feature? | no | Deprecations? | no | Issues | - | License | MIT Adjustments based on the latest reviews from #59340 Commits ------- 8ab2f32 [Console] Invokable command adjustments
2 parents 5cab1e1 + 8ab2f32 commit b684e7e
Copy full SHA for b684e7e

File tree

6 files changed

+198
-65
lines changed
Filter options

6 files changed

+198
-65
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
@@ -611,7 +611,7 @@ public function load(array $configs, ContainerBuilder $container): void
611611
$container->registerForAutoconfiguration(AssetCompilerInterface::class)
612612
->addTag('asset_mapper.compiler');
613613
$container->registerAttributeForAutoconfiguration(AsCommand::class, static function (ChildDefinition $definition, AsCommand $attribute, \ReflectionClass $reflector): void {
614-
$definition->addTag('console.command', ['command' => $attribute->name, 'description' => $attribute->description ?? $reflector->getName()]);
614+
$definition->addTag('console.command', ['command' => $attribute->name, 'description' => $attribute->description]);
615615
});
616616
$container->registerForAutoconfiguration(Command::class)
617617
->addTag('console.command');

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

Copy file name to clipboardExpand all lines: 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

Copy file name to clipboardExpand all lines: 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->default;
118128
}
119129
}

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

Copy file name to clipboardExpand all lines: 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

Copy file name to clipboardExpand all lines: 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)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.