Completed
Pull Request — master (#276)
by Alejandro
02:50
created

ProcessVisitsCommand::execute()   A

Complexity

Conditions 2
Paths 4

Size

Total Lines 28
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 18
nc 4
nop 2
dl 0
loc 28
rs 9.6666
c 0
b 0
f 0
ccs 18
cts 18
cp 1
crap 2
1
<?php
2
declare(strict_types=1);
3
4
namespace Shlinkio\Shlink\CLI\Command\Visit;
5
6
use Shlinkio\Shlink\Common\Exception\WrongIpException;
7
use Shlinkio\Shlink\Common\IpGeolocation\IpLocationResolverInterface;
8
use Shlinkio\Shlink\Common\Util\IpAddress;
9
use Shlinkio\Shlink\Core\Entity\Visit;
10
use Shlinkio\Shlink\Core\Entity\VisitLocation;
11
use Shlinkio\Shlink\Core\Exception\IpCannotBeLocatedException;
12
use Shlinkio\Shlink\Core\Service\VisitServiceInterface;
13
use Symfony\Component\Console\Command\Command;
14
use Symfony\Component\Console\Input\InputInterface;
15
use Symfony\Component\Console\Output\OutputInterface;
16
use Symfony\Component\Console\Style\SymfonyStyle;
17
use Symfony\Component\Lock\Factory as Locker;
18
use Zend\I18n\Translator\TranslatorInterface;
19
use function sprintf;
20
21
class ProcessVisitsCommand extends Command
22
{
23
    public const NAME = 'visit:process';
24
25
    /**
26
     * @var VisitServiceInterface
27
     */
28
    private $visitService;
29
    /**
30
     * @var IpLocationResolverInterface
31
     */
32
    private $ipLocationResolver;
33
    /**
34
     * @var TranslatorInterface
35
     */
36
    private $translator;
37
    /**
38
     * @var Locker
39
     */
40
    private $locker;
41
    /**
42
     * @var OutputInterface
43
     */
44
    private $output;
45
46 6
    public function __construct(
47
        VisitServiceInterface $visitService,
48
        IpLocationResolverInterface $ipLocationResolver,
49
        Locker $locker,
50
        TranslatorInterface $translator
51
    ) {
52 6
        $this->visitService = $visitService;
53 6
        $this->ipLocationResolver = $ipLocationResolver;
54 6
        $this->translator = $translator;
55 6
        $this->locker = $locker;
56 6
        parent::__construct();
57
    }
58
59 6
    protected function configure(): void
60
    {
61
        $this
62 6
            ->setName(self::NAME)
63 6
            ->setDescription($this->translator->translate('Processes visits where location is not set yet'));
64
    }
65
66 6
    protected function execute(InputInterface $input, OutputInterface $output): void
67
    {
68 6
        $this->output = $output;
69 6
        $io = new SymfonyStyle($input, $output);
70
71 6
        $lock = $this->locker->createLock(self::NAME);
72 6
        if (! $lock->acquire()) {
73 1
            $io->warning(sprintf(
74 1
                $this->translator->translate('There is already an instance of the "%s" command in execution'),
75 1
                self::NAME
76
            ));
77 1
            return;
78
        }
79
80
        try {
81 5
            $this->visitService->locateVisits(
82 5
                [$this, 'getGeolocationDataForVisit'],
83
                function (VisitLocation $location) use ($output) {
84 1
                    $output->writeln(sprintf(
85 1
                        ' [<info>' . $this->translator->translate('Address located at "%s"') . '</info>]',
86 1
                        $location->getCountryName()
87
                    ));
88 5
                }
89
            );
90
91 1
            $io->success($this->translator->translate('Finished processing all IPs'));
92 1
        } finally {
93 5
            $lock->release();
94
        }
95
    }
96
97 5
    public function getGeolocationDataForVisit(Visit $visit): array
98
    {
99 5
        if (! $visit->hasRemoteAddr()) {
100 2
            $this->output->writeln(sprintf(
101 2
                '<comment>%s</comment>',
102 2
                $this->translator->translate('Ignored visit with no IP address')
103 2
            ), OutputInterface::VERBOSITY_VERBOSE);
104 2
            throw new IpCannotBeLocatedException('Ignored visit with no IP address');
105
        }
106
107 3
        $ipAddr = $visit->getRemoteAddr();
108 3
        $this->output->write(sprintf('%s <fg=blue>%s</>', $this->translator->translate('Processing IP'), $ipAddr));
109 3
        if ($ipAddr === IpAddress::LOCALHOST) {
110 1
            $this->output->writeln(
111 1
                sprintf(' [<comment>%s</comment>]', $this->translator->translate('Ignored localhost address'))
112
            );
113 1
            throw new IpCannotBeLocatedException('Ignored localhost address');
114
        }
115
116
        try {
117 2
            return $this->ipLocationResolver->resolveIpLocation($ipAddr);
118 1
        } catch (WrongIpException $e) {
119 1
            $this->output->writeln(
120 1
                sprintf(
121 1
                    ' [<fg=red>%s</>]',
122 1
                    $this->translator->translate('An error occurred while locating IP. Skipped')
123
                )
124
            );
125 1
            if ($this->output->isVerbose()) {
126 1
                $this->getApplication()->renderException($e, $this->output);
127
            }
128
129 1
            throw new IpCannotBeLocatedException('An error occurred while locating IP', 0, $e);
130
        }
131
    }
132
}
133