mirror of
https://github.com/shlinkio/shlink.git
synced 2026-03-10 01:03:13 +08:00
Split some logic from VisitRepository into its own injectable repository
This commit is contained in:
@@ -8,18 +8,15 @@ use Doctrine\ORM\EntityManagerInterface;
|
||||
use Shlinkio\Shlink\Core\Exception\IpCannotBeLocatedException;
|
||||
use Shlinkio\Shlink\Core\Visit\Entity\Visit;
|
||||
use Shlinkio\Shlink\Core\Visit\Entity\VisitLocation;
|
||||
use Shlinkio\Shlink\Core\Visit\Repository\VisitRepositoryInterface;
|
||||
use Shlinkio\Shlink\Core\Visit\Repository\VisitLocationRepositoryInterface;
|
||||
use Shlinkio\Shlink\IpGeolocation\Model\Location;
|
||||
|
||||
class VisitLocator implements VisitLocatorInterface
|
||||
{
|
||||
private VisitRepositoryInterface $repo;
|
||||
|
||||
public function __construct(private EntityManagerInterface $em)
|
||||
{
|
||||
/** @var VisitRepositoryInterface $repo */
|
||||
$repo = $em->getRepository(Visit::class);
|
||||
$this->repo = $repo;
|
||||
public function __construct(
|
||||
private readonly EntityManagerInterface $em,
|
||||
private readonly VisitLocationRepositoryInterface $repo,
|
||||
) {
|
||||
}
|
||||
|
||||
public function locateUnlocatedVisits(VisitGeolocationHelperInterface $helper): void
|
||||
|
||||
74
module/Core/src/Visit/Repository/VisitLocationRepository.php
Normal file
74
module/Core/src/Visit/Repository/VisitLocationRepository.php
Normal file
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Core\Visit\Repository;
|
||||
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Happyr\DoctrineSpecification\Repository\EntitySpecificationRepository;
|
||||
use Shlinkio\Shlink\Core\Visit\Entity\Visit;
|
||||
|
||||
class VisitLocationRepository extends EntitySpecificationRepository implements VisitLocationRepositoryInterface
|
||||
{
|
||||
/**
|
||||
* @return iterable<Visit>
|
||||
*/
|
||||
public function findUnlocatedVisits(int $blockSize = self::DEFAULT_BLOCK_SIZE): iterable
|
||||
{
|
||||
$qb = $this->getEntityManager()->createQueryBuilder();
|
||||
$qb->select('v')
|
||||
->from(Visit::class, 'v')
|
||||
->where($qb->expr()->isNull('v.visitLocation'));
|
||||
|
||||
return $this->visitsIterableForQuery($qb, $blockSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return iterable<Visit>
|
||||
*/
|
||||
public function findVisitsWithEmptyLocation(int $blockSize = self::DEFAULT_BLOCK_SIZE): iterable
|
||||
{
|
||||
$qb = $this->getEntityManager()->createQueryBuilder();
|
||||
$qb->select('v')
|
||||
->from(Visit::class, 'v')
|
||||
->join('v.visitLocation', 'vl')
|
||||
->where($qb->expr()->isNotNull('v.visitLocation'))
|
||||
->andWhere($qb->expr()->eq('vl.isEmpty', ':isEmpty'))
|
||||
->setParameter('isEmpty', true);
|
||||
|
||||
return $this->visitsIterableForQuery($qb, $blockSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return iterable<Visit>
|
||||
*/
|
||||
public function findAllVisits(int $blockSize = self::DEFAULT_BLOCK_SIZE): iterable
|
||||
{
|
||||
$qb = $this->createQueryBuilder('v');
|
||||
return $this->visitsIterableForQuery($qb, $blockSize);
|
||||
}
|
||||
|
||||
private function visitsIterableForQuery(QueryBuilder $qb, int $blockSize): iterable
|
||||
{
|
||||
$originalQueryBuilder = $qb->setMaxResults($blockSize)
|
||||
->orderBy('v.id', 'ASC');
|
||||
$lastId = '0';
|
||||
|
||||
do {
|
||||
$qb = (clone $originalQueryBuilder)->andWhere($qb->expr()->gt('v.id', $lastId));
|
||||
$iterator = $qb->getQuery()->toIterable();
|
||||
$resultsFound = false;
|
||||
/** @var Visit|null $lastProcessedVisit */
|
||||
$lastProcessedVisit = null;
|
||||
|
||||
foreach ($iterator as $key => $visit) {
|
||||
$resultsFound = true;
|
||||
$lastProcessedVisit = $visit;
|
||||
yield $key => $visit;
|
||||
}
|
||||
|
||||
// As the query is ordered by ID, we can take the last one every time in order to exclude the whole list
|
||||
$lastId = $lastProcessedVisit?->getId() ?? $lastId;
|
||||
} while ($resultsFound);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Core\Visit\Repository;
|
||||
|
||||
use Shlinkio\Shlink\Core\Visit\Entity\Visit;
|
||||
|
||||
interface VisitLocationRepositoryInterface
|
||||
{
|
||||
public const DEFAULT_BLOCK_SIZE = 10000;
|
||||
|
||||
/**
|
||||
* @return iterable<Visit>
|
||||
*/
|
||||
public function findUnlocatedVisits(int $blockSize = self::DEFAULT_BLOCK_SIZE): iterable;
|
||||
|
||||
/**
|
||||
* @return iterable<Visit>
|
||||
*/
|
||||
public function findVisitsWithEmptyLocation(int $blockSize = self::DEFAULT_BLOCK_SIZE): iterable;
|
||||
|
||||
/**
|
||||
* @return iterable<Visit>
|
||||
*/
|
||||
public function findAllVisits(int $blockSize = self::DEFAULT_BLOCK_SIZE): iterable;
|
||||
}
|
||||
@@ -22,65 +22,6 @@ use const PHP_INT_MAX;
|
||||
|
||||
class VisitRepository extends EntitySpecificationRepository implements VisitRepositoryInterface
|
||||
{
|
||||
/**
|
||||
* @return iterable|Visit[]
|
||||
*/
|
||||
public function findUnlocatedVisits(int $blockSize = self::DEFAULT_BLOCK_SIZE): iterable
|
||||
{
|
||||
$qb = $this->getEntityManager()->createQueryBuilder();
|
||||
$qb->select('v')
|
||||
->from(Visit::class, 'v')
|
||||
->where($qb->expr()->isNull('v.visitLocation'));
|
||||
|
||||
return $this->visitsIterableForQuery($qb, $blockSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return iterable|Visit[]
|
||||
*/
|
||||
public function findVisitsWithEmptyLocation(int $blockSize = self::DEFAULT_BLOCK_SIZE): iterable
|
||||
{
|
||||
$qb = $this->getEntityManager()->createQueryBuilder();
|
||||
$qb->select('v')
|
||||
->from(Visit::class, 'v')
|
||||
->join('v.visitLocation', 'vl')
|
||||
->where($qb->expr()->isNotNull('v.visitLocation'))
|
||||
->andWhere($qb->expr()->eq('vl.isEmpty', ':isEmpty'))
|
||||
->setParameter('isEmpty', true);
|
||||
|
||||
return $this->visitsIterableForQuery($qb, $blockSize);
|
||||
}
|
||||
|
||||
public function findAllVisits(int $blockSize = self::DEFAULT_BLOCK_SIZE): iterable
|
||||
{
|
||||
$qb = $this->createQueryBuilder('v');
|
||||
return $this->visitsIterableForQuery($qb, $blockSize);
|
||||
}
|
||||
|
||||
private function visitsIterableForQuery(QueryBuilder $qb, int $blockSize): iterable
|
||||
{
|
||||
$originalQueryBuilder = $qb->setMaxResults($blockSize)
|
||||
->orderBy('v.id', 'ASC');
|
||||
$lastId = '0';
|
||||
|
||||
do {
|
||||
$qb = (clone $originalQueryBuilder)->andWhere($qb->expr()->gt('v.id', $lastId));
|
||||
$iterator = $qb->getQuery()->toIterable();
|
||||
$resultsFound = false;
|
||||
/** @var Visit|null $lastProcessedVisit */
|
||||
$lastProcessedVisit = null;
|
||||
|
||||
foreach ($iterator as $key => $visit) {
|
||||
$resultsFound = true;
|
||||
$lastProcessedVisit = $visit;
|
||||
yield $key => $visit;
|
||||
}
|
||||
|
||||
// As the query is ordered by ID, we can take the last one every time in order to exclude the whole list
|
||||
$lastId = $lastProcessedVisit?->getId() ?? $lastId;
|
||||
} while ($resultsFound);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Visit[]
|
||||
*/
|
||||
|
||||
@@ -11,26 +11,8 @@ use Shlinkio\Shlink\Core\Visit\Entity\Visit;
|
||||
use Shlinkio\Shlink\Core\Visit\Persistence\VisitsCountFiltering;
|
||||
use Shlinkio\Shlink\Core\Visit\Persistence\VisitsListFiltering;
|
||||
|
||||
// TODO Split into VisitsListsRepository and VisitsLocationRepository
|
||||
interface VisitRepositoryInterface extends ObjectRepository, EntitySpecificationRepositoryInterface
|
||||
{
|
||||
public const DEFAULT_BLOCK_SIZE = 10000;
|
||||
|
||||
/**
|
||||
* @return iterable|Visit[]
|
||||
*/
|
||||
public function findUnlocatedVisits(int $blockSize = self::DEFAULT_BLOCK_SIZE): iterable;
|
||||
|
||||
/**
|
||||
* @return iterable|Visit[]
|
||||
*/
|
||||
public function findVisitsWithEmptyLocation(int $blockSize = self::DEFAULT_BLOCK_SIZE): iterable;
|
||||
|
||||
/**
|
||||
* @return iterable|Visit[]
|
||||
*/
|
||||
public function findAllVisits(int $blockSize = self::DEFAULT_BLOCK_SIZE): iterable;
|
||||
|
||||
/**
|
||||
* @return Visit[]
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user