diff --git a/module/Core/src/EventDispatcher/LocateVisit.php b/module/Core/src/EventDispatcher/LocateVisit.php index aa6afed8..6f7fb7e8 100644 --- a/module/Core/src/EventDispatcher/LocateVisit.php +++ b/module/Core/src/EventDispatcher/LocateVisit.php @@ -17,7 +17,7 @@ use Shlinkio\Shlink\IpGeolocation\Model\Location; use Shlinkio\Shlink\IpGeolocation\Resolver\IpLocationResolverInterface; use Throwable; -class LocateVisit +readonly class LocateVisit { public function __construct( private IpLocationResolverInterface $ipLocationResolver, diff --git a/module/Core/src/Visit/VisitsTracker.php b/module/Core/src/Visit/VisitsTracker.php index dd520e8f..f2e26493 100644 --- a/module/Core/src/Visit/VisitsTracker.php +++ b/module/Core/src/Visit/VisitsTracker.php @@ -71,12 +71,15 @@ readonly class VisitsTracker implements VisitsTrackerInterface return; } - $this->em->wrapInTransaction(function () use ($createVisit, $visitor): void { - $visit = $createVisit($visitor->normalizeForTrackingOptions($this->options)); + $visit = $createVisit($visitor->normalizeForTrackingOptions($this->options)); + + // Wrap persisting and flushing the visit in a transaction, so that the ShortUrlVisitsCountTracker performs + // changes inside that very same transaction atomically + $this->em->wrapInTransaction(function () use ($visit): void { $this->em->persist($visit); $this->em->flush(); - - $this->eventDispatcher->dispatch(new UrlVisited($visit->getId(), $visitor->remoteAddress)); }); + + $this->eventDispatcher->dispatch(new UrlVisited($visit->getId(), $visitor->remoteAddress)); } }