Completed
Pull Request — develop (#724)
by Alejandro
04:45
created

LocateShortUrlVisit::locateVisit()   A

Complexity

Conditions 4
Paths 16

Size

Total Lines 14
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
eloc 10
nc 16
nop 3
dl 0
loc 14
rs 9.9332
c 0
b 0
f 0
ccs 10
cts 10
cp 1
crap 4
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Shlinkio\Shlink\Core\EventDispatcher;
6
7
use Doctrine\ORM\EntityManagerInterface;
8
use Psr\EventDispatcher\EventDispatcherInterface;
9
use Psr\Log\LoggerInterface;
10
use Shlinkio\Shlink\CLI\Exception\GeolocationDbUpdateFailedException;
11
use Shlinkio\Shlink\CLI\Util\GeolocationDbUpdaterInterface;
12
use Shlinkio\Shlink\Core\Entity\Visit;
13
use Shlinkio\Shlink\Core\Entity\VisitLocation;
14
use Shlinkio\Shlink\IpGeolocation\Exception\WrongIpException;
15
use Shlinkio\Shlink\IpGeolocation\Model\Location;
16
use Shlinkio\Shlink\IpGeolocation\Resolver\IpLocationResolverInterface;
17
18
use function sprintf;
19
20
class LocateShortUrlVisit
21
{
22
    private IpLocationResolverInterface $ipLocationResolver;
23
    private EntityManagerInterface $em;
24
    private LoggerInterface $logger;
25
    private GeolocationDbUpdaterInterface $dbUpdater;
26
    private EventDispatcherInterface $eventDispatcher;
27
28 9
    public function __construct(
29
        IpLocationResolverInterface $ipLocationResolver,
30
        EntityManagerInterface $em,
31
        LoggerInterface $logger,
32
        GeolocationDbUpdaterInterface $dbUpdater,
33
        EventDispatcherInterface $eventDispatcher
34
    ) {
35 9
        $this->ipLocationResolver = $ipLocationResolver;
36 9
        $this->em = $em;
37 9
        $this->logger = $logger;
38 9
        $this->dbUpdater = $dbUpdater;
39 9
        $this->eventDispatcher = $eventDispatcher;
40
    }
41
42 9
    public function __invoke(ShortUrlVisited $shortUrlVisited): void
43
    {
44 9
        $visitId = $shortUrlVisited->visitId();
45
46
        /** @var Visit|null $visit */
47 9
        $visit = $this->em->find(Visit::class, $visitId);
48 9
        if ($visit === null) {
49 1
            $this->logger->warning('Tried to locate visit with id "{visitId}", but it does not exist.', [
50 1
                'visitId' => $visitId,
51
            ]);
52 1
            return;
53
        }
54
55 8
        if ($this->downloadOrUpdateGeoLiteDb($visitId)) {
56 7
            $this->locateVisit($visitId, $shortUrlVisited->originalIpAddress(), $visit);
57
        }
58
59 8
        $this->eventDispatcher->dispatch(new VisitLocated($visitId));
60
    }
61
62 8
    private function downloadOrUpdateGeoLiteDb(string $visitId): bool
63
    {
64
        try {
65
            $this->dbUpdater->checkDbUpdate(function (bool $olderDbExists): void {
66
                $this->logger->notice(sprintf('%s GeoLite2 database...', $olderDbExists ? 'Updating' : 'Downloading'));
67 8
            });
68 2
        } catch (GeolocationDbUpdateFailedException $e) {
69 2
            if (! $e->olderDbExists()) {
70 1
                $this->logger->error(
71 1
                    'GeoLite2 database download failed. It is not possible to locate visit with id {visitId}. {e}',
72 1
                    ['e' => $e, 'visitId' => $visitId],
73
                );
74 1
                return false;
75
            }
76
77 1
            $this->logger->warning('GeoLite2 database update failed. Proceeding with old version. {e}', ['e' => $e]);
78
        }
79
80 7
        return true;
81
    }
82
83 7
    private function locateVisit(string $visitId, ?string $originalIpAddress, Visit $visit): void
84
    {
85 7
        $isLocatable = $originalIpAddress !== null || $visit->isLocatable();
86 7
        $addr = $originalIpAddress ?? $visit->getRemoteAddr();
87
88
        try {
89 7
            $location = $isLocatable ? $this->ipLocationResolver->resolveIpLocation($addr) : Location::emptyInstance();
0 ignored issues
show
Bug introduced by
It seems like $addr can also be of type null; however, parameter $ipAddress of Shlinkio\Shlink\IpGeoloc...ce::resolveIpLocation() 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

89
            $location = $isLocatable ? $this->ipLocationResolver->resolveIpLocation(/** @scrutinizer ignore-type */ $addr) : Location::emptyInstance();
Loading history...
90
91 6
            $visit->locate(new VisitLocation($location));
92 6
            $this->em->flush();
93 1
        } catch (WrongIpException $e) {
94 1
            $this->logger->warning(
95 1
                'Tried to locate visit with id "{visitId}", but its address seems to be wrong. {e}',
96 1
                ['e' => $e, 'visitId' => $visitId],
97
            );
98
        }
99
    }
100
}
101