Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

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

Sign up
Appearance settings

[Console] Invokable command adjustments#59493

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
chalasr merged 1 commit intosymfony:7.3fromyceruto:invokable_command_tweaks
Jan 20, 2025
Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -610,7 +610,7 @@ public function load(array $configs, ContainerBuilder $container): void
$container->registerForAutoconfiguration(AssetCompilerInterface::class)
->addTag('asset_mapper.compiler');
$container->registerAttributeForAutoconfiguration(AsCommand::class, static function (ChildDefinition $definition, AsCommand $attribute, \ReflectionClass $reflector): void {
$definition->addTag('console.command', ['command' => $attribute->name, 'description' => $attribute->description ?? $reflector->getName()]);
$definition->addTag('console.command', ['command' => $attribute->name, 'description' => $attribute->description]);
});
$container->registerForAutoconfiguration(Command::class)
->addTag('console.command');
Expand Down
22 changes: 10 additions & 12 deletionssrc/Symfony/Component/Console/Attribute/Argument.php
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,25 +22,23 @@ class Argument
{
private const ALLOWED_TYPES = ['string', 'bool', 'int', 'float', 'array'];

private string|bool|int|float|array|null $default = null;
private array|\Closure $suggestedValues;
private ?int $mode = null;

/**
* Represents a console command <argument> definition.
*
* If unset, the `name`and `default` values will be inferred from the parameter definition.
* If unset, the `name`value will be inferred from the parameter definition.
*
* @param string|bool|int|float|array|null $default The default value (for InputArgument::OPTIONAL mode only)
* @param array|callable-string(CompletionInput):list<string|Suggestion> $suggestedValues The values used for input completion
* @param array<string|Suggestion>|callable(CompletionInput):list<string|Suggestion> $suggestedValues The values used for input completion
*/
public function __construct(
public string $name = '',
public string $description = '',
public string|bool|int|float|array|null $default = null,
public array|string $suggestedValues = [],
array|callable $suggestedValues = [],
) {
if (\is_string($suggestedValues) && !\is_callable($suggestedValues)) {
throw new \TypeError(\sprintf('Argument 4 passed to "%s()" must be either an array or a callable-string.', __METHOD__));
}
$this->suggestedValues = \is_callable($suggestedValues) ? $suggestedValues(...) : $suggestedValues;
}

/**
Expand DownExpand Up@@ -70,13 +68,13 @@ public static function tryFrom(\ReflectionParameter $parameter): ?self
$self->name = $name;
}

$self->mode = null !== $self->default || $parameter->isDefaultValueAvailable() ? InputArgument::OPTIONAL : InputArgument::REQUIRED;
$self->default = $parameter->isDefaultValueAvailable() ? $parameter->getDefaultValue() : null;

$self->mode = $parameter->isDefaultValueAvailable() || $parameter->allowsNull() ? InputArgument::OPTIONAL : InputArgument::REQUIRED;
if ('array' === $parameterTypeName) {
$self->mode |= InputArgument::IS_ARRAY;
}

$self->default ??= $parameter->isDefaultValueAvailable() ? $parameter->getDefaultValue() : null;

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]])) {
$self->suggestedValues = [$instance, $self->suggestedValues[1]];
}
Expand All@@ -99,6 +97,6 @@ public function toInputArgument(): InputArgument
*/
public function resolveValue(InputInterface $input): mixed
{
return $input->hasArgument($this->name) ? $input->getArgument($this->name) : null;
return $input->getArgument($this->name);
Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

removed$input->hasArgument($this->name) as it's better to get the "argument does not exist" exception than a TypeError or just null (if nullable)

chalasr reacted with thumbs up emoji
}
}
52 changes: 31 additions & 21 deletionssrc/Symfony/Component/Console/Attribute/Option.php
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,28 +22,27 @@ class Option
{
private const ALLOWED_TYPES = ['string', 'bool', 'int', 'float', 'array'];

private string|bool|int|float|array|null $default = null;
private array|\Closure $suggestedValues;
private ?int $mode = null;
private string $typeName = '';
private bool $allowNull = false;

/**
* Represents a console command --option definition.
*
* If unset, the `name`and `default` values will be inferred from the parameter definition.
* If unset, the `name`value will be inferred from the parameter definition.
*
* @param array|string|null $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts
* @param scalar|array|null $default The default value (must be null for self::VALUE_NONE)
* @param array|callable-string(CompletionInput):list<string|Suggestion> $suggestedValues The values used for input completion
* @param array|string|null $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts
* @param array<string|Suggestion>|callable(CompletionInput):list<string|Suggestion> $suggestedValues The values used for input completion
*/
public function __construct(
public string $name = '',
public array|string|null $shortcut = null,
public string $description = '',
public string|bool|int|float|array|null $default = null,
public array|string $suggestedValues = [],
array|callable $suggestedValues = [],
) {
if (\is_string($suggestedValues) && !\is_callable($suggestedValues)) {
throw new \TypeError(\sprintf('Argument 5 passed to "%s()" must be either an array or a callable-string.', __METHOD__));
}
$this->suggestedValues = \is_callable($suggestedValues) ? $suggestedValues(...) : $suggestedValues;
}

/**
Expand All@@ -69,25 +68,29 @@ public static function tryFrom(\ReflectionParameter $parameter): ?self
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)));
}

if (!$parameter->isDefaultValueAvailable()) {
throw new LogicException(\sprintf('The option parameter "$%s" must declare a default value.', $name));
}

if (!$self->name) {
$self->name = $name;
}

$self->default = $parameter->getDefaultValue();
$self->allowNull = $parameter->allowsNull();

if ('bool' === $self->typeName) {
$self->mode = InputOption::VALUE_NONE | InputOption::VALUE_NEGATABLE;
$self->mode = InputOption::VALUE_NONE;
if (false !== $self->default) {
$self->mode |= InputOption::VALUE_NEGATABLE;
}
} else {
$self->mode =null !==$self->default || $parameter->isDefaultValueAvailable() ? InputOption::VALUE_OPTIONAL : InputOption::VALUE_REQUIRED;
$self->mode = $self->allowNull ? InputOption::VALUE_OPTIONAL : InputOption::VALUE_REQUIRED;
if ('array' === $self->typeName) {
$self->mode |= InputOption::VALUE_IS_ARRAY;
}
}

if (InputOption::VALUE_NONE === (InputOption::VALUE_NONE & $self->mode)) {
$self->default = null;
} else {
$self->default ??= $parameter->isDefaultValueAvailable() ? $parameter->getDefaultValue() : null;
}

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

return new InputOption($this->name, $this->shortcut, $this->mode, $this->description, $this->default, $suggestedValues);
return new InputOption($this->name, $this->shortcut, $this->mode, $this->description, $default, $suggestedValues);
}

/**
* @internal
*/
public function resolveValue(InputInterface $input): mixed
{
if ('bool' === $this->typeName) {
return $input->hasOption($this->name) && null !== $input->getOption($this->name) ? $input->getOption($this->name) : ($this->default ?? false);
$value = $input->getOption($this->name);

if ('bool' !== $this->typeName) {
return $value;
}

if ($this->allowNull && null === $value) {
return null;
}

return $input->hasOption($this->name) ? $input->getOption($this->name) : null;
return $value ?? $this->default;
}
}
23 changes: 4 additions & 19 deletionssrc/Symfony/Component/Console/Command/Command.php
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -100,6 +100,10 @@ public function __construct(?string $name = null)
$this->setDescription(static::getDefaultDescription() ?? '');
}

