Completed
Pull Request — master (#604)
by Alejandro
11:57 queued 08:51
created

ListShortUrlsCommand::processOrderBy()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3

Importance

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

104
        $tags = ! empty($tags) ? explode(',', /** @scrutinizer ignore-type */ $tags) : [];
Loading history...
105 15
        $showTags = (bool) $input->getOption('showTags');
106 15
        $startDate = $this->getDateOption($input, $output, 'startDate');
107 15
        $endDate = $this->getDateOption($input, $output, 'endDate');
108 15
        $orderBy = $this->processOrderBy($input);
109
110
        do {
111 15
            $result = $this->renderPage($output, $page, $searchTerm, $tags, $showTags, $startDate, $endDate, $orderBy);
112 15
            $page++;
113
114 15
            $continue = $this->isLastPage($result)
115 13
                ? false
116 15
                : $io->confirm(sprintf('Continue with page <options=bold>%s</>?', $page), false);
117 15
        } while ($continue);
118
119 15
        $io->newLine();
120 15
        $io->success('Short URLs properly listed');
121
122 15
        return ExitCodes::EXIT_SUCCESS;
123
    }
124
125
    /**
126
     * @param string|array|null $orderBy
127
     */
128 15
    private function renderPage(
129
        OutputInterface $output,
130
        int $page,
131
        ?string $searchTerm,
132
        array $tags,
133
        bool $showTags,
134
        ?Chronos $startDate,
135
        ?Chronos $endDate,
136
        $orderBy
137
    ): Paginator {
138 15
        $result = $this->shortUrlService->listShortUrls(
139 15
            $page,
140
            $searchTerm,
141
            $tags,
142
            $orderBy,
143 15
            new DateRange($startDate, $endDate),
144
        );
145
146 15
        $headers = ['Short code', 'Short URL', 'Long URL', 'Date created', 'Visits count'];
147 15
        if ($showTags) {
148 1
            $headers[] = 'Tags';
149
        }
150
151 15
        $rows = [];
152 15
        foreach ($result as $row) {
153 2
            $shortUrl = $this->transformer->transform($row);
154 2
            if ($showTags) {
155
                $shortUrl['tags'] = implode(', ', $shortUrl['tags']);
156
            } else {
157 2
                unset($shortUrl['tags']);
158
            }
159
160 2
            $rows[] = array_values(array_intersect_key($shortUrl, array_flip(self::COLUMNS_WHITELIST)));
161
        }
162
163 15
        ShlinkTable::fromOutput($output)->render($headers, $rows, $this->formatCurrentPageMessage(
164 15
            $result,
165 15
            'Page %s of %s',
166
        ));
167
168 15
        return $result;
169
    }
170
171
    /**
172
     * @return array|string|null
173
     */
174 15
    private function processOrderBy(InputInterface $input)
175
    {
176 15
        $orderBy = $input->getOption('orderBy');
177 15
        if (empty($orderBy)) {
178 12
            return null;
179
        }
180
181 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

181
        $orderBy = explode(',', /** @scrutinizer ignore-type */ $orderBy);
Loading history...
182 3
        return count($orderBy) === 1 ? $orderBy[0] : [$orderBy[0] => $orderBy[1]];
183
    }
184
}
185