Completed
Pull Request — master (#246)
by Alejandro
05:59
created

GenerateShortUrlCommand   A

Complexity

Total Complexity 13

Size/Duplication

Total Lines 131
Duplicated Lines 0 %

Test Coverage

Coverage 81.33%

Importance

Changes 0
Metric Value
eloc 76
dl 0
loc 131
rs 10
c 0
b 0
f 0
ccs 61
cts 75
cp 0.8133
wmc 13

5 Methods

Rating   Name   Duplication   Size   Complexity  
A interact() 0 13 3
A __construct() 0 9 1
A getOptionalDate() 0 4 2
B execute() 0 45 6
A configure() 0 28 1
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 2
    public function __construct(
44
        UrlShortenerInterface $urlShortener,
45
        TranslatorInterface $translator,
46
        array $domainConfig
47
    ) {
48 2
        $this->urlShortener = $urlShortener;
49 2
        $this->translator = $translator;
50 2
        $this->domainConfig = $domainConfig;
51 2
        parent::__construct(null);
52 2
    }
53
54 2
    protected function configure(): void
55
    {
56
        $this
57 2
            ->setName(self::NAME)
58 2
            ->setAliases(self::ALIASES)
59 2
            ->setDescription(
60 2
                $this->translator->translate('Generates a short URL for provided long URL and returns it')
61
            )
62 2
            ->addArgument('longUrl', InputArgument::REQUIRED, $this->translator->translate('The long URL to parse'))
63 2
            ->addOption(
64 2
                'tags',
65 2
                't',
66 2
                InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED,
67 2
                $this->translator->translate('Tags to apply to the new short URL')
68
            )
69 2
            ->addOption('validSince', 's', InputOption::VALUE_REQUIRED, $this->translator->translate(
70
                'The date from which this short URL will be valid. '
71 2
                . 'If someone tries to access it before this date, it will not be found.'
72
            ))
73 2
            ->addOption('validUntil', 'u', InputOption::VALUE_REQUIRED, $this->translator->translate(
74
                'The date until which this short URL will be valid. '
75 2
                . 'If someone tries to access it after this date, it will not be found.'
76
            ))
77 2
            ->addOption('customSlug', 'c', InputOption::VALUE_REQUIRED, $this->translator->translate(
78 2
                'If provided, this slug will be used instead of generating a short code'
79
            ))
80 2
            ->addOption('maxVisits', 'm', InputOption::VALUE_REQUIRED, $this->translator->translate(
81 2
                'This will limit the number of visits for this short URL.'
82
            ));
83 2
    }
84
85 2
    protected function interact(InputInterface $input, OutputInterface $output): void
86
    {
87 2
        $io = new SymfonyStyle($input, $output);
88 2
        $longUrl = $input->getArgument('longUrl');
89 2
        if (! empty($longUrl)) {
90 2
            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 2
    protected function execute(InputInterface $input, OutputInterface $output): void
102
    {
103 2
        $io = new SymfonyStyle($input, $output);
104 2
        $longUrl = $input->getArgument('longUrl');
105 2
        if (empty($longUrl)) {
106
            $io->error($this->translator->translate('A URL was not provided!'));
107
            return;
108
        }
109
110 2
        $tags = $input->getOption('tags');
111 2
        $processedTags = [];
112 2
        foreach ($tags as $key => $tag) {
113
            $explodedTags = explode(',', $tag);
114
            $processedTags = array_merge($processedTags, $explodedTags);
115
        }
116 2
        $tags = $processedTags;
117 2
        $customSlug = $input->getOption('customSlug');
118 2
        $maxVisits = $input->getOption('maxVisits');
119
120
        try {
121 2
            $shortCode = $this->urlShortener->urlToShortCode(
122 2
                new Uri($longUrl),
123 2
                $tags,
124 2
                $this->getOptionalDate($input, 'validSince'),
125 2
                $this->getOptionalDate($input, 'validUntil'),
126 2
                $customSlug,
127 2
                $maxVisits !== null ? (int) $maxVisits : null
128 1
            )->getShortCode();
129 1
            $shortUrl = $this->buildShortUrl($this->domainConfig, $shortCode);
130
131 1
            $io->writeln([
132 1
                sprintf('%s <info>%s</info>', $this->translator->translate('Processed long URL:'), $longUrl),
133 1
                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 2
    }
149
150 2
    private function getOptionalDate(InputInterface $input, string $fieldName): ?Chronos
151
    {
152 2
        $since = $input->getOption($fieldName);
153 2
        return $since !== null ? Chronos::parse($since) : null;
154
    }
155
}
156