diff --git a/module/CLI/src/Command/ShortUrl/DeleteShortUrlCommand.php b/module/CLI/src/Command/ShortUrl/DeleteShortUrlCommand.php
index e6a11ea1..c3b4ba0c 100644
--- a/module/CLI/src/Command/ShortUrl/DeleteShortUrlCommand.php
+++ b/module/CLI/src/Command/ShortUrl/DeleteShortUrlCommand.php
@@ -4,53 +4,40 @@ declare(strict_types=1);
namespace Shlinkio\Shlink\CLI\Command\ShortUrl;
-use Shlinkio\Shlink\CLI\Input\ShortUrlIdentifierInput;
use Shlinkio\Shlink\Core\Exception;
use Shlinkio\Shlink\Core\ShortUrl\DeleteShortUrlServiceInterface;
use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlIdentifier;
+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\InputInterface;
-use Symfony\Component\Console\Input\InputOption;
-use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use function sprintf;
+#[AsCommand(name: DeleteShortUrlCommand::NAME, description: 'Deletes a short URL')]
class DeleteShortUrlCommand extends Command
{
public const string NAME = 'short-url:delete';
- private readonly ShortUrlIdentifierInput $shortUrlIdentifierInput;
-
public function __construct(private readonly DeleteShortUrlServiceInterface $deleteShortUrlService)
{
parent::__construct();
- $this->shortUrlIdentifierInput = new ShortUrlIdentifierInput(
- $this,
- shortCodeDesc: 'The short code for the short URL to be deleted',
- domainDesc: 'The domain if the short code does not belong to the default one',
- );
}
- protected function configure(): void
- {
- $this
- ->setName(self::NAME)
- ->setDescription('Deletes a short URL')
- ->addOption(
- 'ignore-threshold',
- 'i',
- InputOption::VALUE_NONE,
- 'Ignores the safety visits threshold check, which could make short URLs with many visits to be '
- . 'accidentally deleted',
- );
- }
-
- protected function execute(InputInterface $input, OutputInterface $output): int
- {
- $io = new SymfonyStyle($input, $output);
- $identifier = $this->shortUrlIdentifierInput->toShortUrlIdentifier($input);
- $ignoreThreshold = $input->getOption('ignore-threshold');
+ public function __invoke(
+ SymfonyStyle $io,
+ #[Argument('The short code for the short URL to be deleted')] string $shortCode,
+ #[Option('The domain if the short code does not belong to the default one', shortcut: 'd')]
+ string|null $domain = null,
+ #[Option(
+ 'Ignores the safety visits threshold check, which could make short URLs with many visits to be '
+ . 'accidentally deleted',
+ shortcut: 'i',
+ )]
+ bool $ignoreThreshold = false,
+ ): int {
+ $identifier = ShortUrlIdentifier::fromShortCodeAndDomain($shortCode, $domain);
try {
$this->runDelete($io, $identifier, $ignoreThreshold);
diff --git a/module/CLI/src/Command/ShortUrl/DeleteShortUrlVisitsCommand.php b/module/CLI/src/Command/ShortUrl/DeleteShortUrlVisitsCommand.php
index 4c238f31..16667eb3 100644
--- a/module/CLI/src/Command/ShortUrl/DeleteShortUrlVisitsCommand.php
+++ b/module/CLI/src/Command/ShortUrl/DeleteShortUrlVisitsCommand.php
@@ -4,41 +4,44 @@ declare(strict_types=1);
namespace Shlinkio\Shlink\CLI\Command\ShortUrl;
-use Shlinkio\Shlink\CLI\Command\Visit\AbstractDeleteVisitsCommand;
-use Shlinkio\Shlink\CLI\Input\ShortUrlIdentifierInput;
+use Shlinkio\Shlink\CLI\Command\Util\CommandUtils;
use Shlinkio\Shlink\Core\Exception\ShortUrlNotFoundException;
+use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlIdentifier;
use Shlinkio\Shlink\Core\ShortUrl\ShortUrlVisitsDeleterInterface;
-use Symfony\Component\Console\Input\InputInterface;
+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\Style\SymfonyStyle;
use function sprintf;
-class DeleteShortUrlVisitsCommand extends AbstractDeleteVisitsCommand
+#[AsCommand(DeleteShortUrlVisitsCommand::NAME, 'Deletes visits from a short URL')]
+class DeleteShortUrlVisitsCommand extends Command
{
public const string NAME = 'short-url:visits-delete';
- private readonly ShortUrlIdentifierInput $shortUrlIdentifierInput;
-
public function __construct(private readonly ShortUrlVisitsDeleterInterface $deleter)
{
parent::__construct();
- $this->shortUrlIdentifierInput = new ShortUrlIdentifierInput(
- $this,
- shortCodeDesc: 'The short code for the short URL which visits will be deleted',
- domainDesc: 'The domain if the short code does not belong to the default one',
+ }
+
+ public function __invoke(
+ SymfonyStyle $io,
+ #[Argument('The short code for the short URL which visits will be deleted')] string $shortCode,
+ #[Option('The domain if the short code does not belong to the default one', shortcut: 'd')]
+ string|null $domain = null,
+ ): int {
+ $identifier = ShortUrlIdentifier::fromShortCodeAndDomain($shortCode, $domain);
+ return CommandUtils::executeWithWarning(
+ 'You are about to delete all visits for a short URL. This operation cannot be undone',
+ $io,
+ fn () => $this->deleteVisits($io, $identifier),
);
}
- protected function configure(): void
+ private function deleteVisits(SymfonyStyle $io, ShortUrlIdentifier $identifier): int
{
- $this
- ->setName(self::NAME)
- ->setDescription('Deletes visits from a short URL');
- }
-
- protected function doExecute(InputInterface $input, SymfonyStyle $io): int
- {
- $identifier = $this->shortUrlIdentifierInput->toShortUrlIdentifier($input);
try {
$result = $this->deleter->deleteShortUrlVisits($identifier);
$io->success(sprintf('Successfully deleted %s visits', $result->affectedItems));
@@ -49,9 +52,4 @@ class DeleteShortUrlVisitsCommand extends AbstractDeleteVisitsCommand
return self::INVALID;
}
}
-
- protected function getWarningMessage(): string
- {
- return 'You are about to delete all visits for a short URL. This operation cannot be undone.';
- }
}
diff --git a/module/CLI/src/Command/ShortUrl/EditShortUrlCommand.php b/module/CLI/src/Command/ShortUrl/EditShortUrlCommand.php
index 7bdd82e2..f4e3b06e 100644
--- a/module/CLI/src/Command/ShortUrl/EditShortUrlCommand.php
+++ b/module/CLI/src/Command/ShortUrl/EditShortUrlCommand.php
@@ -36,8 +36,8 @@ class EditShortUrlCommand extends Command
public function __invoke(
SymfonyStyle $io,
- #[Argument('The short code to edit')] string $shortCode,
#[MapInput] ShortUrlDataInput $data,
+ #[Argument('The short code to edit')] string $shortCode,
#[Option('The domain to which the short URL is attached', shortcut: 'd')] string|null $domain = null,
#[Option('The long URL to set', shortcut: 'l')] string|null $longUrl = null,
): int {
diff --git a/module/CLI/src/Command/ShortUrl/ResolveUrlCommand.php b/module/CLI/src/Command/ShortUrl/ResolveUrlCommand.php
index b6bf71f7..b70ba70e 100644
--- a/module/CLI/src/Command/ShortUrl/ResolveUrlCommand.php
+++ b/module/CLI/src/Command/ShortUrl/ResolveUrlCommand.php
@@ -4,60 +4,42 @@ declare(strict_types=1);
namespace Shlinkio\Shlink\CLI\Command\ShortUrl;
-use Shlinkio\Shlink\CLI\Input\ShortUrlIdentifierInput;
use Shlinkio\Shlink\Core\Exception\ShortUrlNotFoundException;
+use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlIdentifier;
use Shlinkio\Shlink\Core\ShortUrl\ShortUrlResolverInterface;
+use Symfony\Component\Console\Attribute\Argument;
+use Symfony\Component\Console\Attribute\AsCommand;
+use Symfony\Component\Console\Attribute\Ask;
+use Symfony\Component\Console\Attribute\Option;
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 sprintf;
+#[AsCommand(ResolveUrlCommand::NAME, 'Returns the long URL behind a short code')]
class ResolveUrlCommand extends Command
{
- public const string NAME = 'short-url:parse';
-
- private readonly ShortUrlIdentifierInput $shortUrlIdentifierInput;
+ public const string NAME = 'short-url:resolve';
public function __construct(private readonly ShortUrlResolverInterface $urlResolver)
{
parent::__construct();
- $this->shortUrlIdentifierInput = new ShortUrlIdentifierInput(
- $this,
- shortCodeDesc: 'The short code to parse',
- domainDesc: 'The domain to which the short URL is attached.',
- );
}
- protected function configure(): void
- {
- $this
- ->setName(self::NAME)
- ->setDescription('Returns the long URL behind a short code');
- }
-
- protected function interact(InputInterface $input, OutputInterface $output): void
- {
- $shortCode = $this->shortUrlIdentifierInput->shortCode($input);
- if (! empty($shortCode)) {
- return;
- }
-
- $io = new SymfonyStyle($input, $output);
- $shortCode = $io->ask('A short code was not provided. Which short code do you want to parse?');
- if (! empty($shortCode)) {
- $input->setArgument('shortCode', $shortCode);
- }
- }
-
- protected function execute(InputInterface $input, OutputInterface $output): int
- {
- $io = new SymfonyStyle($input, $output);
+ public function __invoke(
+ SymfonyStyle $io,
+ #[
+ Argument('The short code to resolve'),
+ Ask('A short code was not provided. Which short code do you want to resolve?'),
+ ]
+ string $shortCode,
+ #[Option('The domain to which the short URL is attached', shortcut: 'd')] string|null $domain = null,
+ ): int {
+ $identifier = ShortUrlIdentifier::fromShortCodeAndDomain($shortCode, $domain);
try {
- $url = $this->urlResolver->resolveShortUrl($this->shortUrlIdentifierInput->toShortUrlIdentifier($input));
- $output->writeln(sprintf('Long URL: %s', $url->getLongUrl()));
+ $url = $this->urlResolver->resolveShortUrl($identifier);
+ $io->writeln(sprintf('Long URL: %s', $url->getLongUrl()));
return self::SUCCESS;
} catch (ShortUrlNotFoundException $e) {
$io->error($e->getMessage());
diff --git a/module/CLI/src/Command/Util/CommandUtils.php b/module/CLI/src/Command/Util/CommandUtils.php
new file mode 100644
index 00000000..76085f1a
--- /dev/null
+++ b/module/CLI/src/Command/Util/CommandUtils.php
@@ -0,0 +1,28 @@
+warning($warning);
+ if (! $io->confirm('Do you want to proceed?', default: false)) {
+ $io->info('Operation aborted');
+ return Command::SUCCESS;
+ }
+
+ return $callback();
+ }
+}
diff --git a/module/CLI/src/Command/Visit/AbstractDeleteVisitsCommand.php b/module/CLI/src/Command/Visit/AbstractDeleteVisitsCommand.php
deleted file mode 100644
index d8ef98e3..00000000
--- a/module/CLI/src/Command/Visit/AbstractDeleteVisitsCommand.php
+++ /dev/null
@@ -1,34 +0,0 @@
-confirm($io)) {
- $io->info('Operation aborted');
- return self::SUCCESS;
- }
-
- return $this->doExecute($input, $io);
- }
-
- private function confirm(SymfonyStyle $io): bool
- {
- $io->warning($this->getWarningMessage());
- return $io->confirm('Continue deleting visits?', false);
- }
-
- abstract protected function doExecute(InputInterface $input, SymfonyStyle $io): int;
-
- abstract protected function getWarningMessage(): string;
-}
diff --git a/module/CLI/src/Command/Visit/DeleteOrphanVisitsCommand.php b/module/CLI/src/Command/Visit/DeleteOrphanVisitsCommand.php
index 77fefaaa..654b92bf 100644
--- a/module/CLI/src/Command/Visit/DeleteOrphanVisitsCommand.php
+++ b/module/CLI/src/Command/Visit/DeleteOrphanVisitsCommand.php
@@ -4,13 +4,16 @@ declare(strict_types=1);
namespace Shlinkio\Shlink\CLI\Command\Visit;
+use Shlinkio\Shlink\CLI\Command\Util\CommandUtils;
use Shlinkio\Shlink\Core\Visit\VisitsDeleterInterface;
-use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Attribute\AsCommand;
+use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Style\SymfonyStyle;
use function sprintf;
-class DeleteOrphanVisitsCommand extends AbstractDeleteVisitsCommand
+#[AsCommand(DeleteOrphanVisitsCommand::NAME, 'Deletes all orphan visits')]
+class DeleteOrphanVisitsCommand extends Command
{
public const string NAME = 'visit:orphan-delete';
@@ -19,23 +22,20 @@ class DeleteOrphanVisitsCommand extends AbstractDeleteVisitsCommand
parent::__construct();
}
- protected function configure(): void
+ public function __invoke(SymfonyStyle $io): int
{
- $this
- ->setName(self::NAME)
- ->setDescription('Deletes all orphan visits');
+ return CommandUtils::executeWithWarning(
+ 'You are about to delete all orphan visits. This operation cannot be undone',
+ $io,
+ fn () => $this->deleteVisits($io),
+ );
}
- protected function doExecute(InputInterface $input, SymfonyStyle $io): int
+ private function deleteVisits(SymfonyStyle $io): int
{
$result = $this->deleter->deleteOrphanVisits();
$io->success(sprintf('Successfully deleted %s visits', $result->affectedItems));
return self::SUCCESS;
}
-
- protected function getWarningMessage(): string
- {
- return 'You are about to delete all orphan visits. This operation cannot be undone.';
- }
}
diff --git a/module/CLI/test/Command/ShortUrl/DeleteShortUrlCommandTest.php b/module/CLI/test/Command/ShortUrl/DeleteShortUrlCommandTest.php
index 62872123..c8d94388 100644
--- a/module/CLI/test/Command/ShortUrl/DeleteShortUrlCommandTest.php
+++ b/module/CLI/test/Command/ShortUrl/DeleteShortUrlCommandTest.php
@@ -39,7 +39,7 @@ class DeleteShortUrlCommandTest extends TestCase
$this->isFalse(),
);
- $this->commandTester->execute(['shortCode' => $shortCode]);
+ $this->commandTester->execute(['short-code' => $shortCode]);
$output = $this->commandTester->getDisplay();
self::assertStringContainsString(
@@ -58,7 +58,7 @@ class DeleteShortUrlCommandTest extends TestCase
$this->isFalse(),
)->willThrowException(Exception\ShortUrlNotFoundException::fromNotFound($identifier));
- $this->commandTester->execute(['shortCode' => $shortCode]);
+ $this->commandTester->execute(['short-code' => $shortCode]);
$output = $this->commandTester->getDisplay();
self::assertStringContainsString(sprintf('No URL found with short code "%s"', $shortCode), $output);
@@ -88,7 +88,7 @@ class DeleteShortUrlCommandTest extends TestCase
});
$this->commandTester->setInputs($retryAnswer);
- $this->commandTester->execute(['shortCode' => $shortCode]);
+ $this->commandTester->execute(['short-code' => $shortCode]);
$output = $this->commandTester->getDisplay();
self::assertStringContainsString(sprintf(
@@ -118,7 +118,7 @@ class DeleteShortUrlCommandTest extends TestCase
));
$this->commandTester->setInputs(['no']);
- $this->commandTester->execute(['shortCode' => $shortCode]);
+ $this->commandTester->execute(['short-code' => $shortCode]);
$output = $this->commandTester->getDisplay();
self::assertStringContainsString(sprintf(
diff --git a/module/CLI/test/Command/ShortUrl/DeleteShortUrlVisitsCommandTest.php b/module/CLI/test/Command/ShortUrl/DeleteShortUrlVisitsCommandTest.php
index 3efa94b5..6f066d0f 100644
--- a/module/CLI/test/Command/ShortUrl/DeleteShortUrlVisitsCommandTest.php
+++ b/module/CLI/test/Command/ShortUrl/DeleteShortUrlVisitsCommandTest.php
@@ -36,7 +36,7 @@ class DeleteShortUrlVisitsCommandTest extends TestCase
$this->deleter->expects($this->never())->method('deleteShortUrlVisits');
$this->commandTester->setInputs($input);
- $exitCode = $this->commandTester->execute(['shortCode' => 'foo']);
+ $exitCode = $this->commandTester->execute(['short-code' => 'foo']);
$output = $this->commandTester->getDisplay();
self::assertEquals(Command::SUCCESS, $exitCode);
@@ -67,8 +67,8 @@ class DeleteShortUrlVisitsCommandTest extends TestCase
public static function provideErrorArgs(): iterable
{
- yield 'domain' => [['shortCode' => 'foo'], 'Short URL not found for "foo"'];
- yield 'no domain' => [['shortCode' => 'foo', '--domain' => 's.test'], 'Short URL not found for "s.test/foo"'];
+ yield 'domain' => [['short-code' => 'foo'], 'Short URL not found for "foo"'];
+ yield 'no domain' => [['short-code' => 'foo', '--domain' => 's.test'], 'Short URL not found for "s.test/foo"'];
}
#[Test]
@@ -77,7 +77,7 @@ class DeleteShortUrlVisitsCommandTest extends TestCase
$this->deleter->expects($this->once())->method('deleteShortUrlVisits')->willReturn(new BulkDeleteResult(5));
$this->commandTester->setInputs(['yes']);
- $exitCode = $this->commandTester->execute(['shortCode' => 'foo']);
+ $exitCode = $this->commandTester->execute(['short-code' => 'foo']);
$output = $this->commandTester->getDisplay();
self::assertEquals(Command::SUCCESS, $exitCode);
diff --git a/module/CLI/test/Command/ShortUrl/ResolveUrlCommandTest.php b/module/CLI/test/Command/ShortUrl/ResolveUrlCommandTest.php
index 742ae05c..30060dd6 100644
--- a/module/CLI/test/Command/ShortUrl/ResolveUrlCommandTest.php
+++ b/module/CLI/test/Command/ShortUrl/ResolveUrlCommandTest.php
@@ -40,7 +40,7 @@ class ResolveUrlCommandTest extends TestCase
ShortUrlIdentifier::fromShortCodeAndDomain($shortCode),
)->willReturn($shortUrl);
- $this->commandTester->execute(['shortCode' => $shortCode]);
+ $this->commandTester->execute(['short-code' => $shortCode]);
$output = $this->commandTester->getDisplay();
self::assertEquals('Long URL: ' . $expectedUrl . PHP_EOL, $output);
}
@@ -68,7 +68,7 @@ class ResolveUrlCommandTest extends TestCase
ShortUrlNotFoundException::fromNotFound($identifier),
);
- $this->commandTester->execute(['shortCode' => $shortCode]);
+ $this->commandTester->execute(['short-code' => $shortCode]);
$output = $this->commandTester->getDisplay();
self::assertStringContainsString(sprintf('No URL found with short code "%s"', $shortCode), $output);
}