mirror of
https://github.com/shlinkio/shlink.git
synced 2025-03-27 20:11:34 +03:00
Merge pull request #1629 from acelaya-forks/feature/docker-8.2
Feature/docker 8.2
This commit is contained in:
commit
0c83dea8b7
23 changed files with 311 additions and 233 deletions
|
@ -16,11 +16,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this
|
||||||
* [#1599](https://github.com/shlinkio/shlink/issues/1599) Added support for credentials on redis DSNs, either only password, or both username and password.
|
* [#1599](https://github.com/shlinkio/shlink/issues/1599) Added support for credentials on redis DSNs, either only password, or both username and password.
|
||||||
* [#1616](https://github.com/shlinkio/shlink/issues/1616) Added support to import orphan visits when importing short URLs from another Shlink instance.
|
* [#1616](https://github.com/shlinkio/shlink/issues/1616) Added support to import orphan visits when importing short URLs from another Shlink instance.
|
||||||
* [#1519](https://github.com/shlinkio/shlink/issues/1519) Allowing to search short URLs by default domain.
|
* [#1519](https://github.com/shlinkio/shlink/issues/1519) Allowing to search short URLs by default domain.
|
||||||
* [#1555](https://github.com/shlinkio/shlink/issues/1555) Added full support for PHP 8.2, pdating the dockr image to this version.
|
* [#1555](https://github.com/shlinkio/shlink/issues/1555) and [#1625](https://github.com/shlinkio/shlink/issues/1625) Added full support for PHP 8.2, updating the docker image to this version.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
* [#1563](https://github.com/shlinkio/shlink/issues/1563) Moved logic to reuse command options to option classes instead of base abstract command classes.
|
* [#1563](https://github.com/shlinkio/shlink/issues/1563) Moved logic to reuse command options to option classes instead of base abstract command classes.
|
||||||
* [#1569](https://github.com/shlinkio/shlink/issues/1569) Migrated test doubles from phpspec/prophecy to PHPUnit mocks.
|
* [#1569](https://github.com/shlinkio/shlink/issues/1569) Migrated test doubles from phpspec/prophecy to PHPUnit mocks.
|
||||||
|
* [#1329](https://github.com/shlinkio/shlink/issues/1329) Split some logic from `VisitRepository` and `ShortUrlRepository` into separated repository classes.
|
||||||
|
|
||||||
### Deprecated
|
### Deprecated
|
||||||
* *Nothing*
|
* *Nothing*
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
FROM php:8.1.13-alpine3.17 as base
|
FROM php:8.2-alpine3.17 as base
|
||||||
|
|
||||||
ARG SHLINK_VERSION=latest
|
ARG SHLINK_VERSION=latest
|
||||||
ENV SHLINK_VERSION ${SHLINK_VERSION}
|
ENV SHLINK_VERSION ${SHLINK_VERSION}
|
||||||
|
@ -15,7 +15,7 @@ WORKDIR /etc/shlink
|
||||||
# Install required PHP extensions
|
# Install required PHP extensions
|
||||||
RUN \
|
RUN \
|
||||||
# Temp install dev dependencies needed to compile the extensions
|
# Temp install dev dependencies needed to compile the extensions
|
||||||
apk add --no-cache --virtual .dev-deps sqlite-dev postgresql-dev icu-dev libzip-dev zlib-dev libpng-dev && \
|
apk add --no-cache --virtual .dev-deps sqlite-dev postgresql-dev icu-dev libzip-dev zlib-dev libpng-dev linux-headers && \
|
||||||
docker-php-ext-install -j"$(nproc)" pdo_mysql pdo_pgsql intl calendar sockets bcmath zip gd && \
|
docker-php-ext-install -j"$(nproc)" pdo_mysql pdo_pgsql intl calendar sockets bcmath zip gd && \
|
||||||
apk add --no-cache sqlite-libs && \
|
apk add --no-cache sqlite-libs && \
|
||||||
docker-php-ext-install -j"$(nproc)" pdo_sqlite && \
|
docker-php-ext-install -j"$(nproc)" pdo_sqlite && \
|
||||||
|
|
|
@ -45,7 +45,7 @@
|
||||||
"php-middleware/request-id": "^4.1",
|
"php-middleware/request-id": "^4.1",
|
||||||
"pugx/shortid-php": "^1.1",
|
"pugx/shortid-php": "^1.1",
|
||||||
"ramsey/uuid": "^4.5",
|
"ramsey/uuid": "^4.5",
|
||||||
"shlinkio/shlink-common": "dev-main#e2a5bb7 as 5.2",
|
"shlinkio/shlink-common": "dev-main#107b753 as 5.2",
|
||||||
"shlinkio/shlink-config": "dev-main#96c81fb as 2.3",
|
"shlinkio/shlink-config": "dev-main#96c81fb as 2.3",
|
||||||
"shlinkio/shlink-event-dispatcher": "^2.6",
|
"shlinkio/shlink-event-dispatcher": "^2.6",
|
||||||
"shlinkio/shlink-importer": "dev-main#c97662b as 5.0",
|
"shlinkio/shlink-importer": "dev-main#c97662b as 5.0",
|
||||||
|
@ -135,7 +135,7 @@
|
||||||
"infect:ci:unit": "@infect:ci:base --coverage=build/coverage-unit --min-msi=80",
|
"infect:ci:unit": "@infect:ci:base --coverage=build/coverage-unit --min-msi=80",
|
||||||
"infect:ci:db": "@infect:ci:base --coverage=build/coverage-db --min-msi=95 --configuration=infection-db.json5",
|
"infect:ci:db": "@infect:ci:base --coverage=build/coverage-db --min-msi=95 --configuration=infection-db.json5",
|
||||||
"infect:ci:api": "@infect:ci:base --coverage=build/coverage-api --min-msi=80 --configuration=infection-api.json5",
|
"infect:ci:api": "@infect:ci:base --coverage=build/coverage-api --min-msi=80 --configuration=infection-api.json5",
|
||||||
"infect:ci:cli": "@infect:ci:base --coverage=build/coverage-cli --min-msi=80 --configuration=infection-cli.json5",
|
"infect:ci:cli": "@infect:ci:base --coverage=build/coverage-cli --min-msi=90 --configuration=infection-cli.json5",
|
||||||
"infect:ci": "@parallel infect:ci:unit infect:ci:db infect:ci:api infect:ci:cli",
|
"infect:ci": "@parallel infect:ci:unit infect:ci:db infect:ci:api infect:ci:cli",
|
||||||
"infect:test": [
|
"infect:test": [
|
||||||
"@parallel test:unit:ci test:db:sqlite:ci test:api:ci",
|
"@parallel test:unit:ci test:db:sqlite:ci test:api:ci",
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
FROM php:8.1.13-fpm-alpine3.17
|
FROM php:8.2-fpm-alpine3.17
|
||||||
MAINTAINER Alejandro Celaya <alejandro@alejandrocelaya.com>
|
MAINTAINER Alejandro Celaya <alejandro@alejandrocelaya.com>
|
||||||
|
|
||||||
ENV APCU_VERSION 5.1.21
|
ENV APCU_VERSION 5.1.21
|
||||||
|
@ -31,7 +31,9 @@ RUN docker-php-ext-install gd
|
||||||
RUN apk add --no-cache postgresql-dev
|
RUN apk add --no-cache postgresql-dev
|
||||||
RUN docker-php-ext-install pdo_pgsql
|
RUN docker-php-ext-install pdo_pgsql
|
||||||
|
|
||||||
RUN docker-php-ext-install sockets
|
RUN apk add --no-cache --virtual .phpize-deps $PHPIZE_DEPS linux-headers && \
|
||||||
|
docker-php-ext-install sockets && \
|
||||||
|
apk del .phpize-deps
|
||||||
RUN docker-php-ext-install bcmath
|
RUN docker-php-ext-install bcmath
|
||||||
|
|
||||||
# Install APCu extension
|
# Install APCu extension
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
FROM php:8.1.13-alpine3.17
|
FROM php:8.2-alpine3.17
|
||||||
MAINTAINER Alejandro Celaya <alejandro@alejandrocelaya.com>
|
MAINTAINER Alejandro Celaya <alejandro@alejandrocelaya.com>
|
||||||
|
|
||||||
ENV APCU_VERSION 5.1.21
|
ENV APCU_VERSION 5.1.21
|
||||||
|
@ -31,7 +31,9 @@ RUN docker-php-ext-install gd
|
||||||
RUN apk add --no-cache postgresql-dev
|
RUN apk add --no-cache postgresql-dev
|
||||||
RUN docker-php-ext-install pdo_pgsql
|
RUN docker-php-ext-install pdo_pgsql
|
||||||
|
|
||||||
RUN docker-php-ext-install sockets
|
RUN apk add --no-cache --virtual .phpize-deps $PHPIZE_DEPS linux-headers && \
|
||||||
|
docker-php-ext-install sockets && \
|
||||||
|
apk del .phpize-deps
|
||||||
RUN docker-php-ext-install bcmath
|
RUN docker-php-ext-install bcmath
|
||||||
|
|
||||||
# Install APCu extension
|
# Install APCu extension
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
FROM php:8.1.13-alpine3.17
|
FROM php:8.2-alpine3.17
|
||||||
MAINTAINER Alejandro Celaya <alejandro@alejandrocelaya.com>
|
MAINTAINER Alejandro Celaya <alejandro@alejandrocelaya.com>
|
||||||
|
|
||||||
ENV APCU_VERSION 5.1.21
|
ENV APCU_VERSION 5.1.21
|
||||||
|
@ -33,7 +33,9 @@ RUN docker-php-ext-install gd
|
||||||
RUN apk add --no-cache postgresql-dev
|
RUN apk add --no-cache postgresql-dev
|
||||||
RUN docker-php-ext-install pdo_pgsql
|
RUN docker-php-ext-install pdo_pgsql
|
||||||
|
|
||||||
RUN docker-php-ext-install sockets
|
RUN apk add --no-cache --virtual .phpize-deps $PHPIZE_DEPS linux-headers && \
|
||||||
|
docker-php-ext-install sockets && \
|
||||||
|
apk del .phpize-deps
|
||||||
RUN docker-php-ext-install bcmath
|
RUN docker-php-ext-install bcmath
|
||||||
|
|
||||||
# Install APCu extension
|
# Install APCu extension
|
||||||
|
|
|
@ -50,6 +50,10 @@ return [
|
||||||
EntityRepositoryFactory::class,
|
EntityRepositoryFactory::class,
|
||||||
ShortUrl\Entity\ShortUrl::class,
|
ShortUrl\Entity\ShortUrl::class,
|
||||||
],
|
],
|
||||||
|
ShortUrl\Repository\CrawlableShortCodesQuery::class => [
|
||||||
|
EntityRepositoryFactory::class,
|
||||||
|
ShortUrl\Entity\ShortUrl::class,
|
||||||
|
],
|
||||||
|
|
||||||
Tag\TagService::class => ConfigAbstractFactory::class,
|
Tag\TagService::class => ConfigAbstractFactory::class,
|
||||||
|
|
||||||
|
@ -61,6 +65,10 @@ return [
|
||||||
Visit\Geolocation\VisitToLocationHelper::class => ConfigAbstractFactory::class,
|
Visit\Geolocation\VisitToLocationHelper::class => ConfigAbstractFactory::class,
|
||||||
Visit\VisitsStatsHelper::class => ConfigAbstractFactory::class,
|
Visit\VisitsStatsHelper::class => ConfigAbstractFactory::class,
|
||||||
Visit\Transformer\OrphanVisitDataTransformer::class => InvokableFactory::class,
|
Visit\Transformer\OrphanVisitDataTransformer::class => InvokableFactory::class,
|
||||||
|
Visit\Repository\VisitLocationRepository::class => [
|
||||||
|
EntityRepositoryFactory::class,
|
||||||
|
Visit\Entity\Visit::class,
|
||||||
|
],
|
||||||
|
|
||||||
Util\UrlValidator::class => ConfigAbstractFactory::class,
|
Util\UrlValidator::class => ConfigAbstractFactory::class,
|
||||||
Util\DoctrineBatchHelper::class => ConfigAbstractFactory::class,
|
Util\DoctrineBatchHelper::class => ConfigAbstractFactory::class,
|
||||||
|
@ -119,7 +127,7 @@ return [
|
||||||
ShortUrl\Repository\ShortUrlListRepository::class,
|
ShortUrl\Repository\ShortUrlListRepository::class,
|
||||||
Options\UrlShortenerOptions::class,
|
Options\UrlShortenerOptions::class,
|
||||||
],
|
],
|
||||||
Visit\Geolocation\VisitLocator::class => ['em'],
|
Visit\Geolocation\VisitLocator::class => ['em', Visit\Repository\VisitLocationRepository::class],
|
||||||
Visit\Geolocation\VisitToLocationHelper::class => [IpLocationResolverInterface::class],
|
Visit\Geolocation\VisitToLocationHelper::class => [IpLocationResolverInterface::class],
|
||||||
Visit\VisitsStatsHelper::class => ['em'],
|
Visit\VisitsStatsHelper::class => ['em'],
|
||||||
Tag\TagService::class => ['em'],
|
Tag\TagService::class => ['em'],
|
||||||
|
|
|
@ -4,20 +4,16 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace Shlinkio\Shlink\Core\Crawling;
|
namespace Shlinkio\Shlink\Core\Crawling;
|
||||||
|
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Shlinkio\Shlink\Core\ShortUrl\Repository\CrawlableShortCodesQueryInterface;
|
||||||
use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl;
|
|
||||||
use Shlinkio\Shlink\Core\ShortUrl\Repository\ShortUrlRepositoryInterface;
|
|
||||||
|
|
||||||
class CrawlingHelper implements CrawlingHelperInterface
|
class CrawlingHelper implements CrawlingHelperInterface
|
||||||
{
|
{
|
||||||
public function __construct(private EntityManagerInterface $em)
|
public function __construct(private readonly CrawlableShortCodesQueryInterface $query)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public function listCrawlableShortCodes(): iterable
|
public function listCrawlableShortCodes(): iterable
|
||||||
{
|
{
|
||||||
/** @var ShortUrlRepositoryInterface $repo */
|
yield from ($this->query)();
|
||||||
$repo = $this->em->getRepository(ShortUrl::class);
|
|
||||||
yield from $repo->findCrawlableShortCodes();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Shlinkio\Shlink\Core\ShortUrl\Repository;
|
||||||
|
|
||||||
|
use Happyr\DoctrineSpecification\Repository\EntitySpecificationRepository;
|
||||||
|
use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl;
|
||||||
|
|
||||||
|
class CrawlableShortCodesQuery extends EntitySpecificationRepository implements CrawlableShortCodesQueryInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return iterable<string>
|
||||||
|
*/
|
||||||
|
public function __invoke(): iterable
|
||||||
|
{
|
||||||
|
$blockSize = 1000;
|
||||||
|
$qb = $this->getEntityManager()->createQueryBuilder();
|
||||||
|
$qb->select('DISTINCT s.shortCode')
|
||||||
|
->from(ShortUrl::class, 's')
|
||||||
|
->where($qb->expr()->eq('s.crawlable', ':crawlable'))
|
||||||
|
->setParameter('crawlable', true)
|
||||||
|
->setMaxResults($blockSize);
|
||||||
|
|
||||||
|
$page = 0;
|
||||||
|
do {
|
||||||
|
$qbClone = (clone $qb)->setFirstResult($blockSize * $page);
|
||||||
|
$iterator = $qbClone->getQuery()->toIterable();
|
||||||
|
$resultsFound = false;
|
||||||
|
$page++;
|
||||||
|
|
||||||
|
foreach ($iterator as ['shortCode' => $shortCode]) {
|
||||||
|
$resultsFound = true;
|
||||||
|
yield $shortCode;
|
||||||
|
}
|
||||||
|
} while ($resultsFound);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Shlinkio\Shlink\Core\ShortUrl\Repository;
|
||||||
|
|
||||||
|
interface CrawlableShortCodesQueryInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return iterable<string>
|
||||||
|
*/
|
||||||
|
public function __invoke(): iterable;
|
||||||
|
}
|
|
@ -190,28 +190,4 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU
|
||||||
$qb->andWhere($qb->expr()->isNull('s.domain'));
|
$qb->andWhere($qb->expr()->isNull('s.domain'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function findCrawlableShortCodes(): iterable
|
|
||||||
{
|
|
||||||
$blockSize = 1000;
|
|
||||||
$qb = $this->getEntityManager()->createQueryBuilder();
|
|
||||||
$qb->select('DISTINCT s.shortCode')
|
|
||||||
->from(ShortUrl::class, 's')
|
|
||||||
->where($qb->expr()->eq('s.crawlable', ':crawlable'))
|
|
||||||
->setParameter('crawlable', true)
|
|
||||||
->setMaxResults($blockSize);
|
|
||||||
|
|
||||||
$page = 0;
|
|
||||||
do {
|
|
||||||
$qbClone = (clone $qb)->setFirstResult($blockSize * $page);
|
|
||||||
$iterator = $qbClone->getQuery()->toIterable();
|
|
||||||
$resultsFound = false;
|
|
||||||
$page++;
|
|
||||||
|
|
||||||
foreach ($iterator as ['shortCode' => $shortCode]) {
|
|
||||||
$resultsFound = true;
|
|
||||||
yield $shortCode;
|
|
||||||
}
|
|
||||||
} while ($resultsFound);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,4 @@ interface ShortUrlRepositoryInterface extends ObjectRepository, EntitySpecificat
|
||||||
public function findOneMatching(ShortUrlCreation $meta): ?ShortUrl;
|
public function findOneMatching(ShortUrlCreation $meta): ?ShortUrl;
|
||||||
|
|
||||||
public function findOneByImportedUrl(ImportedShlinkUrl $url): ?ShortUrl;
|
public function findOneByImportedUrl(ImportedShlinkUrl $url): ?ShortUrl;
|
||||||
|
|
||||||
public function findCrawlableShortCodes(): iterable;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,18 +8,15 @@ use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Shlinkio\Shlink\Core\Exception\IpCannotBeLocatedException;
|
use Shlinkio\Shlink\Core\Exception\IpCannotBeLocatedException;
|
||||||
use Shlinkio\Shlink\Core\Visit\Entity\Visit;
|
use Shlinkio\Shlink\Core\Visit\Entity\Visit;
|
||||||
use Shlinkio\Shlink\Core\Visit\Entity\VisitLocation;
|
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;
|
use Shlinkio\Shlink\IpGeolocation\Model\Location;
|
||||||
|
|
||||||
class VisitLocator implements VisitLocatorInterface
|
class VisitLocator implements VisitLocatorInterface
|
||||||
{
|
{
|
||||||
private VisitRepositoryInterface $repo;
|
public function __construct(
|
||||||
|
private readonly EntityManagerInterface $em,
|
||||||
public function __construct(private EntityManagerInterface $em)
|
private readonly VisitLocationRepositoryInterface $repo,
|
||||||
{
|
) {
|
||||||
/** @var VisitRepositoryInterface $repo */
|
|
||||||
$repo = $em->getRepository(Visit::class);
|
|
||||||
$this->repo = $repo;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function locateUnlocatedVisits(VisitGeolocationHelperInterface $helper): void
|
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
|
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[]
|
* @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\VisitsCountFiltering;
|
||||||
use Shlinkio\Shlink\Core\Visit\Persistence\VisitsListFiltering;
|
use Shlinkio\Shlink\Core\Visit\Persistence\VisitsListFiltering;
|
||||||
|
|
||||||
// TODO Split into VisitsListsRepository and VisitsLocationRepository
|
|
||||||
interface VisitRepositoryInterface extends ObjectRepository, EntitySpecificationRepositoryInterface
|
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[]
|
* @return Visit[]
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace ShlinkioDbTest\Shlink\Core\ShortUrl\Repository;
|
||||||
|
|
||||||
|
use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl;
|
||||||
|
use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlCreation;
|
||||||
|
use Shlinkio\Shlink\Core\ShortUrl\Repository\CrawlableShortCodesQuery;
|
||||||
|
use Shlinkio\Shlink\TestUtils\DbTest\DatabaseTestCase;
|
||||||
|
|
||||||
|
class CrawlableShortCodesQueryTest extends DatabaseTestCase
|
||||||
|
{
|
||||||
|
private CrawlableShortCodesQuery $query;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
$em = $this->getEntityManager();
|
||||||
|
$this->query = new CrawlableShortCodesQuery($em, $em->getClassMetadata(ShortUrl::class));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function invokingQueryReturnsExpectedResult(): void
|
||||||
|
{
|
||||||
|
$createShortUrl = fn (bool $crawlable) => ShortUrl::create(
|
||||||
|
ShortUrlCreation::fromRawData(['crawlable' => $crawlable, 'longUrl' => 'foo.com']),
|
||||||
|
);
|
||||||
|
|
||||||
|
$shortUrl1 = $createShortUrl(true);
|
||||||
|
$this->getEntityManager()->persist($shortUrl1);
|
||||||
|
$shortUrl2 = $createShortUrl(false);
|
||||||
|
$this->getEntityManager()->persist($shortUrl2);
|
||||||
|
$shortUrl3 = $createShortUrl(true);
|
||||||
|
$this->getEntityManager()->persist($shortUrl3);
|
||||||
|
$shortUrl4 = $createShortUrl(true);
|
||||||
|
$this->getEntityManager()->persist($shortUrl4);
|
||||||
|
$shortUrl5 = $createShortUrl(false);
|
||||||
|
$this->getEntityManager()->persist($shortUrl5);
|
||||||
|
$this->getEntityManager()->flush();
|
||||||
|
|
||||||
|
$results = [...($this->query)()];
|
||||||
|
|
||||||
|
self::assertCount(3, $results);
|
||||||
|
self::assertContains($shortUrl1->getShortCode(), $results);
|
||||||
|
self::assertContains($shortUrl3->getShortCode(), $results);
|
||||||
|
self::assertContains($shortUrl4->getShortCode(), $results);
|
||||||
|
self::assertNotContains($shortUrl2->getShortCode(), $results);
|
||||||
|
self::assertNotContains($shortUrl5->getShortCode(), $results);
|
||||||
|
}
|
||||||
|
}
|
|
@ -391,37 +391,4 @@ class ShortUrlRepositoryTest extends DatabaseTestCase
|
||||||
self::assertNull($this->repo->findOneByImportedUrl($buildImported('my-cool-slug', 'doma.in')));
|
self::assertNull($this->repo->findOneByImportedUrl($buildImported('my-cool-slug', 'doma.in')));
|
||||||
self::assertNull($this->repo->findOneByImportedUrl($buildImported('another-slug')));
|
self::assertNull($this->repo->findOneByImportedUrl($buildImported('another-slug')));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @test */
|
|
||||||
public function findCrawlableShortCodesReturnsExpectedResult(): void
|
|
||||||
{
|
|
||||||
$createShortUrl = fn (bool $crawlable) => ShortUrl::create(
|
|
||||||
ShortUrlCreation::fromRawData(['crawlable' => $crawlable, 'longUrl' => 'foo.com']),
|
|
||||||
);
|
|
||||||
|
|
||||||
$shortUrl1 = $createShortUrl(true);
|
|
||||||
$this->getEntityManager()->persist($shortUrl1);
|
|
||||||
$shortUrl2 = $createShortUrl(false);
|
|
||||||
$this->getEntityManager()->persist($shortUrl2);
|
|
||||||
$shortUrl3 = $createShortUrl(true);
|
|
||||||
$this->getEntityManager()->persist($shortUrl3);
|
|
||||||
$shortUrl4 = $createShortUrl(true);
|
|
||||||
$this->getEntityManager()->persist($shortUrl4);
|
|
||||||
$shortUrl5 = $createShortUrl(false);
|
|
||||||
$this->getEntityManager()->persist($shortUrl5);
|
|
||||||
$this->getEntityManager()->flush();
|
|
||||||
|
|
||||||
$iterable = $this->repo->findCrawlableShortCodes();
|
|
||||||
$results = [];
|
|
||||||
foreach ($iterable as $shortCode) {
|
|
||||||
$results[] = $shortCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
self::assertCount(3, $results);
|
|
||||||
self::assertContains($shortUrl1->getShortCode(), $results);
|
|
||||||
self::assertContains($shortUrl3->getShortCode(), $results);
|
|
||||||
self::assertContains($shortUrl4->getShortCode(), $results);
|
|
||||||
self::assertNotContains($shortUrl2->getShortCode(), $results);
|
|
||||||
self::assertNotContains($shortUrl5->getShortCode(), $results);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace ShlinkioDbTest\Shlink\Core\Visit\Repository;
|
||||||
|
|
||||||
|
use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl;
|
||||||
|
use Shlinkio\Shlink\Core\Visit\Entity\Visit;
|
||||||
|
use Shlinkio\Shlink\Core\Visit\Entity\VisitLocation;
|
||||||
|
use Shlinkio\Shlink\Core\Visit\Model\Visitor;
|
||||||
|
use Shlinkio\Shlink\Core\Visit\Repository\VisitLocationRepository;
|
||||||
|
use Shlinkio\Shlink\IpGeolocation\Model\Location;
|
||||||
|
use Shlinkio\Shlink\TestUtils\DbTest\DatabaseTestCase;
|
||||||
|
|
||||||
|
use function Functional\map;
|
||||||
|
use function range;
|
||||||
|
|
||||||
|
class VisitLocationRepositoryTest extends DatabaseTestCase
|
||||||
|
{
|
||||||
|
private VisitLocationRepository $repo;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
$em = $this->getEntityManager();
|
||||||
|
$this->repo = new VisitLocationRepository($em, $em->getClassMetadata(Visit::class));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
* @dataProvider provideBlockSize
|
||||||
|
*/
|
||||||
|
public function findVisitsReturnsProperVisits(int $blockSize): void
|
||||||
|
{
|
||||||
|
$shortUrl = ShortUrl::createEmpty();
|
||||||
|
$this->getEntityManager()->persist($shortUrl);
|
||||||
|
|
||||||
|
for ($i = 0; $i < 6; $i++) {
|
||||||
|
$visit = Visit::forValidShortUrl($shortUrl, Visitor::emptyInstance());
|
||||||
|
|
||||||
|
if ($i >= 2) {
|
||||||
|
$location = VisitLocation::fromGeolocation(Location::emptyInstance());
|
||||||
|
$this->getEntityManager()->persist($location);
|
||||||
|
$visit->locate($location);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->getEntityManager()->persist($visit);
|
||||||
|
}
|
||||||
|
$this->getEntityManager()->flush();
|
||||||
|
|
||||||
|
$withEmptyLocation = $this->repo->findVisitsWithEmptyLocation($blockSize);
|
||||||
|
$unlocated = $this->repo->findUnlocatedVisits($blockSize);
|
||||||
|
$all = $this->repo->findAllVisits($blockSize);
|
||||||
|
|
||||||
|
self::assertCount(2, [...$unlocated]);
|
||||||
|
self::assertCount(4, [...$withEmptyLocation]);
|
||||||
|
self::assertCount(6, [...$all]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function provideBlockSize(): iterable
|
||||||
|
{
|
||||||
|
return map(range(1, 10), fn (int $value) => [$value]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,20 +14,16 @@ use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlIdentifier;
|
||||||
use Shlinkio\Shlink\Core\ShortUrl\Model\Validation\ShortUrlInputFilter;
|
use Shlinkio\Shlink\Core\ShortUrl\Model\Validation\ShortUrlInputFilter;
|
||||||
use Shlinkio\Shlink\Core\ShortUrl\Resolver\PersistenceShortUrlRelationResolver;
|
use Shlinkio\Shlink\Core\ShortUrl\Resolver\PersistenceShortUrlRelationResolver;
|
||||||
use Shlinkio\Shlink\Core\Visit\Entity\Visit;
|
use Shlinkio\Shlink\Core\Visit\Entity\Visit;
|
||||||
use Shlinkio\Shlink\Core\Visit\Entity\VisitLocation;
|
|
||||||
use Shlinkio\Shlink\Core\Visit\Model\Visitor;
|
use Shlinkio\Shlink\Core\Visit\Model\Visitor;
|
||||||
use Shlinkio\Shlink\Core\Visit\Persistence\VisitsCountFiltering;
|
use Shlinkio\Shlink\Core\Visit\Persistence\VisitsCountFiltering;
|
||||||
use Shlinkio\Shlink\Core\Visit\Persistence\VisitsListFiltering;
|
use Shlinkio\Shlink\Core\Visit\Persistence\VisitsListFiltering;
|
||||||
use Shlinkio\Shlink\Core\Visit\Repository\VisitRepository;
|
use Shlinkio\Shlink\Core\Visit\Repository\VisitRepository;
|
||||||
use Shlinkio\Shlink\IpGeolocation\Model\Location;
|
|
||||||
use Shlinkio\Shlink\Rest\ApiKey\Model\ApiKeyMeta;
|
use Shlinkio\Shlink\Rest\ApiKey\Model\ApiKeyMeta;
|
||||||
use Shlinkio\Shlink\Rest\ApiKey\Model\RoleDefinition;
|
use Shlinkio\Shlink\Rest\ApiKey\Model\RoleDefinition;
|
||||||
use Shlinkio\Shlink\Rest\Entity\ApiKey;
|
use Shlinkio\Shlink\Rest\Entity\ApiKey;
|
||||||
use Shlinkio\Shlink\TestUtils\DbTest\DatabaseTestCase;
|
use Shlinkio\Shlink\TestUtils\DbTest\DatabaseTestCase;
|
||||||
|
|
||||||
use function Functional\map;
|
|
||||||
use function is_string;
|
use function is_string;
|
||||||
use function range;
|
|
||||||
use function sprintf;
|
use function sprintf;
|
||||||
use function str_pad;
|
use function str_pad;
|
||||||
|
|
||||||
|
@ -44,52 +40,6 @@ class VisitRepositoryTest extends DatabaseTestCase
|
||||||
$this->relationResolver = new PersistenceShortUrlRelationResolver($this->getEntityManager());
|
$this->relationResolver = new PersistenceShortUrlRelationResolver($this->getEntityManager());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @test
|
|
||||||
* @dataProvider provideBlockSize
|
|
||||||
*/
|
|
||||||
public function findVisitsReturnsProperVisits(int $blockSize): void
|
|
||||||
{
|
|
||||||
$shortUrl = ShortUrl::createEmpty();
|
|
||||||
$this->getEntityManager()->persist($shortUrl);
|
|
||||||
$countIterable = static function (iterable $results): int {
|
|
||||||
$resultsCount = 0;
|
|
||||||
foreach ($results as $value) {
|
|
||||||
$resultsCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $resultsCount;
|
|
||||||
};
|
|
||||||
|
|
||||||
for ($i = 0; $i < 6; $i++) {
|
|
||||||
$visit = Visit::forValidShortUrl($shortUrl, Visitor::emptyInstance());
|
|
||||||
|
|
||||||
if ($i >= 2) {
|
|
||||||
$location = VisitLocation::fromGeolocation(Location::emptyInstance());
|
|
||||||
$this->getEntityManager()->persist($location);
|
|
||||||
$visit->locate($location);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->getEntityManager()->persist($visit);
|
|
||||||
}
|
|
||||||
$this->getEntityManager()->flush();
|
|
||||||
|
|
||||||
$withEmptyLocation = $this->repo->findVisitsWithEmptyLocation($blockSize);
|
|
||||||
$unlocated = $this->repo->findUnlocatedVisits($blockSize);
|
|
||||||
$all = $this->repo->findAllVisits($blockSize);
|
|
||||||
|
|
||||||
// Important! assertCount will not work here, as this iterable object loads data dynamically and the count
|
|
||||||
// is 0 if not iterated
|
|
||||||
self::assertEquals(2, $countIterable($unlocated));
|
|
||||||
self::assertEquals(4, $countIterable($withEmptyLocation));
|
|
||||||
self::assertEquals(6, $countIterable($all));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function provideBlockSize(): iterable
|
|
||||||
{
|
|
||||||
return map(range(1, 10), fn (int $value) => [$value]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @test */
|
/** @test */
|
||||||
public function findVisitsByShortCodeReturnsProperData(): void
|
public function findVisitsByShortCodeReturnsProperData(): void
|
||||||
{
|
{
|
||||||
|
|
|
@ -4,34 +4,26 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace ShlinkioTest\Shlink\Core\Crawling;
|
namespace ShlinkioTest\Shlink\Core\Crawling;
|
||||||
|
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
|
||||||
use PHPUnit\Framework\MockObject\MockObject;
|
use PHPUnit\Framework\MockObject\MockObject;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Shlinkio\Shlink\Core\Crawling\CrawlingHelper;
|
use Shlinkio\Shlink\Core\Crawling\CrawlingHelper;
|
||||||
use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl;
|
use Shlinkio\Shlink\Core\ShortUrl\Repository\CrawlableShortCodesQueryInterface;
|
||||||
use Shlinkio\Shlink\Core\ShortUrl\Repository\ShortUrlRepositoryInterface;
|
|
||||||
|
|
||||||
class CrawlingHelperTest extends TestCase
|
class CrawlingHelperTest extends TestCase
|
||||||
{
|
{
|
||||||
private CrawlingHelper $helper;
|
private CrawlingHelper $helper;
|
||||||
private MockObject & EntityManagerInterface $em;
|
private MockObject & CrawlableShortCodesQueryInterface $query;
|
||||||
|
|
||||||
protected function setUp(): void
|
protected function setUp(): void
|
||||||
{
|
{
|
||||||
$this->em = $this->createMock(EntityManagerInterface::class);
|
$this->query = $this->createMock(CrawlableShortCodesQueryInterface::class);
|
||||||
$this->helper = new CrawlingHelper($this->em);
|
$this->helper = new CrawlingHelper($this->query);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @test */
|
/** @test */
|
||||||
public function listCrawlableShortCodesDelegatesIntoRepository(): void
|
public function listCrawlableShortCodesDelegatesIntoRepository(): void
|
||||||
{
|
{
|
||||||
$repo = $this->createMock(ShortUrlRepositoryInterface::class);
|
$this->query->expects($this->once())->method('__invoke')->willReturn([]);
|
||||||
$repo->expects($this->once())->method('findCrawlableShortCodes')->willReturn([]);
|
[...$this->helper->listCrawlableShortCodes()];
|
||||||
$this->em->expects($this->once())->method('getRepository')->with(ShortUrl::class)->willReturn($repo);
|
|
||||||
|
|
||||||
$result = $this->helper->listCrawlableShortCodes();
|
|
||||||
foreach ($result as $shortCode) {
|
|
||||||
// $result is a generator and therefore, it needs to be iterated
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ use Shlinkio\Shlink\Core\Visit\Entity\VisitLocation;
|
||||||
use Shlinkio\Shlink\Core\Visit\Geolocation\VisitGeolocationHelperInterface;
|
use Shlinkio\Shlink\Core\Visit\Geolocation\VisitGeolocationHelperInterface;
|
||||||
use Shlinkio\Shlink\Core\Visit\Geolocation\VisitLocator;
|
use Shlinkio\Shlink\Core\Visit\Geolocation\VisitLocator;
|
||||||
use Shlinkio\Shlink\Core\Visit\Model\Visitor;
|
use Shlinkio\Shlink\Core\Visit\Model\Visitor;
|
||||||
use Shlinkio\Shlink\Core\Visit\Repository\VisitRepositoryInterface;
|
use Shlinkio\Shlink\Core\Visit\Repository\VisitLocationRepositoryInterface;
|
||||||
use Shlinkio\Shlink\IpGeolocation\Model\Location;
|
use Shlinkio\Shlink\IpGeolocation\Model\Location;
|
||||||
|
|
||||||
use function count;
|
use function count;
|
||||||
|
@ -28,15 +28,14 @@ class VisitLocatorTest extends TestCase
|
||||||
{
|
{
|
||||||
private VisitLocator $visitService;
|
private VisitLocator $visitService;
|
||||||
private MockObject & EntityManager $em;
|
private MockObject & EntityManager $em;
|
||||||
private MockObject & VisitRepositoryInterface $repo;
|
private MockObject & VisitLocationRepositoryInterface $repo;
|
||||||
|
|
||||||
protected function setUp(): void
|
protected function setUp(): void
|
||||||
{
|
{
|
||||||
$this->em = $this->createMock(EntityManager::class);
|
$this->em = $this->createMock(EntityManager::class);
|
||||||
$this->repo = $this->createMock(VisitRepositoryInterface::class);
|
$this->repo = $this->createMock(VisitLocationRepositoryInterface::class);
|
||||||
$this->em->method('getRepository')->with(Visit::class)->willReturn($this->repo);
|
|
||||||
|
|
||||||
$this->visitService = new VisitLocator($this->em);
|
$this->visitService = new VisitLocator($this->em, $this->repo);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -103,7 +102,7 @@ class VisitLocatorTest extends TestCase
|
||||||
|
|
||||||
$this->visitService->{$serviceMethodName}(
|
$this->visitService->{$serviceMethodName}(
|
||||||
new class ($isNonLocatableAddress) implements VisitGeolocationHelperInterface {
|
new class ($isNonLocatableAddress) implements VisitGeolocationHelperInterface {
|
||||||
public function __construct(private bool $isNonLocatableAddress)
|
public function __construct(private readonly bool $isNonLocatableAddress)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue