Completed
Push — develop ( f79a36...9f24b8 )
by Alejandro
19s queued 14s
created

LocateShortUrlVisit::downloadOrUpdateGeoLiteDb()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 19
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 4.0119

Importance

Changes 0
Metric Value
cc 4
eloc 11
nc 3
nop 1
dl 0
loc 19
ccs 10
cts 11
cp 0.9091
crap 4.0119
rs 9.9
c 0
b 0
f 0
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\Common\Doctrine\ReopeningEntityManager;
13
use Shlinkio\Shlink\Core\Entity\Visit;
14
use Shlinkio\Shlink\Core\Entity\VisitLocation;
15
use Shlinkio\Shlink\IpGeolocation\Exception\WrongIpException;
16
use Shlinkio\Shlink\IpGeolocation\Model\Location;
17
use Shlinkio\Shlink\IpGeolocation\Resolver\IpLocationResolverInterface;
18
19
use function sprintf;
20
21
class LocateShortUrlVisit
22
{
23
    private IpLocationResolverInterface $ipLocationResolver;
24
    private EntityManagerInterface $em;
25
    private LoggerInterface $logger;
26
    private GeolocationDbUpdaterInterface $dbUpdater;
27
    private EventDispatcherInterface $eventDispatcher;
28
29 9
    public function __construct(
30
        IpLocationResolverInterface $ipLocationResolver,
31
        EntityManagerInterface $em,
32
        LoggerInterface $logger,
33
        GeolocationDbUpdaterInterface $dbUpdater,
34
        EventDispatcherInterface $eventDispatcher
35
    ) {
36 9
        $this->ipLocationResolver = $ipLocationResolver;
37 9
        $this->em = $em;
38 9
        $this->logger = $logger;
39 9
        $this->dbUpdater = $dbUpdater;
40 9
        $this->eventDispatcher = $eventDispatcher;
41
    }
42
43 9
    public function __invoke(ShortUrlVisited $shortUrlVisited): void
44
    {
45
        // FIXME Temporarily handling DB connection reset here to fix https://github.com/shlinkio/shlink/issues/717
46
        //       Remove when https://github.com/shlinkio/shlink-event-dispatcher/issues/23 is implemented
47 9
        if ($this->em instanceof ReopeningEntityManager) {
48
            $this->em->open();
0 ignored issues
show
Bug introduced by
The method open() does not exist on Doctrine\ORM\EntityManagerInterface. It seems like you code against a sub-type of Doctrine\ORM\EntityManagerInterface such as Shlinkio\Shlink\Common\D...\ReopeningEntityManager. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

48
            $this->em->/** @scrutinizer ignore-call */ 
49
                       open();
Loading history...
49
        }
50
51 9
        $visitId = $shortUrlVisited->visitId();
52
53
        try {
54
            /** @var Visit|null $visit */
55 9
            $visit = $this->em->find(Visit::class, $visitId);
56 9
            if ($visit === null) {
57 1
                $this->logger->warning('Tried to locate visit with id "{visitId}", but it does not exist.', [
58 1
                    'visitId' => $visitId,
59
                ]);
60 1
                return;
61
            }
62
63 8
            if ($this->downloadOrUpdateGeoLiteDb($visitId)) {
64 7
                $this->locateVisit($visitId, $shortUrlVisited->originalIpAddress(), $visit);
65
            }
66
67 8
            $this->eventDispatcher->dispatch(new VisitLocated($visitId));
68 8
        } finally {
69
            // FIXME Temporarily handling DB connection reset here to fix https://github.com/shlinkio/shlink/issues/717
70
            //       Remove when https://github.com/shlinkio/shlink-event-dispatcher/issues/23 is implemented
71 9
            $this->em->getConnection()->close();
72 9
            $this->em->clear();
73
        }
74
    }
75
76 8
    private function downloadOrUpdateGeoLiteDb(string $visitId): bool
77
    {
78
        try {
79
            $this->dbUpdater->checkDbUpdate(function (bool $olderDbExists): void {
80
                $this->logger->notice(sprintf('%s GeoLite2 database...', $olderDbExists ? 'Updating' : 'Downloading'));
81 8
            });
82 2
        } catch (GeolocationDbUpdateFailedException $e) {
83 2
            if (! $e->olderDbExists()) {
84 1
                $this->logger->error(
85 1
                    'GeoLite2 database download failed. It is not possible to locate visit with id {visitId}. {e}',
86 1
                    ['e' => $e, 'visitId' => $visitId],
87
                );
88 1
                return false;
89
            }
90
91 1
            $this->logger->warning('GeoLite2 database update failed. Proceeding with old version. {e}', ['e' => $e]);
92
        }
93
94 7
        return true;
95
    }
96
97 7
    private function locateVisit(string $visitId, ?string $originalIpAddress, Visit $visit): void
98
    {
99 7
        $isLocatable = $originalIpAddress !== null || $visit->isLocatable();
100 7
        $addr = $originalIpAddress ?? $visit->getRemoteAddr();
101
102
        try {
103 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

103
            $location = $isLocatable ? $this->ipLocationResolver->resolveIpLocation(/** @scrutinizer ignore-type */ $addr) : Location::emptyInstance();
Loading history...
104
105 6
            $visit->locate(new VisitLocation($location));
106 6
            $this->em->flush();
107 1
        } catch (WrongIpException $e) {
108 1
            $this->logger->warning(
109 1
                'Tried to locate visit with id "{visitId}", but its address seems to be wrong. {e}',
110 1
                ['e' => $e, 'visitId' => $visitId],
111
            );
112
        }
113
    }
114
}
115