Completed
Push — master ( eca780...57807c )
by Alejandro
27s queued 11s
created

ListShortUrlsCommand::execute()   B

Complexity

Conditions 6
Paths 4

Size

Total Lines 33
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 25
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
eloc 25
c 1
b 0
f 0
nc 4
nop 2
dl 0
loc 33
rs 8.8977
ccs 25
cts 25
cp 1
crap 6
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Shlinkio\Shlink\CLI\Command\ShortUrl;
6
7
use Laminas\Paginator\Paginator;
8
use Shlinkio\Shlink\CLI\Command\Util\AbstractWithDateRangeCommand;
9
use Shlinkio\Shlink\CLI\Util\ExitCodes;
10
use Shlinkio\Shlink\CLI\Util\ShlinkTable;
11
use Shlinkio\Shlink\Common\Paginator\Util\PaginatorUtilsTrait;
12
use Shlinkio\Shlink\Core\Model\ShortUrlsOrdering;
13
use Shlinkio\Shlink\Core\Model\ShortUrlsParams;
14
use Shlinkio\Shlink\Core\Paginator\Adapter\ShortUrlRepositoryAdapter;
15
use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface;
16
use Shlinkio\Shlink\Core\Transformer\ShortUrlDataTransformer;
17
use Shlinkio\Shlink\Core\Validation\ShortUrlsParamsInputFilter;
18
use Symfony\Component\Console\Input\InputInterface;
19
use Symfony\Component\Console\Input\InputOption;
20
use Symfony\Component\Console\Output\OutputInterface;
21
use Symfony\Component\Console\Style\SymfonyStyle;
22
23
use function array_flip;
24
use function array_intersect_key;
25
use function array_values;
26
use function count;
27
use function explode;
28
use function implode;
29
use function sprintf;
30
31
class ListShortUrlsCommand extends AbstractWithDateRangeCommand
32
{
33
    use PaginatorUtilsTrait;
34
35
    public const NAME = 'short-url:list';
36
    private const COLUMNS_WHITELIST = [
37
        'shortCode',
38
        'shortUrl',
39
        'longUrl',
40
        'dateCreated',
41
        'visitsCount',
42
        'tags',
43
    ];
44
45
    private ShortUrlServiceInterface $shortUrlService;
46
    private ShortUrlDataTransformer $transformer;
47
48 15
    public function __construct(ShortUrlServiceInterface $shortUrlService, array $domainConfig)
49
    {
50 15
        parent::__construct();
51 15
        $this->shortUrlService = $shortUrlService;
52 15
        $this->transformer = new ShortUrlDataTransformer($domainConfig);
53
    }
54
55 15
    protected function doConfigure(): void
56
    {
57
        $this
58 15
            ->setName(self::NAME)
59 15
            ->setDescription('List all short URLs')
60 15
            ->addOption(
61 15
                'page',
62 15
                'p',
63 15
                InputOption::VALUE_REQUIRED,
64 15
                sprintf('The first page to list (%s items per page)', ShortUrlRepositoryAdapter::ITEMS_PER_PAGE),
65 15
                '1',
66
            )
67 15
            ->addOption(
68 15
                'searchTerm',
69 15
                'st',
70 15
                InputOption::VALUE_REQUIRED,
71 15
                'A query used to filter results by searching for it on the longUrl and shortCode fields',
72
            )
73 15
            ->addOption(
74 15
                'tags',
75 15
                't',
76 15
                InputOption::VALUE_REQUIRED,
77 15
                'A comma-separated list of tags to filter results',
78
            )
79 15
            ->addOption(
80 15
                'orderBy',
81 15
                'o',
82 15
                InputOption::VALUE_REQUIRED,
83 15
                'The field from which we want to order by. Pass ASC or DESC separated by a comma',
84
            )
85 15
            ->addOption('showTags', null, InputOption::VALUE_NONE, 'Whether to display the tags or not');
86
    }
87
88 15
    protected function getStartDateDesc(): string
89
    {
90 15
        return 'Allows to filter short URLs, returning only those created after "startDate"';
91
    }
92
93 15
    protected function getEndDateDesc(): string
94
    {
95 15
        return 'Allows to filter short URLs, returning only those created before "endDate"';
96
    }
97
98 15
    protected function execute(InputInterface $input, OutputInterface $output): ?int
99
    {
100 15
        $io = new SymfonyStyle($input, $output);
101
102 15
        $page = (int) $input->getOption('page');
103 15
        $searchTerm = $input->getOption('searchTerm');
104 15
        $tags = $input->getOption('tags');
105 15
        $tags = ! empty($tags) ? explode(',', $tags) : [];
0 ignored issues
show
Bug introduced by
It seems like $tags can also be of type string[]; however, parameter $string of explode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

105
        $tags = ! empty($tags) ? explode(',', /** @scrutinizer ignore-type */ $tags) : [];
Loading history...
106 15
        $showTags = (bool) $input->getOption('showTags');
107 15
        $startDate = $this->getDateOption($input, $output, 'startDate');
108 15
        $endDate = $this->getDateOption($input, $output, 'endDate');
109 15
        $orderBy = $this->processOrderBy($input);
110
111
        do {
112 15
            $result = $this->renderPage($output, $showTags, ShortUrlsParams::fromRawData([
113 15
                ShortUrlsParamsInputFilter::PAGE => $page,
114 15
                ShortUrlsParamsInputFilter::SEARCH_TERM => $searchTerm,
115 15
                ShortUrlsParamsInputFilter::TAGS => $tags,
116 15
                ShortUrlsOrdering::ORDER_BY => $orderBy,
117 15
                ShortUrlsParamsInputFilter::START_DATE => $startDate !== null ? $startDate->toAtomString() : null,
118 15
                ShortUrlsParamsInputFilter::END_DATE => $endDate !== null ? $endDate->toAtomString() : null,
119
            ]));
120 15
            $page++;
121
122 15
            $continue = $this->isLastPage($result)
123 13
                ? false
124 15
                : $io->confirm(sprintf('Continue with page <options=bold>%s</>?', $page), false);
125 15
        } while ($continue);
126
127 15
        $io->newLine();
128 15
        $io->success('Short URLs properly listed');
129
130 15
        return ExitCodes::EXIT_SUCCESS;
131
    }
132
133 15
    private function renderPage(OutputInterface $output, bool $showTags, ShortUrlsParams $params): Paginator
134
    {
135 15
        $result = $this->shortUrlService->listShortUrls($params);
136
137 15
        $headers = ['Short code', 'Short URL', 'Long URL', 'Date created', 'Visits count'];
138 15
        if ($showTags) {
139 1
            $headers[] = 'Tags';
140
        }
141
142 15
        $rows = [];
143 15
        foreach ($result as $row) {
144 2
            $shortUrl = $this->transformer->transform($row);
145 2
            if ($showTags) {
146
                $shortUrl['tags'] = implode(', ', $shortUrl['tags']);
147
            } else {
148 2
                unset($shortUrl['tags']);
149
            }
150
151 2
            $rows[] = array_values(array_intersect_key($shortUrl, array_flip(self::COLUMNS_WHITELIST)));
152
        }
153
154 15
        ShlinkTable::fromOutput($output)->render($headers, $rows, $this->formatCurrentPageMessage(
155 15
            $result,
156 15
            'Page %s of %s',
157
        ));
158
159 15
        return $result;
160
    }
161
162
    /**
163
     * @return array|string|null
164
     */
165 15
    private function processOrderBy(InputInterface $input)
166
    {
167 15
        $orderBy = $input->getOption('orderBy');
168 15
        if (empty($orderBy)) {
169 12
            return null;
170
        }
171
172 3
        $orderBy = explode(',', $orderBy);
0 ignored issues
show
Bug introduced by
It seems like $orderBy can also be of type string[]; however, parameter $string of explode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

172
        $orderBy = explode(',', /** @scrutinizer ignore-type */ $orderBy);
Loading history...
173 3
        return count($orderBy) === 1 ? $orderBy[0] : [$orderBy[0] => $orderBy[1]];
174
    }
175
}
176