Completed
Push — master ( 67e465...aa77c9 )
by Alejandro
13s queued 10s
created

GenerateShortUrlCommand::execute()   B

Complexity

Conditions 6
Paths 15

Size

Total Lines 45
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 28
CRAP Score 6.288

Importance

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

122
                new Uri(/** @scrutinizer ignore-type */ $longUrl),
Loading history...
123 3
                $tags,
124 3
                $this->getOptionalDate($input, 'validSince'),
125 3
                $this->getOptionalDate($input, 'validUntil'),
126 3
                $customSlug,
127 3
                $maxVisits !== null ? (int) $maxVisits : null
128 2
            )->getShortCode();
129 2
            $shortUrl = $this->buildShortUrl($this->domainConfig, $shortCode);
130
131 2
            $io->writeln([
132 2
                sprintf('%s <info>%s</info>', $this->translator->translate('Processed long URL:'), $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

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

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