diff --git a/composer.json b/composer.json index 8755c856..eeda898b 100644 --- a/composer.json +++ b/composer.json @@ -50,9 +50,9 @@ "shlinkio/shlink-json": "dev-main#7c096d6 as 1.3.0", "spiral/roadrunner": "^2025.1", "spiral/roadrunner-cli": "^2.7", - "spiral/roadrunner-http": "^3.5", - "spiral/roadrunner-jobs": "^4.6", - "symfony/console": "^8.0 || ^7.4", + "spiral/roadrunner-http": "^3.6", + "spiral/roadrunner-jobs": "^4.7", + "symfony/console": "^8.0", "symfony/filesystem": "^8.0", "symfony/lock": "^8.0", "symfony/process": "^8.0", diff --git a/module/CLI/src/Command/Domain/DomainRedirectsCommand.php b/module/CLI/src/Command/Domain/DomainRedirectsCommand.php index fc94ee49..59deb35e 100644 --- a/module/CLI/src/Command/Domain/DomainRedirectsCommand.php +++ b/module/CLI/src/Command/Domain/DomainRedirectsCommand.php @@ -9,9 +9,9 @@ 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\Attribute\Interact; 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_filter; @@ -32,7 +32,8 @@ class DomainRedirectsCommand extends Command parent::__construct(); } - protected function interact(InputInterface $input, OutputInterface $output): void + #[Interact] + public function askDomain(InputInterface $input, SymfonyStyle $io): void { /** @var string|null $domain */ $domain = $input->getArgument('domain'); @@ -40,7 +41,6 @@ class DomainRedirectsCommand extends Command return; } - $io = new SymfonyStyle($input, $output); $askNewDomain = static fn () => $io->ask('Domain authority for which you want to set specific redirects'); /** @var string[] $availableDomains */ diff --git a/module/CLI/src/Command/Integration/MatomoSendVisitsCommand.php b/module/CLI/src/Command/Integration/MatomoSendVisitsCommand.php index f5d8e84c..b45c0135 100644 --- a/module/CLI/src/Command/Integration/MatomoSendVisitsCommand.php +++ b/module/CLI/src/Command/Integration/MatomoSendVisitsCommand.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace Shlinkio\Shlink\CLI\Command\Integration; -use Cake\Chronos\Chronos; use Shlinkio\Shlink\Core\Matomo\MatomoOptions; use Shlinkio\Shlink\Core\Matomo\MatomoVisitSenderInterface; use Shlinkio\Shlink\Core\Matomo\VisitSendingProgressTrackerInterface; @@ -17,10 +16,12 @@ use Throwable; use function Shlinkio\Shlink\Common\buildDateRange; use function Shlinkio\Shlink\Core\dateRangeToHumanFriendly; +use function Shlinkio\Shlink\Core\normalizeOptionalDate; use function sprintf; #[AsCommand( name: MatomoSendVisitsCommand::NAME, + description: 'Send existing visits to the configured matomo instance', help: <<setDescription(sprintf( - '%sSend existing visits to the configured matomo instance', - $this->matomoEnabled ? '' : '[MATOMO INTEGRATION DISABLED] ', - )); - } - public function __invoke( SymfonyStyle $io, InputInterface $input, @@ -81,8 +74,8 @@ class MatomoSendVisitsCommand extends Command implements VisitSendingProgressTra // TODO Validate provided date formats $dateRange = buildDateRange( - startDate: $since !== null ? Chronos::parse($since) : null, - endDate: $until !== null ? Chronos::parse($until) : null, + startDate: normalizeOptionalDate($since), + endDate: normalizeOptionalDate($until), ); if ($input->isInteractive()) { diff --git a/module/CLI/src/Command/RedirectRule/ManageRedirectRulesCommand.php b/module/CLI/src/Command/RedirectRule/ManageRedirectRulesCommand.php index 9a129e9d..81e41497 100644 --- a/module/CLI/src/Command/RedirectRule/ManageRedirectRulesCommand.php +++ b/module/CLI/src/Command/RedirectRule/ManageRedirectRulesCommand.php @@ -4,48 +4,41 @@ declare(strict_types=1); namespace Shlinkio\Shlink\CLI\Command\RedirectRule; -use Shlinkio\Shlink\CLI\Input\ShortUrlIdentifierInput; use Shlinkio\Shlink\CLI\RedirectRule\RedirectRuleHandlerInterface; use Shlinkio\Shlink\Core\Exception\ShortUrlNotFoundException; use Shlinkio\Shlink\Core\RedirectRule\ShortUrlRedirectRuleServiceInterface; +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\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: ManageRedirectRulesCommand::NAME, + description: 'Set redirect rules for a short URL', +)] class ManageRedirectRulesCommand extends Command { public const string NAME = 'short-url:manage-rules'; - private readonly ShortUrlIdentifierInput $shortUrlIdentifierInput; - public function __construct( protected readonly ShortUrlResolverInterface $shortUrlResolver, protected readonly ShortUrlRedirectRuleServiceInterface $ruleService, protected readonly RedirectRuleHandlerInterface $ruleHandler, ) { parent::__construct(); - $this->shortUrlIdentifierInput = new ShortUrlIdentifierInput( - $this, - shortCodeDesc: 'The short code which rules we want to set.', - domainDesc: 'The domain for the short code.', - ); } - protected function configure(): void - { - $this - ->setName(self::NAME) - ->setDescription('Set redirect rules for a 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 which rules we want to set')] string $shortCode, + #[Option('The domain of the short code', shortcut: 'd')] string|null $domain = null, + ): int { + $identifier = ShortUrlIdentifier::fromShortCodeAndDomain($shortCode, $domain); try { $shortUrl = $this->shortUrlResolver->resolveShortUrl($identifier); diff --git a/module/CLI/src/Command/ShortUrl/CreateShortUrlCommand.php b/module/CLI/src/Command/ShortUrl/CreateShortUrlCommand.php index 2e52571b..a5133d74 100644 --- a/module/CLI/src/Command/ShortUrl/CreateShortUrlCommand.php +++ b/module/CLI/src/Command/ShortUrl/CreateShortUrlCommand.php @@ -4,109 +4,42 @@ declare(strict_types=1); namespace Shlinkio\Shlink\CLI\Command\ShortUrl; -use Shlinkio\Shlink\CLI\Input\ShortUrlDataInput; +use Shlinkio\Shlink\CLI\Command\ShortUrl\Input\ShortUrlCreationInput; use Shlinkio\Shlink\Core\Config\Options\UrlShortenerOptions; use Shlinkio\Shlink\Core\Exception\NonUniqueSlugException; use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifierInterface; use Shlinkio\Shlink\Core\ShortUrl\UrlShortenerInterface; +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Attribute\MapInput; 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: CreateShortUrlCommand::NAME, + description: 'Generates a short URL for provided long URL and returns it', +)] class CreateShortUrlCommand extends Command { public const string NAME = 'short-url:create'; - private SymfonyStyle $io; - private readonly ShortUrlDataInput $shortUrlDataInput; - public function __construct( private readonly UrlShortenerInterface $urlShortener, private readonly ShortUrlStringifierInterface $stringifier, private readonly UrlShortenerOptions $options, ) { parent::__construct(); - $this->shortUrlDataInput = new ShortUrlDataInput($this); } - protected function configure(): void + public function __invoke(SymfonyStyle $io, #[MapInput] ShortUrlCreationInput $inputData): int { - $this - ->setName(self::NAME) - ->setDescription('Generates a short URL for provided long URL and returns it') - ->addOption( - 'domain', - 'd', - InputOption::VALUE_REQUIRED, - 'The domain to which this short URL will be attached.', - ) - ->addOption( - 'custom-slug', - 'c', - InputOption::VALUE_REQUIRED, - 'If provided, this slug will be used instead of generating a short code', - ) - ->addOption( - 'short-code-length', - 'l', - InputOption::VALUE_REQUIRED, - 'The length for generated short code (it will be ignored if --custom-slug was provided).', - ) - ->addOption( - 'path-prefix', - 'p', - InputOption::VALUE_REQUIRED, - 'Prefix to prepend before the generated short code or provided custom slug', - ) - ->addOption( - 'find-if-exists', - 'f', - InputOption::VALUE_NONE, - 'This will force existing matching URL to be returned if found, instead of creating a new one.', - ); - } - - protected function interact(InputInterface $input, OutputInterface $output): void - { - $this->verifyLongUrlArgument($input, $output); - } - - private function verifyLongUrlArgument(InputInterface $input, OutputInterface $output): void - { - $longUrl = $input->getArgument('longUrl'); - if (! empty($longUrl)) { - return; - } - - $io = $this->getIO($input, $output); - $longUrl = $io->ask('Which URL do you want to shorten?'); - if (! empty($longUrl)) { - $input->setArgument('longUrl', $longUrl); - } - } - - protected function execute(InputInterface $input, OutputInterface $output): int - { - $io = $this->getIO($input, $output); - try { - $result = $this->urlShortener->shorten($this->shortUrlDataInput->toShortUrlCreation( - $input, - $this->options, - customSlugField: 'custom-slug', - shortCodeLengthField: 'short-code-length', - pathPrefixField: 'path-prefix', - findIfExistsField: 'find-if-exists', - domainField: 'domain', - )); + $result = $this->urlShortener->shorten($inputData->toShortUrlCreation($this->options)); $result->onEventDispatchingError(static fn () => $io->isVerbose() && $io->warning( 'Short URL properly created, but the real-time updates cannot be notified when generating the ' - . 'short URL from the command line. Migrate to roadrunner in order to bypass this limitation.', + . 'short URL from the command line. Migrate to roadrunner in order to bypass this limitation.', )); $io->writeln([ @@ -119,9 +52,4 @@ class CreateShortUrlCommand extends Command return self::FAILURE; } } - - private function getIO(InputInterface $input, OutputInterface $output): SymfonyStyle - { - return $this->io ??= new SymfonyStyle($input, $output); - } } 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/Command/ShortUrl/Input/ShortUrlCreationInput.php b/module/CLI/src/Command/ShortUrl/Input/ShortUrlCreationInput.php new file mode 100644 index 00000000..729c8a94 --- /dev/null +++ b/module/CLI/src/Command/ShortUrl/Input/ShortUrlCreationInput.php @@ -0,0 +1,57 @@ +shortCodeLength ?? $options->defaultShortCodesLength; + return ShortUrlCreation::fromRawData([ + ShortUrlInputFilter::LONG_URL => $this->longUrl, + ShortUrlInputFilter::DOMAIN => $this->domain, + ShortUrlInputFilter::CUSTOM_SLUG => $this->customSlug, + ShortUrlInputFilter::SHORT_CODE_LENGTH => $shortCodeLength, + ShortUrlInputFilter::PATH_PREFIX => $this->pathPrefix, + ShortUrlInputFilter::FIND_IF_EXISTS => $this->findIfExists, + ...$this->commonData->toArray(), + ], $options); + } +} diff --git a/module/CLI/src/Command/ShortUrl/Input/ShortUrlDataInput.php b/module/CLI/src/Command/ShortUrl/Input/ShortUrlDataInput.php new file mode 100644 index 00000000..988e87a0 --- /dev/null +++ b/module/CLI/src/Command/ShortUrl/Input/ShortUrlDataInput.php @@ -0,0 +1,80 @@ +validSince !== null) { + $data[ShortUrlInputFilter::VALID_SINCE] = $this->validSince; + } + if ($this->validUntil !== null) { + $data[ShortUrlInputFilter::VALID_UNTIL] = $this->validUntil; + } + if ($this->maxVisits !== null) { + $data[ShortUrlInputFilter::MAX_VISITS] = $this->maxVisits; + } + if ($this->tags !== null) { + $data[ShortUrlInputFilter::TAGS] = array_unique($this->tags); + } + if ($this->title !== null) { + $data[ShortUrlInputFilter::TITLE] = $this->title; + } + if ($this->crawlable !== null) { + $data[ShortUrlInputFilter::CRAWLABLE] = $this->crawlable; + } + if ($this->noForwardQuery !== null) { + $data[ShortUrlInputFilter::FORWARD_QUERY] = !$this->noForwardQuery; + } + + return $data; + } +} diff --git a/module/CLI/src/Command/ShortUrl/Input/ShortUrlsParamsInput.php b/module/CLI/src/Command/ShortUrl/Input/ShortUrlsParamsInput.php index 29c185d0..01a0e2bd 100644 --- a/module/CLI/src/Command/ShortUrl/Input/ShortUrlsParamsInput.php +++ b/module/CLI/src/Command/ShortUrl/Input/ShortUrlsParamsInput.php @@ -45,16 +45,16 @@ final class ShortUrlsParamsInput )] public string|null $domain = null; - /** @var string[] */ + /** @var string[]|null */ #[Option('A list of tags that short URLs need to include', name: 'tag', shortcut: 't')] - public array $tags = []; + public array|null $tags = null; #[Option('If --tag is provided, returns only short URLs including ALL of them')] public bool $tagsAll = false; - /** @var string[] */ + /** @var string[]|null */ #[Option('A list of tags that short URLs should NOT include', name: 'exclude-tag', shortcut: 'et')] - public array $excludeTags = []; + public array|null $excludeTags = null; #[Option('If --exclude-tag is provided, returns only short URLs not including ANY of them')] public bool $excludeTagsAll = false; @@ -88,17 +88,10 @@ final class ShortUrlsParamsInput public function toArray(OutputInterface $output): array { - $tagsMode = $this->tagsAll ? TagsMode::ALL->value : TagsMode::ANY->value; - $excludeTagsMode = $this->excludeTagsAll ? TagsMode::ALL->value : TagsMode::ANY->value; - $data = [ ShortUrlsParamsInputFilter::PAGE => $this->page, ShortUrlsParamsInputFilter::SEARCH_TERM => $this->searchTerm, ShortUrlsParamsInputFilter::DOMAIN => $this->domain, - ShortUrlsParamsInputFilter::TAGS => array_unique($this->tags), - ShortUrlsParamsInputFilter::TAGS_MODE => $tagsMode, - ShortUrlsParamsInputFilter::EXCLUDE_TAGS => array_unique($this->excludeTags), - ShortUrlsParamsInputFilter::EXCLUDE_TAGS_MODE => $excludeTagsMode, ShortUrlsParamsInputFilter::ORDER_BY => $this->orderBy, ShortUrlsParamsInputFilter::START_DATE => InputUtils::processDate('start-date', $this->startDate, $output), ShortUrlsParamsInputFilter::END_DATE => InputUtils::processDate('end-date', $this->endDate, $output), @@ -107,6 +100,18 @@ final class ShortUrlsParamsInput ShortUrlsParamsInputFilter::API_KEY_NAME => $this->apiKeyName, ]; + if ($this->tags !== null) { + $tagsMode = $this->tagsAll ? TagsMode::ALL : TagsMode::ANY; + $data[ShortUrlsParamsInputFilter::TAGS_MODE] = $tagsMode->value; + $data[ShortUrlsParamsInputFilter::TAGS] = array_unique($this->tags); + } + + if ($this->excludeTags !== null) { + $excludeTagsMode = $this->excludeTagsAll ? TagsMode::ALL : TagsMode::ANY; + $data[ShortUrlsParamsInputFilter::EXCLUDE_TAGS_MODE] = $excludeTagsMode->value; + $data[ShortUrlsParamsInputFilter::EXCLUDE_TAGS] = array_unique($this->excludeTags); + } + if ($this->all) { $data[ShortUrlsParamsInputFilter::ITEMS_PER_PAGE] = Paginator::ALL_ITEMS; } diff --git a/module/CLI/src/Input/ShortUrlDataInput.php b/module/CLI/src/Input/ShortUrlDataInput.php deleted file mode 100644 index 908e6536..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/RedirectRule/ManageRedirectRulesCommandTest.php b/module/CLI/test/Command/RedirectRule/ManageRedirectRulesCommandTest.php index 5cb45a4b..ac5073f4 100644 --- a/module/CLI/test/Command/RedirectRule/ManageRedirectRulesCommandTest.php +++ b/module/CLI/test/Command/RedirectRule/ManageRedirectRulesCommandTest.php @@ -48,7 +48,7 @@ class ManageRedirectRulesCommandTest extends TestCase $this->ruleService->expects($this->never())->method('saveRulesForShortUrl'); $this->ruleHandler->expects($this->never())->method('manageRules'); - $exitCode = $this->commandTester->execute(['shortCode' => 'foo']); + $exitCode = $this->commandTester->execute(['short-code' => 'foo']); $output = $this->commandTester->getDisplay(); self::assertEquals(Command::FAILURE, $exitCode); @@ -67,7 +67,7 @@ class ManageRedirectRulesCommandTest extends TestCase $this->ruleHandler->expects($this->once())->method('manageRules')->willReturn(null); $this->ruleService->expects($this->never())->method('saveRulesForShortUrl'); - $exitCode = $this->commandTester->execute(['shortCode' => 'foo']); + $exitCode = $this->commandTester->execute(['short-code' => 'foo']); $output = $this->commandTester->getDisplay(); self::assertEquals(Command::SUCCESS, $exitCode); @@ -86,7 +86,7 @@ class ManageRedirectRulesCommandTest extends TestCase $this->ruleHandler->expects($this->once())->method('manageRules')->willReturn([]); $this->ruleService->expects($this->once())->method('saveRulesForShortUrl')->with($shortUrl, []); - $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/CreateShortUrlCommandTest.php b/module/CLI/test/Command/ShortUrl/CreateShortUrlCommandTest.php index 9452aad5..07dd1efb 100644 --- a/module/CLI/test/Command/ShortUrl/CreateShortUrlCommandTest.php +++ b/module/CLI/test/Command/ShortUrl/CreateShortUrlCommandTest.php @@ -54,7 +54,7 @@ class CreateShortUrlCommandTest extends TestCase ); $this->commandTester->execute([ - 'longUrl' => 'http://domain.com/foo/bar', + 'long-url' => 'http://domain.com/foo/bar', '--max-visits' => '3', ], ['verbosity' => OutputInterface::VERBOSITY_VERBOSE]); $output = $this->commandTester->getDisplay(); @@ -87,7 +87,7 @@ class CreateShortUrlCommandTest extends TestCase ); $this->stringifier->method('stringify')->willReturn(''); - $this->commandTester->execute(['longUrl' => 'http://domain.com/invalid', '--custom-slug' => 'my-slug']); + $this->commandTester->execute(['long-url' => 'http://domain.com/invalid', '--custom-slug' => 'my-slug']); $output = $this->commandTester->getDisplay(); self::assertEquals(Command::FAILURE, $this->commandTester->getStatusCode()); @@ -109,7 +109,7 @@ class CreateShortUrlCommandTest extends TestCase ); $this->commandTester->execute([ - 'longUrl' => 'http://domain.com/foo/bar', + 'long-url' => 'http://domain.com/foo/bar', '--tag' => ['foo', 'bar', 'baz', 'boo', 'zar', 'baz'], ]); $output = $this->commandTester->getDisplay(); @@ -129,7 +129,7 @@ class CreateShortUrlCommandTest extends TestCase )->willReturn(UrlShorteningResult::withoutErrorOnEventDispatching(ShortUrl::createFake())); $this->stringifier->method('stringify')->willReturn(''); - $input['longUrl'] = 'http://domain.com/foo/bar'; + $input['long-url'] = 'http://domain.com/foo/bar'; $this->commandTester->execute($input); self::assertEquals(Command::SUCCESS, $this->commandTester->getStatusCode()); @@ -156,7 +156,7 @@ class CreateShortUrlCommandTest extends TestCase )->willReturn(UrlShorteningResult::withoutErrorOnEventDispatching($shortUrl)); $this->stringifier->method('stringify')->willReturn(''); - $options['longUrl'] = 'http://domain.com/foo/bar'; + $options['long-url'] = 'http://domain.com/foo/bar'; $this->commandTester->execute($options); } @@ -178,7 +178,7 @@ class CreateShortUrlCommandTest extends TestCase ); $this->stringifier->method('stringify')->willReturn('stringified_short_url'); - $this->commandTester->execute(['longUrl' => 'http://domain.com/foo/bar'], ['verbosity' => $verbosity]); + $this->commandTester->execute(['long-url' => 'http://domain.com/foo/bar'], ['verbosity' => $verbosity]); $output = $this->commandTester->getDisplay(); $assert($output); 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(); diff --git a/module/CLI/test/Command/ShortUrl/ListShortUrlsCommandTest.php b/module/CLI/test/Command/ShortUrl/ListShortUrlsCommandTest.php index 69190f7c..424cee51 100644 --- a/module/CLI/test/Command/ShortUrl/ListShortUrlsCommandTest.php +++ b/module/CLI/test/Command/ShortUrl/ListShortUrlsCommandTest.php @@ -203,25 +203,34 @@ class ListShortUrlsCommandTest extends TestCase array $commandArgs, int|null $page, string|null $searchTerm, - array $tags, + array|null $tags, string $tagsMode, string|null $startDate = null, string|null $endDate = null, - array $excludeTags = [], + array|null $excludeTags = null, string $excludeTagsMode = TagsMode::ANY->value, string|null $apiKeyName = null, ): void { - $this->shortUrlService->expects($this->once())->method('listShortUrls')->with(ShortUrlsParams::fromRawData([ + $expectedData = [ 'page' => $page, 'searchTerm' => $searchTerm, - 'tags' => $tags, 'tagsMode' => $tagsMode, 'startDate' => $startDate !== null ? Chronos::parse($startDate)->toAtomString() : null, 'endDate' => $endDate !== null ? Chronos::parse($endDate)->toAtomString() : null, - 'excludeTags' => $excludeTags, 'excludeTagsMode' => $excludeTagsMode, 'apiKeyName' => $apiKeyName, - ]))->willReturn(new Paginator(new ArrayAdapter([]))); + ]; + + if ($tags !== null) { + $expectedData['tags'] = $tags; + } + if ($excludeTags !== null) { + $expectedData['excludeTags'] = $excludeTags; + } + + $this->shortUrlService->expects($this->once())->method('listShortUrls')->with(ShortUrlsParams::fromRawData( + $expectedData, + ))->willReturn(new Paginator(new ArrayAdapter([]))); $this->commandTester->setInputs(['n']); $this->commandTester->execute($commandArgs); @@ -231,7 +240,7 @@ class ListShortUrlsCommandTest extends TestCase { yield [[], 1, null, [], TagsMode::ANY->value]; yield [['--page' => $page = 3], $page, null, [], TagsMode::ANY->value]; - yield [['--tags-all' => true], 1, null, [], TagsMode::ALL->value]; + yield [['--tags-all' => true, '--tag' => ['foo']], 1, null, ['foo'], TagsMode::ALL->value]; yield [['--search-term' => $searchTerm = 'search this'], 1, $searchTerm, [], TagsMode::ANY->value]; yield [ ['--page' => $page = 3, '--search-term' => $searchTerm = 'search this', '--tag' => $tags = ['foo', 'bar']], @@ -270,7 +279,7 @@ class ListShortUrlsCommandTest extends TestCase ['--exclude-tag' => ['foo', 'bar'], '--exclude-tags-all' => true], 1, null, - [], + null, TagsMode::ANY->value, null, null,