diff --git a/module/CLI/src/Command/Visit/ProcessVisitsCommand.php b/module/CLI/src/Command/Visit/ProcessVisitsCommand.php index a5a33fcb..117ad80f 100644 --- a/module/CLI/src/Command/Visit/ProcessVisitsCommand.php +++ b/module/CLI/src/Command/Visit/ProcessVisitsCommand.php @@ -6,6 +6,7 @@ namespace Shlinkio\Shlink\CLI\Command\Visit; use Shlinkio\Shlink\Common\Exception\WrongIpException; use Shlinkio\Shlink\Common\IpGeolocation\IpLocationResolverInterface; use Shlinkio\Shlink\Common\Util\IpAddress; +use Shlinkio\Shlink\Core\Entity\Visit; use Shlinkio\Shlink\Core\Entity\VisitLocation; use Shlinkio\Shlink\Core\Service\VisitServiceInterface; use Symfony\Component\Console\Command\Command; @@ -57,47 +58,52 @@ class ProcessVisitsCommand extends Command $visits = $this->visitService->getUnlocatedVisits(); foreach ($visits as $visit) { - if (! $visit->hasRemoteAddr()) { - $io->writeln( - sprintf('%s', $this->translator->translate('Ignored visit with no IP address')), - OutputInterface::VERBOSITY_VERBOSE - ); - continue; - } - - $ipAddr = $visit->getRemoteAddr(); - $io->write(sprintf('%s %s', $this->translator->translate('Processing IP'), $ipAddr)); - if ($ipAddr === IpAddress::LOCALHOST) { - $io->writeln( - sprintf(' [%s]', $this->translator->translate('Ignored localhost address')) - ); - continue; - } - - try { - $result = $this->ipLocationResolver->resolveIpLocation($ipAddr); - - $location = new VisitLocation($result); - $visit->setVisitLocation($location); - $this->visitService->saveVisit($visit); - - $io->writeln(sprintf( - ' [' . $this->translator->translate('Address located at "%s"') . ']', - $location->getCountryName() - )); - } catch (WrongIpException $e) { - $io->writeln( - sprintf( - ' [%s]', - $this->translator->translate('An error occurred while locating IP. Skipped') - ) - ); - if ($io->isVerbose()) { - $this->getApplication()->renderException($e, $output); - } - } + $this->processVisit($io, $visit, false); } $io->success($this->translator->translate('Finished processing all IPs')); } + + private function processVisit(SymfonyStyle $io, Visit $visit, bool $clear): void + { + if (! $visit->hasRemoteAddr()) { + $io->writeln( + sprintf('%s', $this->translator->translate('Ignored visit with no IP address')), + OutputInterface::VERBOSITY_VERBOSE + ); + return; + } + + $ipAddr = $visit->getRemoteAddr(); + $io->write(sprintf('%s %s', $this->translator->translate('Processing IP'), $ipAddr)); + if ($ipAddr === IpAddress::LOCALHOST) { + $io->writeln( + sprintf(' [%s]', $this->translator->translate('Ignored localhost address')) + ); + return; + } + + try { + $result = $this->ipLocationResolver->resolveIpLocation($ipAddr); + } catch (WrongIpException $e) { + $io->writeln( + sprintf( + ' [%s]', + $this->translator->translate('An error occurred while locating IP. Skipped') + ) + ); + if ($io->isVerbose()) { + $this->getApplication()->renderException($e, $output); + } + + return; + } + + $location = new VisitLocation($result); + $this->visitService->locateVisit($visit, $location, $clear); + $io->writeln(sprintf( + ' [' . $this->translator->translate('Address located at "%s"') . ']', + $location->getCountryName() + )); + } } diff --git a/module/CLI/test/Command/ShortUrl/GetVisitsCommandTest.php b/module/CLI/test/Command/ShortUrl/GetVisitsCommandTest.php index f7523d78..d36c798a 100644 --- a/module/CLI/test/Command/ShortUrl/GetVisitsCommandTest.php +++ b/module/CLI/test/Command/ShortUrl/GetVisitsCommandTest.php @@ -81,7 +81,7 @@ class GetVisitsCommandTest extends TestCase { $shortCode = 'abc123'; $this->visitsTracker->info($shortCode, Argument::any())->willReturn([ - (new Visit(new ShortUrl(''), new Visitor('bar', 'foo', '')))->setVisitLocation( + (new Visit(new ShortUrl(''), new Visitor('bar', 'foo', '')))->locate( new VisitLocation(['country_name' => 'Spain']) ), ])->shouldBeCalledOnce(); diff --git a/module/CLI/test/Command/Visit/ProcessVisitsCommandTest.php b/module/CLI/test/Command/Visit/ProcessVisitsCommandTest.php index 5a28e5ab..a0266d4e 100644 --- a/module/CLI/test/Command/Visit/ProcessVisitsCommandTest.php +++ b/module/CLI/test/Command/Visit/ProcessVisitsCommandTest.php @@ -64,7 +64,7 @@ class ProcessVisitsCommandTest extends TestCase $this->visitService->getUnlocatedVisits()->willReturn($visits) ->shouldBeCalledOnce(); - $this->visitService->saveVisit(Argument::any())->shouldBeCalledTimes(count($visits)); + $this->visitService->locateVisit(Argument::cetera())->shouldBeCalledTimes(count($visits)); $this->ipResolver->resolveIpLocation(Argument::any())->willReturn([]) ->shouldBeCalledTimes(count($visits)); @@ -96,7 +96,7 @@ class ProcessVisitsCommandTest extends TestCase $this->visitService->getUnlocatedVisits()->willReturn($visits) ->shouldBeCalledOnce(); - $this->visitService->saveVisit(Argument::any())->shouldBeCalledTimes(count($visits) - 4); + $this->visitService->locateVisit(Argument::cetera())->shouldBeCalledTimes(count($visits) - 4); $this->ipResolver->resolveIpLocation(Argument::any())->willReturn([]) ->shouldBeCalledTimes(count($visits) - 4); diff --git a/module/Core/src/Entity/Visit.php b/module/Core/src/Entity/Visit.php index 54926e9e..3fd98db0 100644 --- a/module/Core/src/Entity/Visit.php +++ b/module/Core/src/Entity/Visit.php @@ -93,7 +93,7 @@ class Visit extends AbstractEntity implements JsonSerializable return $this->visitLocation; } - public function setVisitLocation(VisitLocation $visitLocation): self + public function locate(VisitLocation $visitLocation): self { $this->visitLocation = $visitLocation; return $this; diff --git a/module/Core/src/Service/VisitService.php b/module/Core/src/Service/VisitService.php index ab18ce1c..9aea80cb 100644 --- a/module/Core/src/Service/VisitService.php +++ b/module/Core/src/Service/VisitService.php @@ -5,6 +5,7 @@ namespace Shlinkio\Shlink\Core\Service; use Doctrine\ORM\EntityManagerInterface; use Shlinkio\Shlink\Core\Entity\Visit; +use Shlinkio\Shlink\Core\Entity\VisitLocation; use Shlinkio\Shlink\Core\Repository\VisitRepository; class VisitService implements VisitServiceInterface @@ -22,19 +23,23 @@ class VisitService implements VisitServiceInterface /** * @return Visit[] */ - public function getUnlocatedVisits() + public function getUnlocatedVisits(): array { /** @var VisitRepository $repo */ $repo = $this->em->getRepository(Visit::class); return $repo->findUnlocatedVisits(); } - /** - * @param Visit $visit - */ - public function saveVisit(Visit $visit) + public function locateVisit(Visit $visit, VisitLocation $location, bool $clear = false): void { + $visit->locate($location); + $this->em->persist($visit); $this->em->flush(); + + if ($clear) { + $this->em->clear(VisitLocation::class); + $this->em->clear(Visit::class); + } } } diff --git a/module/Core/src/Service/VisitServiceInterface.php b/module/Core/src/Service/VisitServiceInterface.php index 46f6ceb7..2b5d5811 100644 --- a/module/Core/src/Service/VisitServiceInterface.php +++ b/module/Core/src/Service/VisitServiceInterface.php @@ -4,16 +4,14 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Service; use Shlinkio\Shlink\Core\Entity\Visit; +use Shlinkio\Shlink\Core\Entity\VisitLocation; interface VisitServiceInterface { /** * @return Visit[] */ - public function getUnlocatedVisits(); + public function getUnlocatedVisits(): array; - /** - * @param Visit $visit - */ - public function saveVisit(Visit $visit); + public function locateVisit(Visit $visit, VisitLocation $location, bool $clear = false): void; } diff --git a/module/Core/test-func/Repository/VisitRepositoryTest.php b/module/Core/test-func/Repository/VisitRepositoryTest.php index 886cce70..683009cd 100644 --- a/module/Core/test-func/Repository/VisitRepositoryTest.php +++ b/module/Core/test-func/Repository/VisitRepositoryTest.php @@ -45,7 +45,7 @@ class VisitRepositoryTest extends DatabaseTestCase if ($i % 2 === 0) { $location = new VisitLocation([]); $this->getEntityManager()->persist($location); - $visit->setVisitLocation($location); + $visit->locate($location); } $this->getEntityManager()->persist($visit); diff --git a/module/Core/test/Service/VisitServiceTest.php b/module/Core/test/Service/VisitServiceTest.php index 728d18ee..cc8193be 100644 --- a/module/Core/test/Service/VisitServiceTest.php +++ b/module/Core/test/Service/VisitServiceTest.php @@ -8,6 +8,7 @@ use PHPUnit\Framework\TestCase; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Entity\Visit; +use Shlinkio\Shlink\Core\Entity\VisitLocation; use Shlinkio\Shlink\Core\Model\Visitor; use Shlinkio\Shlink\Core\Repository\VisitRepository; use Shlinkio\Shlink\Core\Service\VisitService; @@ -32,12 +33,12 @@ class VisitServiceTest extends TestCase /** * @test */ - public function saveVisitsPersistsProvidedVisit() + public function locateVisitPersistsProvidedVisit() { $visit = new Visit(new ShortUrl(''), Visitor::emptyInstance()); $this->em->persist($visit)->shouldBeCalledOnce(); $this->em->flush()->shouldBeCalledOnce(); - $this->visitService->saveVisit($visit); + $this->visitService->locateVisit($visit, new VisitLocation([])); } /**