From c6b83a64379189d642acb25038e3a7bf92179e68 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Wed, 17 Dec 2025 15:43:14 +0100 Subject: [PATCH] Convert GetShortUrlVisitsCommand into invokable command --- .../ShortUrl/GetShortUrlVisitsCommand.php | 68 ++++++++----------- .../Visit/AbstractVisitsListCommand.php | 53 --------------- module/CLI/src/Input/DateOption.php | 48 ------------- module/CLI/src/Input/DomainOption.php | 29 -------- module/CLI/src/Input/EndDateOption.php | 30 -------- .../CLI/src/Input/ShortUrlIdentifierInput.php | 34 ---------- module/CLI/src/Input/StartDateOption.php | 30 -------- .../ShortUrl/GetShortUrlVisitsCommandTest.php | 29 +------- 8 files changed, 33 insertions(+), 288 deletions(-) delete mode 100644 module/CLI/src/Command/Visit/AbstractVisitsListCommand.php delete mode 100644 module/CLI/src/Input/DateOption.php delete mode 100644 module/CLI/src/Input/DomainOption.php delete mode 100644 module/CLI/src/Input/EndDateOption.php delete mode 100644 module/CLI/src/Input/ShortUrlIdentifierInput.php delete mode 100644 module/CLI/src/Input/StartDateOption.php diff --git a/module/CLI/src/Command/ShortUrl/GetShortUrlVisitsCommand.php b/module/CLI/src/Command/ShortUrl/GetShortUrlVisitsCommand.php index 8507b9ca..83347319 100644 --- a/module/CLI/src/Command/ShortUrl/GetShortUrlVisitsCommand.php +++ b/module/CLI/src/Command/ShortUrl/GetShortUrlVisitsCommand.php @@ -4,61 +4,53 @@ declare(strict_types=1); namespace Shlinkio\Shlink\CLI\Command\ShortUrl; -use Shlinkio\Shlink\CLI\Command\Visit\AbstractVisitsListCommand; -use Shlinkio\Shlink\CLI\Input\ShortUrlIdentifierInput; -use Shlinkio\Shlink\Common\Paginator\Paginator; -use Shlinkio\Shlink\Common\Util\DateRange; +use Shlinkio\Shlink\CLI\Command\Visit\VisitsCommandUtils; +use Shlinkio\Shlink\CLI\Input\VisitsDateRangeInput; +use Shlinkio\Shlink\CLI\Util\ShlinkTable; +use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlIdentifier; use Shlinkio\Shlink\Core\Visit\Entity\Visit; use Shlinkio\Shlink\Core\Visit\Model\VisitsParams; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Output\OutputInterface; +use Shlinkio\Shlink\Core\Visit\VisitsStatsHelperInterface; +use Symfony\Component\Console\Attribute\Argument; +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Attribute\Ask; +use Symfony\Component\Console\Attribute\MapInput; +use Symfony\Component\Console\Attribute\Option; +use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Style\SymfonyStyle; -class GetShortUrlVisitsCommand extends AbstractVisitsListCommand +#[AsCommand(GetShortUrlVisitsCommand::NAME, 'Returns the detailed visits information for provided short code')] +class GetShortUrlVisitsCommand extends Command { public const string NAME = 'short-url:visits'; - private ShortUrlIdentifierInput $shortUrlIdentifierInput; - - protected function configure(): void + public function __construct(protected readonly VisitsStatsHelperInterface $visitsHelper) { - $this - ->setName(self::NAME) - ->setDescription('Returns the detailed visits information for provided short code'); - $this->shortUrlIdentifierInput = new ShortUrlIdentifierInput( - $this, - shortCodeDesc: 'The short code which visits we want to get.', - domainDesc: 'The domain for the short code.', - ); + parent::__construct(); } - protected function interact(InputInterface $input, OutputInterface $output): void - { - $shortCode = $this->shortUrlIdentifierInput->shortCode($input); - if (! empty($shortCode)) { - return; - } + public function __invoke( + SymfonyStyle $io, + #[Argument('The short code which visits we want to get'), Ask('Which short code do you want to use?')] + string $shortCode, + #[MapInput] VisitsDateRangeInput $dateRangeInput, + #[Option('The domain for the short code', shortcut: 'd')] + string|null $domain = null, + ): int { + $identifier = ShortUrlIdentifier::fromShortCodeAndDomain($shortCode, $domain); + $dateRange = $dateRangeInput->toDateRange(); + $paginator = $this->visitsHelper->visitsForShortUrl($identifier, new VisitsParams($dateRange)); + [$rows, $headers] = VisitsCommandUtils::resolveRowsAndHeaders($paginator, $this->mapExtraFields(...)); - $io = new SymfonyStyle($input, $output); - $shortCode = $io->ask('A short code was not provided. Which short code do you want to use?'); - if (! empty($shortCode)) { - $input->setArgument('shortCode', $shortCode); - } - } + ShlinkTable::default($io)->render($headers, $rows); - /** - * @return Paginator - */ - protected function getVisitsPaginator(InputInterface $input, DateRange $dateRange): Paginator - { - $identifier = $this->shortUrlIdentifierInput->toShortUrlIdentifier($input); - return $this->visitsHelper->visitsForShortUrl($identifier, new VisitsParams($dateRange)); + return self::SUCCESS; } /** * @return array */ - protected function mapExtraFields(Visit $visit): array + private function mapExtraFields(Visit $visit): array { return []; } diff --git a/module/CLI/src/Command/Visit/AbstractVisitsListCommand.php b/module/CLI/src/Command/Visit/AbstractVisitsListCommand.php deleted file mode 100644 index 0bce0feb..00000000 --- a/module/CLI/src/Command/Visit/AbstractVisitsListCommand.php +++ /dev/null @@ -1,53 +0,0 @@ -startDateOption = new StartDateOption($this, 'visits'); - $this->endDateOption = new EndDateOption($this, 'visits'); - } - - final protected function execute(InputInterface $input, OutputInterface $output): int - { - $startDate = $this->startDateOption->get($input, $output); - $endDate = $this->endDateOption->get($input, $output); - $paginator = $this->getVisitsPaginator($input, buildDateRange($startDate, $endDate)); - [$rows, $headers] = VisitsCommandUtils::resolveRowsAndHeaders($paginator, $this->mapExtraFields(...)); - - ShlinkTable::default($output)->render($headers, $rows); - - return self::SUCCESS; - } - - /** - * @return Paginator - */ - abstract protected function getVisitsPaginator(InputInterface $input, DateRange $dateRange): Paginator; - - /** - * @return array - */ - abstract protected function mapExtraFields(Visit $visit): array; -} diff --git a/module/CLI/src/Input/DateOption.php b/module/CLI/src/Input/DateOption.php deleted file mode 100644 index 05c9de94..00000000 --- a/module/CLI/src/Input/DateOption.php +++ /dev/null @@ -1,48 +0,0 @@ -addOption($name, $shortcut, InputOption::VALUE_REQUIRED, $description); - } - - public function get(InputInterface $input, OutputInterface $output): Chronos|null - { - $value = $input->getOption($this->name); - if (empty($value) || ! is_string($value)) { - return null; - } - - try { - return normalizeOptionalDate($value); - } catch (Throwable $e) { - $output->writeln(sprintf( - '> Ignored provided "%s" since its value "%s" is not a valid date. <', - $this->name, - $value, - )); - - if ($output->isVeryVerbose()) { - $this->command->getApplication()?->renderThrowable($e, $output); - } - - return null; - } - } -} diff --git a/module/CLI/src/Input/DomainOption.php b/module/CLI/src/Input/DomainOption.php deleted file mode 100644 index e7a15f52..00000000 --- a/module/CLI/src/Input/DomainOption.php +++ /dev/null @@ -1,29 +0,0 @@ -addOption( - name: self::NAME, - shortcut: 'd', - mode: InputOption::VALUE_REQUIRED, - description: $description, - ); - } - - public function get(InputInterface $input): string|null - { - return $input->getOption(self::NAME); - } -} diff --git a/module/CLI/src/Input/EndDateOption.php b/module/CLI/src/Input/EndDateOption.php deleted file mode 100644 index a38b9b32..00000000 --- a/module/CLI/src/Input/EndDateOption.php +++ /dev/null @@ -1,30 +0,0 @@ -dateOption = new DateOption($command, 'end-date', 'e', sprintf( - 'Allows to filter %s, returning only those newer than provided date.', - $descriptionHint, - )); - } - - public function get(InputInterface $input, OutputInterface $output): Chronos|null - { - return $this->dateOption->get($input, $output); - } -} diff --git a/module/CLI/src/Input/ShortUrlIdentifierInput.php b/module/CLI/src/Input/ShortUrlIdentifierInput.php deleted file mode 100644 index 46ac79da..00000000 --- a/module/CLI/src/Input/ShortUrlIdentifierInput.php +++ /dev/null @@ -1,34 +0,0 @@ -addArgument('shortCode', InputArgument::REQUIRED, $shortCodeDesc) - ->addOption('domain', 'd', InputOption::VALUE_REQUIRED, $domainDesc); - } - - public function shortCode(InputInterface $input): string|null - { - return $input->getArgument('shortCode'); - } - - public function toShortUrlIdentifier(InputInterface $input): ShortUrlIdentifier - { - $shortCode = $input->getArgument('shortCode'); - $domain = $input->getOption('domain'); - - return ShortUrlIdentifier::fromShortCodeAndDomain($shortCode, $domain); - } -} diff --git a/module/CLI/src/Input/StartDateOption.php b/module/CLI/src/Input/StartDateOption.php deleted file mode 100644 index 453b31a2..00000000 --- a/module/CLI/src/Input/StartDateOption.php +++ /dev/null @@ -1,30 +0,0 @@ -dateOption = new DateOption($command, 'start-date', 's', sprintf( - 'Allows to filter %s, returning only those older than provided date.', - $descriptionHint, - )); - } - - public function get(InputInterface $input, OutputInterface $output): Chronos|null - { - return $this->dateOption->get($input, $output); - } -} diff --git a/module/CLI/test/Command/ShortUrl/GetShortUrlVisitsCommandTest.php b/module/CLI/test/Command/ShortUrl/GetShortUrlVisitsCommandTest.php index e306b0bc..3fd53c48 100644 --- a/module/CLI/test/Command/ShortUrl/GetShortUrlVisitsCommandTest.php +++ b/module/CLI/test/Command/ShortUrl/GetShortUrlVisitsCommandTest.php @@ -24,7 +24,6 @@ use ShlinkioTest\Shlink\CLI\Util\CliTestUtils; use Symfony\Component\Console\Tester\CommandTester; use function Shlinkio\Shlink\Common\buildDateRange; -use function sprintf; class GetShortUrlVisitsCommandTest extends TestCase { @@ -47,7 +46,7 @@ class GetShortUrlVisitsCommandTest extends TestCase new VisitsParams(DateRange::allTime()), )->willReturn(new Paginator(new ArrayAdapter([]))); - $this->commandTester->execute(['shortCode' => $shortCode]); + $this->commandTester->execute(['short-code' => $shortCode]); } #[Test] @@ -75,34 +74,12 @@ class GetShortUrlVisitsCommandTest extends TestCase )->willReturn(new Paginator(new ArrayAdapter([]))); $this->commandTester->execute([ - 'shortCode' => $shortCode, + 'short-code' => $shortCode, '--start-date' => $startDate, '--end-date' => $endDate, ]); } - #[Test] - public function providingInvalidDatesPrintsWarning(): void - { - $shortCode = 'abc123'; - $startDate = 'foo'; - $this->visitsHelper->expects($this->once())->method('visitsForShortUrl')->with( - ShortUrlIdentifier::fromShortCodeAndDomain($shortCode), - new VisitsParams(DateRange::allTime()), - )->willReturn(new Paginator(new ArrayAdapter([]))); - - $this->commandTester->execute([ - 'shortCode' => $shortCode, - '--start-date' => $startDate, - ]); - $output = $this->commandTester->getDisplay(); - - self::assertStringContainsString( - sprintf('Ignored provided "start-date" since its value "%s" is not a valid date', $startDate), - $output, - ); - } - #[Test] public function outputIsProperlyGenerated(): void { @@ -115,7 +92,7 @@ class GetShortUrlVisitsCommandTest extends TestCase $this->anything(), )->willReturn(new Paginator(new ArrayAdapter([$visit]))); - $this->commandTester->execute(['shortCode' => $shortCode]); + $this->commandTester->execute(['short-code' => $shortCode]); $output = $this->commandTester->getDisplay(); self::assertEquals(