From 635e968bb2bbbe72a2fb474fa6fddfde4d69d067 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 15 Dec 2025 09:35:38 +0100 Subject: [PATCH] Convert EditShortUrlCommand into invokable command --- .../Command/ShortUrl/EditShortUrlCommand.php | 46 +++---- module/CLI/src/Input/ShortUrlDataInput.php | 128 ------------------ module/CLI/src/Input/ShortUrlDataOption.php | 39 ------ module/CLI/src/Input/TagsOption.php | 41 ------ .../ShortUrl/EditShortUrlCommandTest.php | 4 +- 5 files changed, 22 insertions(+), 236 deletions(-) delete mode 100644 module/CLI/src/Input/ShortUrlDataInput.php delete mode 100644 module/CLI/src/Input/ShortUrlDataOption.php delete mode 100644 module/CLI/src/Input/TagsOption.php diff --git a/module/CLI/src/Command/ShortUrl/EditShortUrlCommand.php b/module/CLI/src/Command/ShortUrl/EditShortUrlCommand.php index 16fe9458..7bdd82e2 100644 --- a/module/CLI/src/Command/ShortUrl/EditShortUrlCommand.php +++ b/module/CLI/src/Command/ShortUrl/EditShortUrlCommand.php @@ -4,55 +4,49 @@ declare(strict_types=1); namespace Shlinkio\Shlink\CLI\Command\ShortUrl; -use Shlinkio\Shlink\CLI\Input\ShortUrlDataInput; -use Shlinkio\Shlink\CLI\Input\ShortUrlIdentifierInput; +use Shlinkio\Shlink\CLI\Command\ShortUrl\Input\ShortUrlDataInput; use Shlinkio\Shlink\Core\Exception\ShortUrlNotFoundException; use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifierInterface; +use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlEdition; +use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlIdentifier; use Shlinkio\Shlink\Core\ShortUrl\ShortUrlServiceInterface; +use Symfony\Component\Console\Attribute\Argument; +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Attribute\MapInput; +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( + name: EditShortUrlCommand::NAME, + description: 'Edit an existing short URL', +)] class EditShortUrlCommand extends Command { public const string NAME = 'short-url:edit'; - private readonly ShortUrlDataInput $shortUrlDataInput; - private readonly ShortUrlIdentifierInput $shortUrlIdentifierInput; - public function __construct( private readonly ShortUrlServiceInterface $shortUrlService, private readonly ShortUrlStringifierInterface $stringifier, ) { parent::__construct(); - - $this->shortUrlDataInput = new ShortUrlDataInput($this, longUrlAsOption: true); - $this->shortUrlIdentifierInput = new ShortUrlIdentifierInput( - $this, - shortCodeDesc: 'The short code to edit', - domainDesc: 'The domain to which the short URL is attached.', - ); } - protected function configure(): void - { - $this - ->setName(self::NAME) - ->setDescription('Edit an existing short URL'); - } - - protected function execute(InputInterface $input, OutputInterface $output): int - { - $io = new SymfonyStyle($input, $output); - $identifier = $this->shortUrlIdentifierInput->toShortUrlIdentifier($input); + public function __invoke( + SymfonyStyle $io, + #[Argument('The short code to edit')] string $shortCode, + #[MapInput] ShortUrlDataInput $data, + #[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 { + $identifier = ShortUrlIdentifier::fromShortCodeAndDomain($shortCode, $domain); try { $shortUrl = $this->shortUrlService->updateShortUrl( $identifier, - $this->shortUrlDataInput->toShortUrlEdition($input), + ShortUrlEdition::fromRawData($data->toArray()), ); $io->success(sprintf('Short URL "%s" properly edited', $this->stringifier->stringify($shortUrl))); diff --git a/module/CLI/src/Input/ShortUrlDataInput.php b/module/CLI/src/Input/ShortUrlDataInput.php deleted file mode 100644 index d67bdd31..00000000 --- a/module/CLI/src/Input/ShortUrlDataInput.php +++ /dev/null @@ -1,128 +0,0 @@ -addOption('long-url', 'l', InputOption::VALUE_REQUIRED, 'The long URL to set'); - } else { - $command->addArgument('longUrl', InputArgument::REQUIRED, 'The long URL to set'); - } - - $this->tagsOption = new TagsOption($command, 'Tags to apply to the short URL'); - - $command - ->addOption( - ShortUrlDataOption::VALID_SINCE->value, - ShortUrlDataOption::VALID_SINCE->shortcut(), - InputOption::VALUE_REQUIRED, - 'The date from which this short URL will be valid. ' - . 'If someone tries to access it before this date, it will not be found.', - ) - ->addOption( - ShortUrlDataOption::VALID_UNTIL->value, - ShortUrlDataOption::VALID_UNTIL->shortcut(), - InputOption::VALUE_REQUIRED, - 'The date until which this short URL will be valid. ' - . 'If someone tries to access it after this date, it will not be found.', - ) - ->addOption( - ShortUrlDataOption::MAX_VISITS->value, - ShortUrlDataOption::MAX_VISITS->shortcut(), - InputOption::VALUE_REQUIRED, - 'This will limit the number of visits for this short URL.', - ) - ->addOption( - ShortUrlDataOption::TITLE->value, - ShortUrlDataOption::TITLE->shortcut(), - InputOption::VALUE_REQUIRED, - 'A descriptive title for the short URL.', - ) - ->addOption( - ShortUrlDataOption::CRAWLABLE->value, - ShortUrlDataOption::CRAWLABLE->shortcut(), - InputOption::VALUE_NONE, - 'Tells if this short URL will be included as "Allow" in Shlink\'s robots.txt.', - ) - ->addOption( - ShortUrlDataOption::NO_FORWARD_QUERY->value, - ShortUrlDataOption::NO_FORWARD_QUERY->shortcut(), - InputOption::VALUE_NONE, - 'Disables the forwarding of the query string to the long URL, when the short URL is visited.', - ); - } - - public function toShortUrlEdition(InputInterface $input): ShortUrlEdition - { - return ShortUrlEdition::fromRawData($this->getCommonData($input)); - } - - public function toShortUrlCreation( - InputInterface $input, - UrlShortenerOptions $options, - string $customSlugField, - string $shortCodeLengthField, - string $pathPrefixField, - string $findIfExistsField, - string $domainField, - ): ShortUrlCreation { - $shortCodeLength = $input->getOption($shortCodeLengthField) ?? $options->defaultShortCodesLength; - return ShortUrlCreation::fromRawData([ - ...$this->getCommonData($input), - ShortUrlInputFilter::CUSTOM_SLUG => $input->getOption($customSlugField), - ShortUrlInputFilter::SHORT_CODE_LENGTH => $shortCodeLength, - ShortUrlInputFilter::PATH_PREFIX => $input->getOption($pathPrefixField), - ShortUrlInputFilter::FIND_IF_EXISTS => $input->getOption($findIfExistsField), - ShortUrlInputFilter::DOMAIN => $input->getOption($domainField), - ], $options); - } - - private function getCommonData(InputInterface $input): array - { - $longUrl = $this->longUrlAsOption ? $input->getOption('long-url') : $input->getArgument('longUrl'); - $data = [ShortUrlInputFilter::LONG_URL => $longUrl]; - - // Avoid setting arguments that were not explicitly provided. - // This is important when editing short URLs and should not make a difference when creating. - if (ShortUrlDataOption::VALID_SINCE->wasProvided($input)) { - $data[ShortUrlInputFilter::VALID_SINCE] = $input->getOption('valid-since'); - } - if (ShortUrlDataOption::VALID_UNTIL->wasProvided($input)) { - $data[ShortUrlInputFilter::VALID_UNTIL] = $input->getOption('valid-until'); - } - if (ShortUrlDataOption::MAX_VISITS->wasProvided($input)) { - $maxVisits = $input->getOption('max-visits'); - $data[ShortUrlInputFilter::MAX_VISITS] = $maxVisits !== null ? (int) $maxVisits : null; - } - if ($this->tagsOption->exists($input)) { - $data[ShortUrlInputFilter::TAGS] = $this->tagsOption->get($input); - } - if (ShortUrlDataOption::TITLE->wasProvided($input)) { - $data[ShortUrlInputFilter::TITLE] = $input->getOption('title'); - } - if (ShortUrlDataOption::CRAWLABLE->wasProvided($input)) { - $data[ShortUrlInputFilter::CRAWLABLE] = $input->getOption('crawlable'); - } - if (ShortUrlDataOption::NO_FORWARD_QUERY->wasProvided($input)) { - $data[ShortUrlInputFilter::FORWARD_QUERY] = !$input->getOption('no-forward-query'); - } - - return $data; - } -} diff --git a/module/CLI/src/Input/ShortUrlDataOption.php b/module/CLI/src/Input/ShortUrlDataOption.php deleted file mode 100644 index 4d8b582e..00000000 --- a/module/CLI/src/Input/ShortUrlDataOption.php +++ /dev/null @@ -1,39 +0,0 @@ - 's', - self::VALID_UNTIL => 'u', - self::MAX_VISITS => 'm', - self::TITLE => null, - self::CRAWLABLE => 'r', - self::NO_FORWARD_QUERY => 'w', - }; - } - - public function wasProvided(InputInterface $input): bool - { - $option = sprintf('--%s', $this->value); - $shortcut = $this->shortcut(); - - return $input->hasParameterOption($shortcut === null ? $option : [$option, sprintf('-%s', $shortcut)]); - } -} diff --git a/module/CLI/src/Input/TagsOption.php b/module/CLI/src/Input/TagsOption.php deleted file mode 100644 index 1cdee7e8..00000000 --- a/module/CLI/src/Input/TagsOption.php +++ /dev/null @@ -1,41 +0,0 @@ -addOption( - 'tag', - 't', - InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, - $description, - ); - } - - /** - * Whether tags have been set or not, via `--tag` or `-t` - */ - public function exists(InputInterface $input): bool - { - return $input->hasParameterOption(['--tag', '-t']); - } - - /** - * @return string[] - */ - public function get(InputInterface $input): array - { - return array_unique($input->getOption('tag')); - } -} diff --git a/module/CLI/test/Command/ShortUrl/EditShortUrlCommandTest.php b/module/CLI/test/Command/ShortUrl/EditShortUrlCommandTest.php index 0fd9a860..ba021fe7 100644 --- a/module/CLI/test/Command/ShortUrl/EditShortUrlCommandTest.php +++ b/module/CLI/test/Command/ShortUrl/EditShortUrlCommandTest.php @@ -40,7 +40,7 @@ class EditShortUrlCommandTest extends TestCase ); $this->stringifier->expects($this->once())->method('stringify')->willReturn('https://s.test/foo'); - $this->commandTester->execute(['shortCode' => 'foobar']); + $this->commandTester->execute(['short-code' => 'foobar']); $output = $this->commandTester->getDisplay(); $exitCode = $this->commandTester->getStatusCode(); @@ -59,7 +59,7 @@ class EditShortUrlCommandTest extends TestCase $this->shortUrlService->expects($this->once())->method('updateShortUrl')->willThrowException($e); $this->stringifier->expects($this->never())->method('stringify'); - $this->commandTester->execute(['shortCode' => 'foo'], ['verbosity' => $verbosity]); + $this->commandTester->execute(['short-code' => 'foo'], ['verbosity' => $verbosity]); $output = $this->commandTester->getDisplay(); $exitCode = $this->commandTester->getStatusCode();