Completed
Push — master ( a81ac8...05e307 )
by Alejandro
25s queued 13s
created

GenerateShortUrlCommand::execute()   A

Complexity

Conditions 5
Paths 6

Size

Total Lines 41
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 28
CRAP Score 5.0073

Importance

Changes 0
Metric Value
cc 5
eloc 31
c 0
b 0
f 0
nc 6
nop 2
dl 0
loc 41
rs 9.1128
ccs 28
cts 30
cp 0.9333
crap 5.0073
1
<?php
2
declare(strict_types=1);
3
4
namespace Shlinkio\Shlink\CLI\Command\ShortUrl;
5
6
use Cake\Chronos\Chronos;
7
use Shlinkio\Shlink\CLI\Util\ExitCodes;
8
use Shlinkio\Shlink\Core\Exception\InvalidUrlException;
9
use Shlinkio\Shlink\Core\Exception\NonUniqueSlugException;
10
use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
11
use Shlinkio\Shlink\Core\Service\UrlShortenerInterface;
12
use Symfony\Component\Console\Command\Command;
13
use Symfony\Component\Console\Input\InputArgument;
14
use Symfony\Component\Console\Input\InputInterface;
15
use Symfony\Component\Console\Input\InputOption;
16
use Symfony\Component\Console\Output\OutputInterface;
17
use Symfony\Component\Console\Style\SymfonyStyle;
18
use Zend\Diactoros\Uri;
19
20
use function array_map;
21
use function Functional\curry;
22
use function Functional\flatten;
23
use function Functional\unique;
24
use function sprintf;
25
26
class GenerateShortUrlCommand extends Command
27
{
28
    public const NAME = 'short-url:generate';
29
    private const ALIASES = ['shortcode:generate', 'short-code:generate'];
30
31
    /** @var UrlShortenerInterface */
32
    private $urlShortener;
33
    /** @var array */
34
    private $domainConfig;
35
36 4
    public function __construct(UrlShortenerInterface $urlShortener, array $domainConfig)
37
    {
38 4
        parent::__construct();
39 4
        $this->urlShortener = $urlShortener;
40 4
        $this->domainConfig = $domainConfig;
41
    }
42
43 4
    protected function configure(): void
44
    {
45
        $this
46 4
            ->setName(self::NAME)
47 4
            ->setAliases(self::ALIASES)
48 4
            ->setDescription('Generates a short URL for provided long URL and returns it')
49 4
            ->addArgument('longUrl', InputArgument::REQUIRED, 'The long URL to parse')
50 4
            ->addOption(
51 4
                'tags',
52 4
                't',
53 4
                InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED,
54 4
                'Tags to apply to the new short URL'
55
            )
56 4
            ->addOption(
57 4
                'validSince',
58 4
                's',
59 4
                InputOption::VALUE_REQUIRED,
60
                'The date from which this short URL will be valid. '
61 4
                . 'If someone tries to access it before this date, it will not be found.'
62
            )
63 4
            ->addOption(
64 4
                'validUntil',
65 4
                'u',
66 4
                InputOption::VALUE_REQUIRED,
67
                'The date until which this short URL will be valid. '
68 4
                . 'If someone tries to access it after this date, it will not be found.'
69
            )
70 4
            ->addOption(
71 4
                'customSlug',
72 4
                'c',
73 4
                InputOption::VALUE_REQUIRED,
74 4
                'If provided, this slug will be used instead of generating a short code'
75
            )
76 4
            ->addOption(
77 4
                'maxVisits',
78 4
                'm',
79 4
                InputOption::VALUE_REQUIRED,
80 4
                'This will limit the number of visits for this short URL.'
81
            )
82 4
            ->addOption(
83 4
                'findIfExists',
84 4
                'f',
85 4
                InputOption::VALUE_NONE,
86 4
                'This will force existing matching URL to be returned if found, instead of creating a new one.'
87
            )
88 4
            ->addOption(
89 4
                'domain',
90 4
                'd',
91 4
                InputOption::VALUE_REQUIRED,
92 4
                'The domain to which this short URL will be attached.'
93
            );
94
    }
95
96 4
    protected function interact(InputInterface $input, OutputInterface $output): void
97
    {
98 4
        $io = new SymfonyStyle($input, $output);
99 4
        $longUrl = $input->getArgument('longUrl');
100 4
        if (! empty($longUrl)) {
101 4
            return;
102
        }
103
104
        $longUrl = $io->ask('A long URL was not provided. Which URL do you want to be shortened?');
105
        if (! empty($longUrl)) {
106
            $input->setArgument('longUrl', $longUrl);
107
        }
108
    }
109
110 4
    protected function execute(InputInterface $input, OutputInterface $output): ?int
111
    {
112 4
        $io = new SymfonyStyle($input, $output);
113 4
        $longUrl = $input->getArgument('longUrl');
114 4
        if (empty($longUrl)) {
115
            $io->error('A URL was not provided!');
116
            return ExitCodes::EXIT_FAILURE;
117
        }
118
119 4
        $explodeWithComma = curry('explode')(',');
120 4
        $tags = unique(flatten(array_map($explodeWithComma, $input->getOption('tags'))));
0 ignored issues
show
Bug introduced by
It seems like $input->getOption('tags') can also be of type boolean and null and string; however, parameter $arr1 of array_map() does only seem to accept array, 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

120
        $tags = unique(flatten(array_map($explodeWithComma, /** @scrutinizer ignore-type */ $input->getOption('tags'))));
Loading history...
121 4
        $customSlug = $input->getOption('customSlug');
122 4
        $maxVisits = $input->getOption('maxVisits');
123
124
        try {
125 4
            $shortUrl = $this->urlShortener->urlToShortCode(
126 4
                new Uri($longUrl),
0 ignored issues
show
Bug introduced by
It seems like $longUrl can also be of type string[]; however, parameter $uri of Zend\Diactoros\Uri::__construct() 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

126
                new Uri(/** @scrutinizer ignore-type */ $longUrl),
Loading history...
127
                $tags,
128 4
                ShortUrlMeta::createFromParams(
129 4
                    $this->getOptionalDate($input, 'validSince'),
130 4
                    $this->getOptionalDate($input, 'validUntil'),
131 4
                    $customSlug,
0 ignored issues
show
Bug introduced by
It seems like $customSlug can also be of type string[]; however, parameter $customSlug of Shlinkio\Shlink\Core\Mod...eta::createFromParams() does only seem to accept null|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

131
                    /** @scrutinizer ignore-type */ $customSlug,
Loading history...
132 4
                    $maxVisits !== null ? (int) $maxVisits : null,
133 4
                    $input->getOption('findIfExists'),
134 4
                    $input->getOption('domain')
135
                )
136
            );
137
138 2
            $io->writeln([
139 2
                sprintf('Processed long URL: <info>%s</info>', $longUrl),
0 ignored issues
show
Bug introduced by
It seems like $longUrl can also be of type string[]; however, parameter $args of sprintf() 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

139
                sprintf('Processed long URL: <info>%s</info>', /** @scrutinizer ignore-type */ $longUrl),
Loading history...
140 2
                sprintf('Generated short URL: <info>%s</info>', $shortUrl->toString($this->domainConfig)),
141
            ]);
142 2
            return ExitCodes::EXIT_SUCCESS;
143 2
        } catch (InvalidUrlException $e) {
144 1
            $io->error(sprintf('Provided URL "%s" is invalid. Try with a different one.', $longUrl));
145 1
            return ExitCodes::EXIT_FAILURE;
146 1
        } catch (NonUniqueSlugException $e) {
147 1
            $io->error(
148 1
                sprintf('Provided slug "%s" is already in use by another URL. Try with a different one.', $customSlug)
149
            );
150 1
            return ExitCodes::EXIT_FAILURE;
151
        }
152
    }
153
154 4
    private function getOptionalDate(InputInterface $input, string $fieldName): ?Chronos
155
    {
156 4
        $since = $input->getOption($fieldName);
157 4
        return $since !== null ? Chronos::parse($since) : null;
0 ignored issues
show
Bug introduced by
It seems like $since can also be of type string[]; however, parameter $time of Cake\Chronos\Chronos::parse() 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

157
        return $since !== null ? Chronos::parse(/** @scrutinizer ignore-type */ $since) : null;
Loading history...
158
    }
159
}
160