LocateVisit.php 3.6 KB

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