diff --git a/module/CLI/src/Command/Visit/ProcessVisitsCommand.php b/module/CLI/src/Command/Visit/ProcessVisitsCommand.php
index 25006cc9..215977d8 100644
--- a/module/CLI/src/Command/Visit/ProcessVisitsCommand.php
+++ b/module/CLI/src/Command/Visit/ProcessVisitsCommand.php
@@ -83,14 +83,14 @@ class ProcessVisitsCommand extends Command
'Ignored visit with no IP address',
OutputInterface::VERBOSITY_VERBOSE
);
- throw new IpCannotBeLocatedException('Ignored visit with no IP address');
+ throw IpCannotBeLocatedException::forEmptyAddress();
}
$ipAddr = $visit->getRemoteAddr();
$this->output->write(sprintf('Processing IP %s>', $ipAddr));
if ($ipAddr === IpAddress::LOCALHOST) {
$this->output->writeln(' [Ignored localhost address]');
- throw new IpCannotBeLocatedException('Ignored localhost address');
+ throw IpCannotBeLocatedException::forLocalhost();
}
try {
@@ -101,7 +101,7 @@ class ProcessVisitsCommand extends Command
$this->getApplication()->renderException($e, $this->output);
}
- throw new IpCannotBeLocatedException('An error occurred while locating IP', $e->getCode(), $e);
+ throw IpCannotBeLocatedException::forError($e);
}
}
}
diff --git a/module/Core/src/Exception/IpCannotBeLocatedException.php b/module/Core/src/Exception/IpCannotBeLocatedException.php
index e172e9c9..3908385e 100644
--- a/module/Core/src/Exception/IpCannotBeLocatedException.php
+++ b/module/Core/src/Exception/IpCannotBeLocatedException.php
@@ -3,6 +3,43 @@ declare(strict_types=1);
namespace Shlinkio\Shlink\Core\Exception;
+use Throwable;
+
class IpCannotBeLocatedException extends RuntimeException
{
+ /** @var bool */
+ private $isNonLocatableAddress;
+
+ public function __construct(
+ bool $isNonLocatableAddress,
+ string $message,
+ int $code = 0,
+ ?Throwable $previous = null
+ ) {
+ $this->isNonLocatableAddress = $isNonLocatableAddress;
+ parent::__construct($message, $code, $previous);
+ }
+
+ public static function forEmptyAddress(): self
+ {
+ return new self(true, 'Ignored visit with no IP address');
+ }
+
+ public static function forLocalhost(): self
+ {
+ return new self(true, 'Ignored localhost address');
+ }
+
+ public static function forError(Throwable $e): self
+ {
+ return new self(false, 'An error occurred while locating IP', $e->getCode(), $e);
+ }
+
+ /**
+ * Tells if this error belongs to an address that will never be possible locate
+ */
+ public function isNonLocatableAddress(): bool
+ {
+ return $this->isNonLocatableAddress;
+ }
}
diff --git a/module/Core/src/Service/VisitService.php b/module/Core/src/Service/VisitService.php
index 4abbf4a2..62e48010 100644
--- a/module/Core/src/Service/VisitService.php
+++ b/module/Core/src/Service/VisitService.php
@@ -30,12 +30,18 @@ class VisitService implements VisitServiceInterface
foreach ($results as $visit) {
$count++;
+
try {
/** @var Location $location */
$location = $geolocateVisit($visit);
} catch (IpCannotBeLocatedException $e) {
- // Skip if the visit's IP could not be located
- continue;
+ if (!$e->isNonLocatableAddress()) {
+ // Skip if the visit's IP could not be located because of an error
+ continue;
+ }
+
+ // If the IP address is non-locatable, locate it as empty to prevent next processes to pick it again
+ $location = Location::emptyInstance();
}
$location = new VisitLocation($location);
diff --git a/module/Core/test/Service/VisitServiceTest.php b/module/Core/test/Service/VisitServiceTest.php
index be2bf8ba..46f1da37 100644
--- a/module/Core/test/Service/VisitServiceTest.php
+++ b/module/Core/test/Service/VisitServiceTest.php
@@ -89,7 +89,7 @@ class VisitServiceTest extends TestCase
});
$this->visitService->locateUnlocatedVisits(function () {
- throw new IpCannotBeLocatedException('Cannot be located');
+ throw new IpCannotBeLocatedException(false, 'Cannot be located');
});
$findUnlocatedVisits->shouldHaveBeenCalledOnce();