Completed
Push — master ( e1a9e3...a81ac8 )
by Alejandro
14s queued 11s
created

GenerateShortUrlCommand::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

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

117
        $tags = unique(flatten(array_map($explodeWithComma, /** @scrutinizer ignore-type */ $input->getOption('tags'))));
Loading history...
118 3
        $customSlug = $input->getOption('customSlug');
119 3
        $maxVisits = $input->getOption('maxVisits');
120
121
        try {
122 3
            $shortCode = $this->urlShortener->urlToShortCode(
123 3
                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

123
                new Uri(/** @scrutinizer ignore-type */ $longUrl),
Loading history...
124 3
                $tags,
125 3
                ShortUrlMeta::createFromParams(
126 3
                    $this->getOptionalDate($input, 'validSince'),
127 3
                    $this->getOptionalDate($input, 'validUntil'),
128 3
                    $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

128
                    /** @scrutinizer ignore-type */ $customSlug,
Loading history...
129 3
                    $maxVisits !== null ? (int) $maxVisits : null,
130 3
                    $input->getOption('findIfExists')
131
                )
132 2
            )->getShortCode();
133 2
            $shortUrl = $this->buildShortUrl($this->domainConfig, $shortCode);
134
135 2
            $io->writeln([
136 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

136
                sprintf('Processed long URL: <info>%s</info>', /** @scrutinizer ignore-type */ $longUrl),
Loading history...
137 2
                sprintf('Generated short URL: <info>%s</info>', $shortUrl),
138
            ]);
139 2
            return ExitCodes::EXIT_SUCCESS;
140 1
        } catch (InvalidUrlException $e) {
141 1
            $io->error(sprintf('Provided URL "%s" is invalid. Try with a different one.', $longUrl));
142 1
            return ExitCodes::EXIT_FAILURE;
143
        } catch (NonUniqueSlugException $e) {
144
            $io->error(
145
                sprintf('Provided slug "%s" is already in use by another URL. Try with a different one.', $customSlug)
146
            );
147
            return ExitCodes::EXIT_FAILURE;
148
        }
149
    }
150
151 3
    private function getOptionalDate(InputInterface $input, string $fieldName): ?Chronos
152
    {
153 3
        $since = $input->getOption($fieldName);
154 3
        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

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