Passed
Pull Request — master (#357)
by Alejandro
05:24
created

ProcessVisitsCommand::configure()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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