Passed
Pull Request — master (#343)
by Alejandro
05:50
created

GenerateShortUrlCommand::execute()   B

Complexity

Conditions 6
Paths 15

Size

Total Lines 41
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 27
CRAP Score 6.1371

Importance

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

118
                new Uri(/** @scrutinizer ignore-type */ $longUrl),
Loading history...
119 3
                $tags,
120 3
                ShortUrlMeta::createFromParams(
121 3
                    $this->getOptionalDate($input, 'validSince'),
122 3
                    $this->getOptionalDate($input, 'validUntil'),
123 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

123
                    /** @scrutinizer ignore-type */ $customSlug,
Loading history...
124 3
                    $maxVisits !== null ? (int) $maxVisits : null
125
                )
126 2
            )->getShortCode();
127 2
            $shortUrl = $this->buildShortUrl($this->domainConfig, $shortCode);
128
129 2
            $io->writeln([
130 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

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

145
        return $since !== null ? Chronos::parse(/** @scrutinizer ignore-type */ $since) : null;
Loading history...
146
    }
147
}
148