From 68e054999bcd1738592b75a0355ba8cf5420140d Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 7 Apr 2026 11:55:54 +0000 Subject: [PATCH 1/4] style(php-cs-fixer): fix coding standards --- src/Console/src/Console.php | 2 +- src/Console/tests/PromptArgumentsTest.php | 6 +++--- src/Core/tests/InvokerTest.php | 12 ++++++------ src/Exceptions/src/Renderer/ConsoleRenderer.php | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Console/src/Console.php b/src/Console/src/Console.php index 7e1eafbdf..f92dbbc6a 100644 --- a/src/Console/src/Console.php +++ b/src/Console/src/Console.php @@ -166,7 +166,7 @@ private function configureIO(InputInterface $input, OutputInterface $output): vo $inputStream = $input->getStream(); } - if ($inputStream !== null && !@posix_isatty($inputStream) && \getenv('SHELL_INTERACTIVE') === false) { + if ($inputStream !== null && !@\posix_isatty($inputStream) && \getenv('SHELL_INTERACTIVE') === false) { $input->setInteractive(false); } } diff --git a/src/Console/tests/PromptArgumentsTest.php b/src/Console/tests/PromptArgumentsTest.php index c0d8e63b2..78edfd604 100644 --- a/src/Console/tests/PromptArgumentsTest.php +++ b/src/Console/tests/PromptArgumentsTest.php @@ -54,7 +54,7 @@ public function testPromptArgumentWithQuestionOnClass(): void self::assertSame('This is question from the attribute', $method->invoke( $promptArguments, - new #[ AsCommand(name: 'foo'), Question(question: 'This is question from the attribute', argument: 'email') ] class extends Command { + new #[AsCommand(name: 'foo'), Question(question: 'This is question from the attribute', argument: 'email')] class extends Command { public function perform(): int { return self::SUCCESS; @@ -71,7 +71,7 @@ public function testPromptArgumentWithQuestionOnClassWithWrongArgumentShouldBeSk self::assertSame('Please provide a value for the `email` argument', $method->invoke( $promptArguments, - new #[ AsCommand(name: 'foo'), Question(question: 'This is question from the attribute', argument: 'foo') ] class extends Command { + new #[AsCommand(name: 'foo'), Question(question: 'This is question from the attribute', argument: 'foo')] class extends Command { public function perform(): int { return self::SUCCESS; @@ -131,7 +131,7 @@ public function testPromptArgumentException(): void $this->expectException(ConsoleException::class); $method->invoke( $promptArguments, - new #[ AsCommand(name: 'foo'), Question(question: 'Bar') ] class extends Command { + new #[AsCommand(name: 'foo'), Question(question: 'Bar')] class extends Command { public function perform(): int { return self::SUCCESS; diff --git a/src/Core/tests/InvokerTest.php b/src/Core/tests/InvokerTest.php index b8ee80ebd..fca62210a 100644 --- a/src/Core/tests/InvokerTest.php +++ b/src/Core/tests/InvokerTest.php @@ -111,9 +111,9 @@ public function testCallStaticMethodWithoutInstantiationAliasedScoped(): void $result = $this->container->runScope( new Scope('foo'), - fn(ScopeInterface $c): mixed => $c->runScope( + static fn(ScopeInterface $c): mixed => $c->runScope( new Scope('bar'), - fn(InvokerInterface $i): mixed => $i->invoke(['alias', 'publicMethod'], [42]), + static fn(InvokerInterface $i): mixed => $i->invoke(['alias', 'publicMethod'], [42]), ), ); @@ -126,7 +126,7 @@ public function testCallStaticMethodWithoutInstantiationAliasedScoped(): void */ public function testCallStaticMethodWithoutInstantiationWithFactory(): void { - $this->container->bind('foo', fn(): PrivateConstructor => throw new \Exception('Should not be called')); + $this->container->bind('foo', static fn(): PrivateConstructor => throw new \Exception('Should not be called')); $result = $this->container->invoke(['foo', 'publicMethod'], [42]); self::assertSame(42, $result); @@ -138,7 +138,7 @@ public function testCallStaticMethodWithoutInstantiationWithFactory(): void public function testCallStaticMethodWithoutInstantiationWithUntypedFactory(): void { // Note: do not add a return type to the closure - $this->container->bind('foo', fn() => throw new \Exception('Factory called')); + $this->container->bind('foo', static fn() => throw new \Exception('Factory called')); try { $this->container->invoke(['foo', 'publicMethod'], [42]); @@ -155,7 +155,7 @@ public function testCallStaticMethodWithoutInstantiationWithUntypedFactory(): vo */ public function testCallStaticMethodWithoutInstantiationWithOvertypedFactory(): void { - $this->container->bind('foo', fn(): PrivateConstructor|SampleClass => throw new \Exception('Factory called')); + $this->container->bind('foo', static fn(): PrivateConstructor|SampleClass => throw new \Exception('Factory called')); try { $this->container->invoke(['foo', 'publicMethod'], [42]); @@ -172,7 +172,7 @@ public function testCallStaticMethodWithoutInstantiationWithOvertypedFactory(): */ public function testCallStaticMethodWithoutInstantiationWithNullableTypedFactory(): void { - $this->container->bind('foo', fn(): ?PrivateConstructor => throw new \Exception('Factory called')); + $this->container->bind('foo', static fn(): ?PrivateConstructor => throw new \Exception('Factory called')); try { $this->container->invoke(['foo', 'publicMethod'], [42]); self::fail('Exception should be thrown'); diff --git a/src/Exceptions/src/Renderer/ConsoleRenderer.php b/src/Exceptions/src/Renderer/ConsoleRenderer.php index 53c782c56..0036f1fc2 100644 --- a/src/Exceptions/src/Renderer/ConsoleRenderer.php +++ b/src/Exceptions/src/Renderer/ConsoleRenderer.php @@ -246,7 +246,7 @@ private function isColorsSupported(mixed $stream = STDOUT): bool try { if (\DIRECTORY_SEPARATOR === '\\') { - return (\function_exists('sapi_windows_vt100_support') && @\sapi_windows_vt100_support($stream)) + return (\function_exists('sapi_windows_vt100_support') && @sapi_windows_vt100_support($stream)) || \getenv('ANSICON') !== false || \getenv('ConEmuANSI') === 'ON' || \getenv('TERM') === 'xterm'; From e8fe3874f0b21ae838259d1d6c56695db2e4599c Mon Sep 17 00:00:00 2001 From: Alexander Strizhak Date: Tue, 14 Apr 2026 19:10:03 +0300 Subject: [PATCH 2/4] feat(console): add aliases support to AsCommand attribute - Add `aliases` parameter to `AsCommand` attribute - Propagate aliases through `CommandDefinition` and `AttributeBasedConfigurator` - Support `--aliases` option in `create:command` scaffolder command - Generate `aliases` in `#[AsCommand]` when provided via scaffolder Co-Authored-By: Claude Sonnet 4.6 --- src/Console/src/Attribute/AsCommand.php | 1 + .../src/Configurator/Attribute/Parser.php | 1 + .../AttributeBasedConfigurator.php | 1 + .../src/Configurator/CommandDefinition.php | 2 ++ src/Console/tests/AttributeTest.php | 12 +++++++++++ .../Fixtures/Attribute/WithAliasesCommand.php | 19 ++++++++++++++++++ src/Scaffolder/src/Command/CommandCommand.php | 4 ++++ .../src/Declaration/CommandDeclaration.php | 5 +++++ src/Scaffolder/tests/Command/CommandTest.php | 20 +++++++++++++++++++ 9 files changed, 65 insertions(+) create mode 100644 src/Console/tests/Fixtures/Attribute/WithAliasesCommand.php diff --git a/src/Console/src/Attribute/AsCommand.php b/src/Console/src/Attribute/AsCommand.php index 31a9ffcc7..4407c1fa9 100644 --- a/src/Console/src/Attribute/AsCommand.php +++ b/src/Console/src/Attribute/AsCommand.php @@ -13,5 +13,6 @@ public function __construct( public readonly string $name, public readonly ?string $description = null, public readonly ?string $help = null, + public readonly array $aliases = [], ) {} } diff --git a/src/Console/src/Configurator/Attribute/Parser.php b/src/Console/src/Configurator/Attribute/Parser.php index 0999e68e7..a6edcae9a 100644 --- a/src/Console/src/Configurator/Attribute/Parser.php +++ b/src/Console/src/Configurator/Attribute/Parser.php @@ -46,6 +46,7 @@ public function parse(\ReflectionClass $reflection): CommandDefinition options: $this->parseOptions($reflection), description: $attribute->description, help: $attribute instanceof AsCommand ? $attribute->help : null, + aliases: $attribute instanceof AsCommand ? $attribute->aliases : [], ); } diff --git a/src/Console/src/Configurator/AttributeBasedConfigurator.php b/src/Console/src/Configurator/AttributeBasedConfigurator.php index 53ea75040..aa1bf51e3 100644 --- a/src/Console/src/Configurator/AttributeBasedConfigurator.php +++ b/src/Console/src/Configurator/AttributeBasedConfigurator.php @@ -28,6 +28,7 @@ public function configure(Command $command, \ReflectionClass $reflection): void $command->setName($result->name); $command->setDescription($result->description ?? (string) $reflection->getConstant('DESCRIPTION')); $command->setHelp((string) $result->help); + $command->setAliases($result->aliases); foreach ($result->options as $option) { $command->getDefinition()->addOption($option); diff --git a/src/Console/src/Configurator/CommandDefinition.php b/src/Console/src/Configurator/CommandDefinition.php index b3fa10c60..7f43980b0 100644 --- a/src/Console/src/Configurator/CommandDefinition.php +++ b/src/Console/src/Configurator/CommandDefinition.php @@ -23,5 +23,7 @@ public function __construct( public readonly ?string $description = null, /** @var ?non-empty-string */ public readonly ?string $help = null, + /** @var string[] */ + public readonly array $aliases = [], ) {} } diff --git a/src/Console/tests/AttributeTest.php b/src/Console/tests/AttributeTest.php index 776a85460..4dbbd0c84 100644 --- a/src/Console/tests/AttributeTest.php +++ b/src/Console/tests/AttributeTest.php @@ -6,6 +6,7 @@ use Spiral\Attributes\AttributeReader; use Spiral\Attributes\ReaderInterface; +use Spiral\Tests\Console\Fixtures\Attribute\WithAliasesCommand; use Spiral\Tests\Console\Fixtures\Attribute\WithDescriptionCommand; use Spiral\Tests\Console\Fixtures\Attribute\WithHelpCommand; use Spiral\Tests\Console\Fixtures\Attribute\WithNameCommand; @@ -40,6 +41,17 @@ public function testCommandWithHelp(): void self::assertSame('Some help message', $core->run(command: 'attribute-with-help')->getOutput()->fetch()); } + public function testCommandWithAliases(): void + { + $core = $this->getCore($this->getStaticLocator([ + WithAliasesCommand::class, + ])); + + self::assertSame('awa,alias-for-with-aliases', $core->run(command: 'attribute-with-aliases')->getOutput()->fetch()); + self::assertSame('awa,alias-for-with-aliases', $core->run(command: 'awa')->getOutput()->fetch()); + self::assertSame('awa,alias-for-with-aliases', $core->run(command: 'alias-for-with-aliases')->getOutput()->fetch()); + } + public function testCommandWithSymfonyAttribute(): void { $core = $this->getCore($this->getStaticLocator([ diff --git a/src/Console/tests/Fixtures/Attribute/WithAliasesCommand.php b/src/Console/tests/Fixtures/Attribute/WithAliasesCommand.php new file mode 100644 index 000000000..295c08a31 --- /dev/null +++ b/src/Console/tests/Fixtures/Attribute/WithAliasesCommand.php @@ -0,0 +1,19 @@ +write(\implode(',', $this->getAliases())); + + return self::SUCCESS; + } +} diff --git a/src/Scaffolder/src/Command/CommandCommand.php b/src/Scaffolder/src/Command/CommandCommand.php index 228cd5c73..3a918c154 100644 --- a/src/Scaffolder/src/Command/CommandCommand.php +++ b/src/Scaffolder/src/Command/CommandCommand.php @@ -29,6 +29,9 @@ class CommandCommand extends AbstractCommand #[Option(description: 'Optional, specify a custom namespace')] private ?string $namespace = null; + #[Option(name: 'aliases', description: 'Command aliases')] + private array $aliases = []; + #[Option(name: 'argument', shortcut: 'a', description: 'Command arguments')] private array $arguments = []; @@ -40,6 +43,7 @@ public function perform(): int $declaration = $this->createDeclaration(CommandDeclaration::class, [ 'description' => $this->description, 'alias' => $this->alias ?? \strtolower((string) \preg_replace('/(?name)), + 'aliases' => $this->aliases, ]); foreach ($this->arguments as $argument) { diff --git a/src/Scaffolder/src/Declaration/CommandDeclaration.php b/src/Scaffolder/src/Declaration/CommandDeclaration.php index ab9acb0fe..1e09a4390 100644 --- a/src/Scaffolder/src/Declaration/CommandDeclaration.php +++ b/src/Scaffolder/src/Declaration/CommandDeclaration.php @@ -22,6 +22,7 @@ public function __construct( ?string $namespace = null, private readonly ?string $alias = null, private readonly ?string $description = null, + private readonly array $aliases = [], ) { parent::__construct($config, $name, $comment, $namespace); } @@ -73,6 +74,10 @@ public function declare(): void $commandDefinition['description'] = $this->description; } + if ($this->aliases !== []) { + $commandDefinition['aliases'] = $this->aliases; + } + $this->class->addAttribute(AsCommand::class, $commandDefinition); $this->class diff --git a/src/Scaffolder/tests/Command/CommandTest.php b/src/Scaffolder/tests/Command/CommandTest.php index 7d4564774..1c1447556 100644 --- a/src/Scaffolder/tests/Command/CommandTest.php +++ b/src/Scaffolder/tests/Command/CommandTest.php @@ -126,6 +126,26 @@ public function testScaffoldWithCustomNamespace(): void self::assertStringContainsString('App\Custom\Command', $content); } + public function testScaffoldWithAliases(): void + { + $this->className = $className = '\\Spiral\\Tests\\Scaffolder\\App\\Command\\AliasedCommand'; + + $this->console()->run('create:command', [ + 'name' => 'Aliased', + '--aliases' => ['al', 'aliased-cmd'], + ]); + + \clearstatcache(); + self::assertTrue(\class_exists($className)); + + $reflection = new \ReflectionClass($className); + + /** @var \Spiral\Console\Attribute\AsCommand $definition */ + $definition = $reflection->getAttributes()[0]->newInstance(); + + self::assertSame(['al', 'aliased-cmd'], $definition->aliases); + } + public function testShowInstructionAfterInstallation(): void { $this->className = $className = '\\Spiral\\Tests\\Scaffolder\\App\\Command\\ArgumentCommand'; From 381f42d6b25f342fc3d1f1157762419154b57163 Mon Sep 17 00:00:00 2001 From: Alexander Strizhak Date: Mon, 4 May 2026 10:04:13 +0300 Subject: [PATCH 3/4] Update src/Console/src/Configurator/CommandDefinition.php Co-authored-by: Aleksei Gagarin --- src/Console/src/Configurator/CommandDefinition.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Console/src/Configurator/CommandDefinition.php b/src/Console/src/Configurator/CommandDefinition.php index 7f43980b0..becfadd24 100644 --- a/src/Console/src/Configurator/CommandDefinition.php +++ b/src/Console/src/Configurator/CommandDefinition.php @@ -23,7 +23,7 @@ public function __construct( public readonly ?string $description = null, /** @var ?non-empty-string */ public readonly ?string $help = null, - /** @var string[] */ + /** @var non-empty-string[] */ public readonly array $aliases = [], ) {} } From aa47e38450aff9e7a42851e29f7697016705a6a5 Mon Sep 17 00:00:00 2001 From: Alexander Strizhak Date: Mon, 4 May 2026 11:24:42 +0300 Subject: [PATCH 4/4] types --- src/Console/src/Attribute/AsCommand.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Console/src/Attribute/AsCommand.php b/src/Console/src/Attribute/AsCommand.php index 4407c1fa9..bd20b8530 100644 --- a/src/Console/src/Attribute/AsCommand.php +++ b/src/Console/src/Attribute/AsCommand.php @@ -13,6 +13,7 @@ public function __construct( public readonly string $name, public readonly ?string $description = null, public readonly ?string $help = null, + /** @var list */ public readonly array $aliases = [], ) {} }