diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0402f290..bf87c663 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -31,7 +31,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this
* [#2472](https://github.com/shlinkio/shlink/issues/2472) Add support for PHP 8.5
### Changed
-* *Nothing*
+* [#2424](https://github.com/shlinkio/shlink/issues/2424) Make simple console commands invokable.
### Deprecated
* *Nothing*
diff --git a/module/CLI/src/Command/Api/InitialApiKeyCommand.php b/module/CLI/src/Command/Api/InitialApiKeyCommand.php
index 66968eb3..680135d8 100644
--- a/module/CLI/src/Command/Api/InitialApiKeyCommand.php
+++ b/module/CLI/src/Command/Api/InitialApiKeyCommand.php
@@ -5,11 +5,15 @@ declare(strict_types=1);
namespace Shlinkio\Shlink\CLI\Command\Api;
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;
+#[AsCommand(
+ name: InitialApiKeyCommand::NAME,
+ description: 'Tries to create initial API key',
+)]
class InitialApiKeyCommand extends Command
{
public const string NAME = 'api-key:initial';
@@ -19,22 +23,14 @@ class InitialApiKeyCommand extends Command
parent::__construct();
}
- protected function configure(): void
- {
- $this
- ->setHidden()
- ->setName(self::NAME)
- ->setDescription('Tries to create initial API key')
- ->addArgument('apiKey', InputArgument::REQUIRED, 'The initial API to create');
- }
+ public function __invoke(
+ SymfonyStyle $io,
+ #[Argument('The initial API to create')] string $apiKey,
+ ): int {
+ $result = $this->apiKeyService->createInitial($apiKey);
- protected function execute(InputInterface $input, OutputInterface $output): int
- {
- $key = $input->getArgument('apiKey');
- $result = $this->apiKeyService->createInitial($key);
-
- if ($result === null && $output->isVerbose()) {
- $output->writeln('Other API keys already exist. Initial API key creation skipped.');
+ if ($result === null && $io->isVerbose()) {
+ $io->writeln('Other API keys already exist. Initial API key creation skipped.');
}
return Command::SUCCESS;
diff --git a/module/CLI/src/Command/Config/ReadEnvVarCommand.php b/module/CLI/src/Command/Config/ReadEnvVarCommand.php
index e1cef3fd..e3a38be6 100644
--- a/module/CLI/src/Command/Config/ReadEnvVarCommand.php
+++ b/module/CLI/src/Command/Config/ReadEnvVarCommand.php
@@ -6,9 +6,10 @@ namespace Shlinkio\Shlink\CLI\Command\Config;
use Closure;
use Shlinkio\Shlink\Core\Config\EnvVars;
+use Symfony\Component\Console\Attribute\Argument;
+use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\InvalidArgumentException;
-use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
@@ -18,6 +19,11 @@ use function Shlinkio\Shlink\Core\ArrayUtils\contains;
use function Shlinkio\Shlink\Core\enumValues;
use function sprintf;
+#[AsCommand(
+ name: ReadEnvVarCommand::NAME,
+ description: 'Display current value for an env var',
+ hidden: true,
+)]
class ReadEnvVarCommand extends Command
{
public const string NAME = 'env-var:read';
@@ -31,19 +37,10 @@ class ReadEnvVarCommand extends Command
parent::__construct();
}
- protected function configure(): void
- {
- $this
- ->setName(self::NAME)
- ->setHidden()
- ->setDescription('Display current value for an env var')
- ->addArgument('envVar', InputArgument::REQUIRED, 'The env var to read');
- }
-
protected function interact(InputInterface $input, OutputInterface $output): void
{
$io = new SymfonyStyle($input, $output);
- $envVar = $input->getArgument('envVar');
+ $envVar = $input->getArgument('env-var');
$validEnvVars = enumValues(EnvVars::class);
if ($envVar === null) {
@@ -54,14 +51,14 @@ class ReadEnvVarCommand extends Command
throw new InvalidArgumentException(sprintf('%s is not a valid Shlink environment variable', $envVar));
}
- $input->setArgument('envVar', $envVar);
+ $input->setArgument('env-var', $envVar);
}
- protected function execute(InputInterface $input, OutputInterface $output): int
- {
- $envVar = $input->getArgument('envVar');
- $output->writeln(formatEnvVarValue(($this->loadEnvVar)($envVar)));
-
+ public function __invoke(
+ SymfonyStyle $io,
+ #[Argument(description: 'The env var to read')] string $envVar,
+ ): int {
+ $io->writeln(formatEnvVarValue(($this->loadEnvVar)($envVar)));
return Command::SUCCESS;
}
}
diff --git a/module/CLI/src/Command/Domain/DomainRedirectsCommand.php b/module/CLI/src/Command/Domain/DomainRedirectsCommand.php
index 4c2e4350..1e272c12 100644
--- a/module/CLI/src/Command/Domain/DomainRedirectsCommand.php
+++ b/module/CLI/src/Command/Domain/DomainRedirectsCommand.php
@@ -7,8 +7,9 @@ namespace Shlinkio\Shlink\CLI\Command\Domain;
use Shlinkio\Shlink\Core\Config\NotFoundRedirects;
use Shlinkio\Shlink\Core\Domain\DomainServiceInterface;
use Shlinkio\Shlink\Core\Domain\Model\DomainItem;
+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;
@@ -18,6 +19,10 @@ use function array_map;
use function sprintf;
use function str_contains;
+#[AsCommand(
+ name: DomainRedirectsCommand::NAME,
+ description: 'Set specific "not found" redirects for individual domains.',
+)]
class DomainRedirectsCommand extends Command
{
public const string NAME = 'domain:redirects';
@@ -27,18 +32,6 @@ class DomainRedirectsCommand extends Command
parent::__construct();
}
- protected function configure(): void
- {
- $this
- ->setName(self::NAME)
- ->setDescription('Set specific "not found" redirects for individual domains.')
- ->addArgument(
- 'domain',
- InputArgument::REQUIRED,
- 'The domain authority to which you want to set the specific redirects',
- );
- }
-
protected function interact(InputInterface $input, OutputInterface $output): void
{
/** @var string|null $domain */
@@ -67,10 +60,11 @@ class DomainRedirectsCommand extends Command
$input->setArgument('domain', str_contains($selectedOption, 'New domain') ? $askNewDomain() : $selectedOption);
}
- protected function execute(InputInterface $input, OutputInterface $output): int
- {
- $io = new SymfonyStyle($input, $output);
- $domainAuthority = $input->getArgument('domain');
+ public function __invoke(
+ SymfonyStyle $io,
+ #[Argument('The domain authority to which you want to set the specific redirects', name: 'domain')]
+ string $domainAuthority,
+ ): int {
$domain = $this->domainService->findByAuthority($domainAuthority);
$ask = static function (string $message, string|null $current) use ($io): string|null {
diff --git a/module/CLI/src/Command/Domain/ListDomainsCommand.php b/module/CLI/src/Command/Domain/ListDomainsCommand.php
index 935d272e..a66d6d7e 100644
--- a/module/CLI/src/Command/Domain/ListDomainsCommand.php
+++ b/module/CLI/src/Command/Domain/ListDomainsCommand.php
@@ -8,13 +8,17 @@ use Shlinkio\Shlink\CLI\Util\ShlinkTable;
use Shlinkio\Shlink\Core\Config\NotFoundRedirectConfigInterface;
use Shlinkio\Shlink\Core\Domain\DomainServiceInterface;
use Shlinkio\Shlink\Core\Domain\Model\DomainItem;
+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_map;
+#[AsCommand(
+ name: ListDomainsCommand::NAME,
+ description: 'List all domains that have been ever used for some short URL',
+)]
class ListDomainsCommand extends Command
{
public const string NAME = 'domain:list';
@@ -24,25 +28,17 @@ class ListDomainsCommand extends Command
parent::__construct();
}
- protected function configure(): void
- {
- $this
- ->setName(self::NAME)
- ->setDescription('List all domains that have been ever used for some short URL')
- ->addOption(
- 'show-redirects',
- 'r',
- InputOption::VALUE_NONE,
- 'Will display an extra column with the information of the "not found" redirects for every domain.',
- );
- }
-
- protected function execute(InputInterface $input, OutputInterface $output): int
- {
+ public function __invoke(
+ SymfonyStyle $io,
+ #[Option(
+ 'Will display an extra column with the information of the "not found" redirects for every domain.',
+ shortcut: 'r',
+ )]
+ bool $showRedirects = false,
+ ): int {
$domains = $this->domainService->listDomains();
- $showRedirects = $input->getOption('show-redirects');
$commonFields = ['Domain', 'Is default'];
- $table = $showRedirects ? ShlinkTable::withRowSeparators($output) : ShlinkTable::default($output);
+ $table = $showRedirects ? ShlinkTable::withRowSeparators($io) : ShlinkTable::default($io);
$table->render(
$showRedirects ? [...$commonFields, '"Not found" redirects'] : $commonFields,
@@ -53,7 +49,7 @@ class ListDomainsCommand extends Command
? [
...$commonValues,
$this->notFoundRedirectsToString($domain->notFoundRedirectConfig),
- ]
+ ]
: $commonValues;
}, $domains),
);
diff --git a/module/CLI/src/Command/Integration/MatomoSendVisitsCommand.php b/module/CLI/src/Command/Integration/MatomoSendVisitsCommand.php
index c1c22075..f5d8e84c 100644
--- a/module/CLI/src/Command/Integration/MatomoSendVisitsCommand.php
+++ b/module/CLI/src/Command/Integration/MatomoSendVisitsCommand.php
@@ -8,10 +8,10 @@ use Cake\Chronos\Chronos;
use Shlinkio\Shlink\Core\Matomo\MatomoOptions;
use Shlinkio\Shlink\Core\Matomo\MatomoVisitSenderInterface;
use Shlinkio\Shlink\Core\Matomo\VisitSendingProgressTrackerInterface;
+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 Throwable;
@@ -19,22 +19,9 @@ use function Shlinkio\Shlink\Common\buildDateRange;
use function Shlinkio\Shlink\Core\dateRangeToHumanFriendly;
use function sprintf;
-class MatomoSendVisitsCommand extends Command implements VisitSendingProgressTrackerInterface
-{
- public const string NAME = 'integration:matomo:send-visits';
-
- private readonly bool $matomoEnabled;
- private SymfonyStyle $io;
-
- public function __construct(MatomoOptions $matomoOptions, private readonly MatomoVisitSenderInterface $visitSender)
- {
- $this->matomoEnabled = $matomoOptions->enabled;
- parent::__construct();
- }
-
- protected function configure(): void
- {
- $help = <<%command.name% --since 2022-01-01 --until 2022-12-31
- HELP;
+ HELP,
+)]
+class MatomoSendVisitsCommand extends Command implements VisitSendingProgressTrackerInterface
+{
+ public const string NAME = 'integration:matomo:send-visits';
- $this
- ->setName(self::NAME)
- ->setDescription(sprintf(
- '%sSend existing visits to the configured matomo instance',
- $this->matomoEnabled ? '' : '[MATOMO INTEGRATION DISABLED] ',
- ))
- ->setHelp($help)
- ->addOption(
- 'since',
- 's',
- InputOption::VALUE_REQUIRED,
- 'Only visits created since this date, inclusively, will be sent to Matomo',
- )
- ->addOption(
- 'until',
- 'u',
- InputOption::VALUE_REQUIRED,
- 'Only visits created until this date, inclusively, will be sent to Matomo',
- );
+ private readonly bool $matomoEnabled;
+ private SymfonyStyle $io;
+
+ public function __construct(MatomoOptions $matomoOptions, private readonly MatomoVisitSenderInterface $visitSender)
+ {
+ $this->matomoEnabled = $matomoOptions->enabled;
+ parent::__construct();
}
- protected function execute(InputInterface $input, OutputInterface $output): int
+ protected function configure(): void
{
- $this->io = new SymfonyStyle($input, $output);
+ $this->setDescription(sprintf(
+ '%sSend existing visits to the configured matomo instance',
+ $this->matomoEnabled ? '' : '[MATOMO INTEGRATION DISABLED] ',
+ ));
+ }
+
+ public function __invoke(
+ SymfonyStyle $io,
+ InputInterface $input,
+ #[Option('Only visits created since this date, inclusively, will be sent to Matomo', shortcut: 's')]
+ string|null $since = null,
+ #[Option('Only visits created until this date, inclusively, will be sent to Matomo', shortcut: 'u')]
+ string|null $until = null,
+ ): int {
+ $this->io = $io;
if (! $this->matomoEnabled) {
$this->io->warning('Matomo integration is not enabled in this Shlink instance');
@@ -87,8 +80,6 @@ class MatomoSendVisitsCommand extends Command implements VisitSendingProgressTra
}
// TODO Validate provided date formats
- $since = $input->getOption('since');
- $until = $input->getOption('until');
$dateRange = buildDateRange(
startDate: $since !== null ? Chronos::parse($since) : null,
endDate: $until !== null ? Chronos::parse($until) : null,
diff --git a/module/CLI/src/Command/ShortUrl/DeleteExpiredShortUrlsCommand.php b/module/CLI/src/Command/ShortUrl/DeleteExpiredShortUrlsCommand.php
index 2b2abd01..626ac136 100644
--- a/module/CLI/src/Command/ShortUrl/DeleteExpiredShortUrlsCommand.php
+++ b/module/CLI/src/Command/ShortUrl/DeleteExpiredShortUrlsCommand.php
@@ -6,14 +6,18 @@ namespace Shlinkio\Shlink\CLI\Command\ShortUrl;
use Shlinkio\Shlink\Core\ShortUrl\DeleteShortUrlServiceInterface;
use Shlinkio\Shlink\Core\ShortUrl\Model\ExpiredShortUrlsConditions;
+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 sprintf;
+#[AsCommand(
+ name: DeleteExpiredShortUrlsCommand::NAME,
+ description: 'Deletes all short URLs that are considered expired, because they have a validUntil date in the past',
+)]
class DeleteExpiredShortUrlsCommand extends Command
{
public const string NAME = 'short-url:delete-expired';
@@ -23,32 +27,17 @@ class DeleteExpiredShortUrlsCommand extends Command
parent::__construct();
}
- protected function configure(): void
- {
- $this
- ->setName(self::NAME)
- ->setDescription(
- 'Deletes all short URLs that are considered expired, because they have a validUntil date in the past',
- )
- ->addOption(
- 'evaluate-max-visits',
- mode: InputOption::VALUE_NONE,
- description: 'Also take into consideration short URLs which have reached their max amount of visits.',
- )
- ->addOption('force', 'f', InputOption::VALUE_NONE, 'Delete short URLs with no confirmation')
- ->addOption(
- 'dry-run',
- mode: InputOption::VALUE_NONE,
- description: 'Delete short URLs with no confirmation',
- );
- }
-
- protected function execute(InputInterface $input, OutputInterface $output): int
- {
- $io = new SymfonyStyle($input, $output);
- $force = $input->getOption('force') || ! $input->isInteractive();
- $dryRun = $input->getOption('dry-run');
- $conditions = new ExpiredShortUrlsConditions(maxVisitsReached: $input->getOption('evaluate-max-visits'));
+ public function __invoke(
+ SymfonyStyle $io,
+ InputInterface $input,
+ #[Option('Also take into consideration short URLs which have reached their max amount of visits.')]
+ bool $evaluateMaxVisits = false,
+ #[Option('Delete short URLs with no confirmation', shortcut: 'f')] bool $force = false,
+ #[Option('Only check how many short URLs would be affected, without actually deleting them')]
+ bool $dryRun = false,
+ ): int {
+ $conditions = new ExpiredShortUrlsConditions(maxVisitsReached: $evaluateMaxVisits);
+ $force = $force || ! $input->isInteractive();
if (! $force && ! $dryRun) {
$io->warning([
@@ -69,6 +58,7 @@ class DeleteExpiredShortUrlsCommand extends Command
$result = $this->deleteShortUrlService->deleteExpiredShortUrls($conditions);
$io->success(sprintf('%s expired short URLs have been deleted', $result));
+
return self::SUCCESS;
}
}
diff --git a/module/CLI/src/Command/Tag/DeleteTagsCommand.php b/module/CLI/src/Command/Tag/DeleteTagsCommand.php
index 2022a9dc..301cba26 100644
--- a/module/CLI/src/Command/Tag/DeleteTagsCommand.php
+++ b/module/CLI/src/Command/Tag/DeleteTagsCommand.php
@@ -5,12 +5,12 @@ declare(strict_types=1);
namespace Shlinkio\Shlink\CLI\Command\Tag;
use Shlinkio\Shlink\Core\Tag\TagServiceInterface;
+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;
+#[AsCommand(name: DeleteTagsCommand::NAME, description: 'Deletes one or more tags.')]
class DeleteTagsCommand extends Command
{
public const string NAME = 'tag:delete';
@@ -20,24 +20,13 @@ class DeleteTagsCommand extends Command
parent::__construct();
}
- protected function configure(): void
- {
- $this
- ->setName(self::NAME)
- ->setDescription('Deletes one or more tags.')
- ->addOption(
- 'name',
- 't',
- InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
- 'The name of the tags to delete',
- );
- }
-
- protected function execute(InputInterface $input, OutputInterface $output): int
- {
- $io = new SymfonyStyle($input, $output);
- $tagNames = $input->getOption('name');
-
+ /**
+ * @param string[] $tagNames
+ */
+ public function __invoke(
+ SymfonyStyle $io,
+ #[Option('The name of the tags to delete', name: 'name', shortcut: 't')] array $tagNames = [],
+ ): int {
if (empty($tagNames)) {
$io->warning('You have to provide at least one tag name');
return self::INVALID;
@@ -45,6 +34,7 @@ class DeleteTagsCommand extends Command
$this->tagService->deleteTags($tagNames);
$io->success('Tags properly deleted');
+
return self::SUCCESS;
}
}
diff --git a/module/CLI/src/Command/Tag/ListTagsCommand.php b/module/CLI/src/Command/Tag/ListTagsCommand.php
index abd9a0dd..66497737 100644
--- a/module/CLI/src/Command/Tag/ListTagsCommand.php
+++ b/module/CLI/src/Command/Tag/ListTagsCommand.php
@@ -8,12 +8,13 @@ use Shlinkio\Shlink\CLI\Util\ShlinkTable;
use Shlinkio\Shlink\Core\Tag\Model\TagInfo;
use Shlinkio\Shlink\Core\Tag\Model\TagsParams;
use Shlinkio\Shlink\Core\Tag\TagServiceInterface;
+use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
-use Symfony\Component\Console\Input\InputInterface;
-use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;
use function array_map;
+#[AsCommand(ListTagsCommand::NAME, 'Lists existing tags.')]
class ListTagsCommand extends Command
{
public const string NAME = 'tag:list';
@@ -23,16 +24,9 @@ class ListTagsCommand extends Command
parent::__construct();
}
- protected function configure(): void
+ public function __invoke(SymfonyStyle $io): int
{
- $this
- ->setName(self::NAME)
- ->setDescription('Lists existing tags.');
- }
-
- protected function execute(InputInterface $input, OutputInterface $output): int
- {
- ShlinkTable::default($output)->render(['Name', 'URLs amount', 'Visits amount'], $this->getTagsRows());
+ ShlinkTable::default($io)->render(['Name', 'URLs amount', 'Visits amount'], $this->getTagsRows());
return self::SUCCESS;
}
diff --git a/module/CLI/src/Command/Tag/RenameTagCommand.php b/module/CLI/src/Command/Tag/RenameTagCommand.php
index 2ae0159c..f9e53f28 100644
--- a/module/CLI/src/Command/Tag/RenameTagCommand.php
+++ b/module/CLI/src/Command/Tag/RenameTagCommand.php
@@ -8,12 +8,12 @@ use Shlinkio\Shlink\Core\Exception\TagConflictException;
use Shlinkio\Shlink\Core\Exception\TagNotFoundException;
use Shlinkio\Shlink\Core\Model\Renaming;
use Shlinkio\Shlink\Core\Tag\TagServiceInterface;
+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;
+#[AsCommand(RenameTagCommand::NAME, 'Renames one existing tag.')]
class RenameTagCommand extends Command
{
public const string NAME = 'tag:rename';
@@ -23,21 +23,11 @@ class RenameTagCommand extends Command
parent::__construct();
}
- protected function configure(): void
- {
- $this
- ->setName(self::NAME)
- ->setDescription('Renames one existing tag.')
- ->addArgument('oldName', InputArgument::REQUIRED, 'Current name of the tag.')
- ->addArgument('newName', InputArgument::REQUIRED, 'New name of the tag.');
- }
-
- 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('Current name of the tag.')] string $oldName,
+ #[Argument('New name of the tag.')] string $newName,
+ ): int {
try {
$this->tagService->renameTag(Renaming::fromNames($oldName, $newName));
$io->success('Tag properly renamed.');
diff --git a/module/CLI/src/Command/Visit/DownloadGeoLiteDbCommand.php b/module/CLI/src/Command/Visit/DownloadGeoLiteDbCommand.php
index 4d58a7d3..f76a4dbc 100644
--- a/module/CLI/src/Command/Visit/DownloadGeoLiteDbCommand.php
+++ b/module/CLI/src/Command/Visit/DownloadGeoLiteDbCommand.php
@@ -8,14 +8,17 @@ use Shlinkio\Shlink\Core\Exception\GeolocationDbUpdateFailedException;
use Shlinkio\Shlink\Core\Geolocation\GeolocationDbUpdaterInterface;
use Shlinkio\Shlink\Core\Geolocation\GeolocationDownloadProgressHandlerInterface;
use Shlinkio\Shlink\Core\Geolocation\GeolocationResult;
+use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\ProgressBar;
-use Symfony\Component\Console\Input\InputInterface;
-use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use function sprintf;
+#[AsCommand(
+ DownloadGeoLiteDbCommand::NAME,
+ 'Checks if the GeoLite2 db file is too old or it does not exist, and tries to download an up-to-date copy if so.',
+)]
class DownloadGeoLiteDbCommand extends Command implements GeolocationDownloadProgressHandlerInterface
{
public const string NAME = 'visit:download-db';
@@ -28,19 +31,9 @@ class DownloadGeoLiteDbCommand extends Command implements GeolocationDownloadPro
parent::__construct();
}
- protected function configure(): void
+ public function __invoke(SymfonyStyle $io): int
{
- $this
- ->setName(self::NAME)
- ->setDescription(
- 'Checks if the GeoLite2 db file is too old or it does not exist, and tries to download an up-to-date '
- . 'copy if so.',
- );
- }
-
- protected function execute(InputInterface $input, OutputInterface $output): int
- {
- $this->io = new SymfonyStyle($input, $output);
+ $this->io = $io;
try {
$result = $this->dbUpdater->checkDbUpdate($this);
diff --git a/module/CLI/test/Command/Api/InitialApiKeyCommandTest.php b/module/CLI/test/Command/Api/InitialApiKeyCommandTest.php
index e86cf0e5..b2311613 100644
--- a/module/CLI/test/Command/Api/InitialApiKeyCommandTest.php
+++ b/module/CLI/test/Command/Api/InitialApiKeyCommandTest.php
@@ -35,7 +35,7 @@ class InitialApiKeyCommandTest extends TestCase
$this->apiKeyService->expects($this->once())->method('createInitial')->with('the_key')->willReturn($result);
$this->commandTester->execute(
- ['apiKey' => 'the_key'],
+ ['api-key' => 'the_key'],
['verbosity' => $verbose ? OutputInterface::VERBOSITY_VERBOSE : OutputInterface::VERBOSITY_NORMAL],
);
$output = $this->commandTester->getDisplay();
diff --git a/module/CLI/test/Command/Config/ReadEnvVarCommandTest.php b/module/CLI/test/Command/Config/ReadEnvVarCommandTest.php
index c377cf86..e90f94af 100644
--- a/module/CLI/test/Command/Config/ReadEnvVarCommandTest.php
+++ b/module/CLI/test/Command/Config/ReadEnvVarCommandTest.php
@@ -28,13 +28,13 @@ class ReadEnvVarCommandTest extends TestCase
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('foo is not a valid Shlink environment variable');
- $this->commandTester->execute(['envVar' => 'foo']);
+ $this->commandTester->execute(['env-var' => 'foo']);
}
#[Test]
public function valueIsPrintedIfProvidedEnvVarIsValid(): void
{
- $this->commandTester->execute(['envVar' => EnvVars::BASE_PATH->value]);
+ $this->commandTester->execute(['env-var' => EnvVars::BASE_PATH->value]);
$output = $this->commandTester->getDisplay();
self::assertStringNotContainsString('Select the env var to read', $output);
diff --git a/module/CLI/test/Command/Tag/RenameTagCommandTest.php b/module/CLI/test/Command/Tag/RenameTagCommandTest.php
index e7fb630d..8681239a 100644
--- a/module/CLI/test/Command/Tag/RenameTagCommandTest.php
+++ b/module/CLI/test/Command/Tag/RenameTagCommandTest.php
@@ -36,8 +36,8 @@ class RenameTagCommandTest extends TestCase
)->willThrowException(TagNotFoundException::fromTag('foo'));
$this->commandTester->execute([
- 'oldName' => $oldName,
- 'newName' => $newName,
+ 'old-name' => $oldName,
+ 'new-name' => $newName,
]);
$output = $this->commandTester->getDisplay();
@@ -54,8 +54,8 @@ class RenameTagCommandTest extends TestCase
)->willReturn(new Tag($newName));
$this->commandTester->execute([
- 'oldName' => $oldName,
- 'newName' => $newName,
+ 'old-name' => $oldName,
+ 'new-name' => $newName,
]);
$output = $this->commandTester->getDisplay();