From 850e8574e92d6c5d58de3c333b443bf7f4b94ec9 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Thu, 26 Jun 2025 08:32:45 +0200 Subject: [PATCH] Use invokable commands approach on some API console commands --- composer.json | 10 +-- .../CLI/src/Command/Api/DisableKeyCommand.php | 87 ++++++++----------- .../CLI/src/Command/Api/ListKeysCommand.php | 37 ++++---- .../src/Command/Api/RenameApiKeyCommand.php | 35 ++++---- .../Command/Api/DisableKeyCommandTest.php | 8 +- .../Command/Api/RenameApiKeyCommandTest.php | 8 +- 6 files changed, 82 insertions(+), 103 deletions(-) diff --git a/composer.json b/composer.json index 49a7913a..26669e2e 100644 --- a/composer.json +++ b/composer.json @@ -54,11 +54,11 @@ "spiral/roadrunner-cli": "^2.7", "spiral/roadrunner-http": "^3.5", "spiral/roadrunner-jobs": "^4.6", - "symfony/console": "^7.2", - "symfony/filesystem": "^7.2", + "symfony/console": "^7.3", + "symfony/filesystem": "^7.3", "symfony/lock": "7.1.6", - "symfony/process": "^7.2", - "symfony/string": "^7.2" + "symfony/process": "^7.3", + "symfony/string": "^7.3" }, "require-dev": { "devizzent/cebe-php-openapi": "^1.1.2", @@ -73,7 +73,7 @@ "roave/security-advisories": "dev-master", "shlinkio/php-coding-standard": "~2.4.2", "shlinkio/shlink-test-utils": "^4.3.1", - "symfony/var-dumper": "^7.2", + "symfony/var-dumper": "^7.3", "veewee/composer-run-parallel": "^1.4" }, "conflict": { diff --git a/module/CLI/src/Command/Api/DisableKeyCommand.php b/module/CLI/src/Command/Api/DisableKeyCommand.php index 52169b26..308c432f 100644 --- a/module/CLI/src/Command/Api/DisableKeyCommand.php +++ b/module/CLI/src/Command/Api/DisableKeyCommand.php @@ -7,16 +7,40 @@ namespace Shlinkio\Shlink\CLI\Command\Api; use Shlinkio\Shlink\Common\Exception\InvalidArgumentException; use Shlinkio\Shlink\Rest\Entity\ApiKey; use Shlinkio\Shlink\Rest\Service\ApiKeyServiceInterface; +use Symfony\Component\Console\Attribute\Argument; +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Attribute\Option; use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; use function Shlinkio\Shlink\Core\ArrayUtils\map; use function sprintf; +#[AsCommand( + name: DisableKeyCommand::NAME, + description: 'Disables an API key by name or plain-text key (providing a plain-text key is DEPRECATED)', + help: <<%command.name% command allows you to disable an existing API key, via its name or the + plain-text key. + + If no arguments are provided, you will be prompted to select one of the existing non-disabled API keys. + + %command.full_name% + + You can optionally pass the API key name to be disabled. In that case --by-name is also + required, to indicate the first argument is the API key name and not the plain-text key: + + %command.full_name% the_key_name --by-name + + You can pass the plain-text key to be disabled, but that is DEPRECATED. In next major version, + the argument will always be assumed to be the name: + + %command.full_name% d6b6c60e-edcd-4e43-96ad-fa6b7014c143 + + HELP, +)] class DisableKeyCommand extends Command { public const string NAME = 'api-key:disable'; @@ -26,47 +50,9 @@ class DisableKeyCommand extends Command parent::__construct(); } - protected function configure(): void - { - $help = <<%command.name% command allows you to disable an existing API key, via its name or the - plain-text key. - - If no arguments are provided, you will be prompted to select one of the existing non-disabled API keys. - - %command.full_name% - - You can optionally pass the API key name to be disabled. In that case --by-name is also - required, to indicate the first argument is the API key name and not the plain-text key: - - %command.full_name% the_key_name --by-name - - You can pass the plain-text key to be disabled, but that is DEPRECATED. In next major version, - the argument will always be assumed to be the name: - - %command.full_name% d6b6c60e-edcd-4e43-96ad-fa6b7014c143 - - HELP; - - $this - ->setName(self::NAME) - ->setDescription('Disables an API key by name or plain-text key (providing a plain-text key is DEPRECATED)') - ->addArgument( - 'keyOrName', - InputArgument::OPTIONAL, - 'The API key to disable. Pass `--by-name` to indicate this value is the name and not the key.', - ) - ->addOption( - 'by-name', - mode: InputOption::VALUE_NONE, - description: 'Indicates the first argument is the API key name, not the plain-text key.', - ) - ->setHelp($help); - } - protected function interact(InputInterface $input, OutputInterface $output): void { - $keyOrName = $input->getArgument('keyOrName'); + $keyOrName = $input->getArgument('key-or-name'); if ($keyOrName === null) { $apiKeys = $this->apiKeyService->listKeys(enabledOnly: true); @@ -75,18 +61,21 @@ class DisableKeyCommand extends Command map($apiKeys, static fn (ApiKey $apiKey) => $apiKey->name), ); - $input->setArgument('keyOrName', $name); + $input->setArgument('key-or-name', $name); $input->setOption('by-name', true); } } - protected function execute(InputInterface $input, OutputInterface $output): int - { - $keyOrName = $input->getArgument('keyOrName'); - $byName = $input->getOption('by-name'); - $io = new SymfonyStyle($input, $output); - - if (! $keyOrName) { + public function __invoke( + SymfonyStyle $io, + #[Argument( + description: 'The API key to disable. Pass `--by-name` to indicate this value is the name and not the key.', + )] + string|null $keyOrName = null, + #[Option(description: 'Indicates the first argument is the API key name, not the plain-text key.')] + bool $byName = false, + ): int { + if ($keyOrName === null) { $io->warning('An API key name was not provided.'); return Command::INVALID; } diff --git a/module/CLI/src/Command/Api/ListKeysCommand.php b/module/CLI/src/Command/Api/ListKeysCommand.php index 7d63b6a4..a7b0a4be 100644 --- a/module/CLI/src/Command/Api/ListKeysCommand.php +++ b/module/CLI/src/Command/Api/ListKeysCommand.php @@ -8,16 +8,20 @@ use Shlinkio\Shlink\CLI\Util\ShlinkTable; use Shlinkio\Shlink\Rest\ApiKey\Role; use Shlinkio\Shlink\Rest\Entity\ApiKey; use Shlinkio\Shlink\Rest\Service\ApiKeyServiceInterface; +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Attribute\Option; use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Input\InputOption; -use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; use function array_filter; use function array_map; use function implode; use function sprintf; +#[AsCommand( + name: ListKeysCommand::NAME, + description: 'Lists all the available API keys.', +)] class ListKeysCommand extends Command { private const string ERROR_STRING_PATTERN = '%s'; @@ -31,23 +35,14 @@ class ListKeysCommand extends Command parent::__construct(); } - protected function configure(): void - { - $this - ->setName(self::NAME) - ->setDescription('Lists all the available API keys.') - ->addOption( - 'enabled-only', - 'e', - InputOption::VALUE_NONE, - 'Tells if only enabled API keys should be returned.', - ); - } - - protected function execute(InputInterface $input, OutputInterface $output): int - { - $enabledOnly = $input->getOption('enabled-only'); - + public function __invoke( + SymfonyStyle $io, + #[Option( + description: 'Tells if only enabled API keys should be returned.', + shortcut: 'e', + )] + bool $enabledOnly = false, + ): int { $rows = array_map(function (ApiKey $apiKey) use ($enabledOnly) { $expiration = $apiKey->expirationDate; $messagePattern = $this->determineMessagePattern($apiKey); @@ -65,7 +60,7 @@ class ListKeysCommand extends Command return $rowData; }, $this->apiKeyService->listKeys($enabledOnly)); - ShlinkTable::withRowSeparators($output)->render(array_filter([ + ShlinkTable::withRowSeparators($io)->render(array_filter([ 'Name', ! $enabledOnly ? 'Is enabled' : null, 'Expiration date', diff --git a/module/CLI/src/Command/Api/RenameApiKeyCommand.php b/module/CLI/src/Command/Api/RenameApiKeyCommand.php index f21d125b..fcbca1ce 100644 --- a/module/CLI/src/Command/Api/RenameApiKeyCommand.php +++ b/module/CLI/src/Command/Api/RenameApiKeyCommand.php @@ -8,14 +8,19 @@ use Shlinkio\Shlink\Core\Exception\InvalidArgumentException; use Shlinkio\Shlink\Core\Model\Renaming; use Shlinkio\Shlink\Rest\Entity\ApiKey; use Shlinkio\Shlink\Rest\Service\ApiKeyServiceInterface; +use Symfony\Component\Console\Attribute\Argument; +use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; use function Shlinkio\Shlink\Core\ArrayUtils\map; +#[AsCommand( + name: RenameApiKeyCommand::NAME, + description: 'Renames an API key by name', +)] class RenameApiKeyCommand extends Command { public const string NAME = 'api-key:rename'; @@ -25,20 +30,11 @@ class RenameApiKeyCommand extends Command parent::__construct(); } - protected function configure(): void - { - $this - ->setName(self::NAME) - ->setDescription('Renames an API key by name') - ->addArgument('oldName', InputArgument::REQUIRED, 'Current name of the API key to rename') - ->addArgument('newName', InputArgument::REQUIRED, 'New name to set to the API key'); - } - protected function interact(InputInterface $input, OutputInterface $output): void { $io = new SymfonyStyle($input, $output); - $oldName = $input->getArgument('oldName'); - $newName = $input->getArgument('newName'); + $oldName = $input->getArgument('old-name'); + $newName = $input->getArgument('new-name'); if ($oldName === null) { $apiKeys = $this->apiKeyService->listKeys(); @@ -47,7 +43,7 @@ class RenameApiKeyCommand extends Command map($apiKeys, static fn (ApiKey $apiKey) => $apiKey->name), ); - $input->setArgument('oldName', $requestedOldName); + $input->setArgument('old-name', $requestedOldName); } if ($newName === null) { @@ -58,16 +54,15 @@ class RenameApiKeyCommand extends Command : throw new InvalidArgumentException('The new name cannot be empty'), ); - $input->setArgument('newName', $requestedNewName); + $input->setArgument('new-name', $requestedNewName); } } - protected function execute(InputInterface $input, OutputInterface $output): int - { - $io = new SymfonyStyle($input, $output); - $oldName = $input->getArgument('oldName'); - $newName = $input->getArgument('newName'); - + public function __invoke( + SymfonyStyle $io, + #[Argument(description: 'Current name of the API key to rename')] string $oldName, + #[Argument(description: 'New name to set to the API key')] string $newName, + ): int { $this->apiKeyService->renameApiKey(Renaming::fromNames($oldName, $newName)); $io->success('API key properly renamed'); diff --git a/module/CLI/test/Command/Api/DisableKeyCommandTest.php b/module/CLI/test/Command/Api/DisableKeyCommandTest.php index e8a08336..85918305 100644 --- a/module/CLI/test/Command/Api/DisableKeyCommandTest.php +++ b/module/CLI/test/Command/Api/DisableKeyCommandTest.php @@ -35,7 +35,7 @@ class DisableKeyCommandTest extends TestCase $this->apiKeyService->expects($this->never())->method('disableByName'); $exitCode = $this->commandTester->execute([ - 'keyOrName' => $apiKey, + 'key-or-name' => $apiKey, ]); $output = $this->commandTester->getDisplay(); @@ -51,7 +51,7 @@ class DisableKeyCommandTest extends TestCase $this->apiKeyService->expects($this->never())->method('disableByKey'); $exitCode = $this->commandTester->execute([ - 'keyOrName' => $name, + 'key-or-name' => $name, '--by-name' => true, ]); $output = $this->commandTester->getDisplay(); @@ -71,7 +71,7 @@ class DisableKeyCommandTest extends TestCase $this->apiKeyService->expects($this->never())->method('disableByName'); $exitCode = $this->commandTester->execute([ - 'keyOrName' => $apiKey, + 'key-or-name' => $apiKey, ]); $output = $this->commandTester->getDisplay(); @@ -90,7 +90,7 @@ class DisableKeyCommandTest extends TestCase $this->apiKeyService->expects($this->never())->method('disableByKey'); $exitCode = $this->commandTester->execute([ - 'keyOrName' => $name, + 'key-or-name' => $name, '--by-name' => true, ]); $output = $this->commandTester->getDisplay(); diff --git a/module/CLI/test/Command/Api/RenameApiKeyCommandTest.php b/module/CLI/test/Command/Api/RenameApiKeyCommandTest.php index 41e5689f..d8c5f07f 100644 --- a/module/CLI/test/Command/Api/RenameApiKeyCommandTest.php +++ b/module/CLI/test/Command/Api/RenameApiKeyCommandTest.php @@ -43,7 +43,7 @@ class RenameApiKeyCommandTest extends TestCase $this->commandTester->setInputs([$oldName]); $this->commandTester->execute([ - 'newName' => $newName, + 'new-name' => $newName, ]); } @@ -60,7 +60,7 @@ class RenameApiKeyCommandTest extends TestCase $this->commandTester->setInputs([$newName]); $this->commandTester->execute([ - 'oldName' => $oldName, + 'old-name' => $oldName, ]); } @@ -76,8 +76,8 @@ class RenameApiKeyCommandTest extends TestCase ); $this->commandTester->execute([ - 'oldName' => $oldName, - 'newName' => $newName, + 'old-name' => $oldName, + 'new-name' => $newName, ]); } }