if (\is_callable($this)) {
$this->code = new InvokableCommand($this, $this(...));
}

$this->configure();
}

Expand DownExpand Up@@ -164,9 +168,6 @@ public function isEnabled(): bool
*/
protected function configure()
{
if (!$this->code && \is_callable($this)) {
$this->code = new InvokableCommand($this, $this(...));
}
Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Moved to the constructor to allow overriding theconfigure() method without requiring a call to the parent implementation

}

/**
Expand DownExpand Up@@ -312,22 +313,6 @@ public function complete(CompletionInput $input, CompletionSuggestions $suggesti
*/
public function setCode(callable $code): static
{
if ($code instanceof \Closure) {
$r = new \ReflectionFunction($code);
if (null === $r->getClosureThis()) {
set_error_handler(static function () {});
try {
if ($c = \Closure::bind($code, $this)) {
$code = $c;
}
} finally {
restore_error_handler();
}
}
} else {
$code = $code(...);
}
Copy link
MemberAuthor

@ycerutoycerutoJan 15, 2025
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Moved toInvokableCommand which supports handling static anonymous functions too


$this->code = new InvokableCommand($this, $code, triggerDeprecations: true);

return $this;
Expand Down
32 changes: 28 additions & 4 deletionssrc/Symfony/Component/Console/Command/InvokableCommand.php
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -30,14 +30,16 @@
*/
class InvokableCommand
{
private readonly \Closure $code;
private readonly \ReflectionFunction $reflection;

public function __construct(
private readonly Command $command,
private readonly \Closure $code,
callable $code,
private readonly bool $triggerDeprecations = false,
) {
$this->reflection = new \ReflectionFunction($code);
$this->code = $this->getClosure($code);
$this->reflection = new \ReflectionFunction($this->code);
}

/**
Expand All@@ -49,7 +51,7 @@

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

return 0;
}
Expand DownExpand Up@@ -77,6 +79,28 @@
}
}

private function getClosure(callable $code): \Closure
{
if (!$code instanceof \Closure) {
return $code(...);
}

if (null !== (new \ReflectionFunction($code))->getClosureThis()) {
return $code;
}

set_error_handler(static function () {});

Check failure on line 92 in src/Symfony/Component/Console/Command/InvokableCommand.php

View workflow job for this annotation

GitHub Actions/ Psalm

InvalidArgument

src/Symfony/Component/Console/Command/InvokableCommand.php:92:27: InvalidArgument: Argument 1 of set_error_handler expects callable(int, string, string=, int=, array<array-key, mixed>=):bool|null, but pure-Closure():void provided (see https://psalm.dev/004)

Check failure on line 92 in src/Symfony/Component/Console/Command/InvokableCommand.php

View workflow job for this annotation

GitHub Actions/ Psalm

InvalidArgument

src/Symfony/Component/Console/Command/InvokableCommand.php:92:27: InvalidArgument: Argument 1 of set_error_handler expects callable(int, string, string=, int=, array<array-key, mixed>=):bool|null, but pure-Closure():void provided (see https://psalm.dev/004)
try {
if ($c = \Closure::bind($code, $this->command)) {
$code = $c;
}
} finally {
restore_error_handler();
}

return $code;
}

private function getParameters(InputInterface $input, OutputInterface $output): array
{
$parameters = [];
Expand All@@ -97,7 +121,7 @@

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

continue;
}
Expand Down
Loading
Loading

[8]ページ先頭

©2009-2025 Movatter.jp