Completed
Pull Request — master (#340)
by Alejandro
05:57
created

GenerateShortUrlCommand::interact()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3.4746

Importance

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

117
                new Uri(/** @scrutinizer ignore-type */ $longUrl),
Loading history...
118 3
                $tags,
119 3
                $this->getOptionalDate($input, 'validSince'),
120 3
                $this->getOptionalDate($input, 'validUntil'),
121 3
                $customSlug,
122 3
                $maxVisits !== null ? (int) $maxVisits : null
123 2
            )->getShortCode();
124 2
            $shortUrl = $this->buildShortUrl($this->domainConfig, $shortCode);
125
126 2
            $io->writeln([
127 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

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

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