Update to doctrine ORM 3.0

This commit is contained in:
Alejandro Celaya 2024-02-17 10:21:36 +01:00
parent e919901487
commit e073b4331a
18 changed files with 56 additions and 55 deletions

View file

@ -16,6 +16,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this
* [#1988](https://github.com/shlinkio/shlink/issues/1988) Remove dependency on `league\uri` package. * [#1988](https://github.com/shlinkio/shlink/issues/1988) Remove dependency on `league\uri` package.
* [#1909](https://github.com/shlinkio/shlink/issues/1909) Update docker image to PHP 8.3. * [#1909](https://github.com/shlinkio/shlink/issues/1909) Update docker image to PHP 8.3.
* [#1786](https://github.com/shlinkio/shlink/issues/1786) Run API tests with RoadRunner by default. * [#1786](https://github.com/shlinkio/shlink/issues/1786) Run API tests with RoadRunner by default.
* [#2008](https://github.com/shlinkio/shlink/issues/2008) Update to Doctrine ORM 3.0.
### Deprecated ### Deprecated
* *Nothing* * *Nothing*

View file

@ -20,12 +20,11 @@
"akrabat/ip-address-middleware": "^2.1", "akrabat/ip-address-middleware": "^2.1",
"cakephp/chronos": "^3.0.2", "cakephp/chronos": "^3.0.2",
"doctrine/migrations": "^3.6", "doctrine/migrations": "^3.6",
"doctrine/orm": "^2.16", "doctrine/orm": "^3.0",
"endroid/qr-code": "^4.8", "endroid/qr-code": "^4.8",
"friendsofphp/proxy-manager-lts": "^1.0", "friendsofphp/proxy-manager-lts": "^1.0",
"geoip2/geoip2": "^3.0", "geoip2/geoip2": "^3.0",
"guzzlehttp/guzzle": "^7.5", "guzzlehttp/guzzle": "^7.5",
"happyr/doctrine-specification": "^2.0",
"jaybizzle/crawler-detect": "^1.2.116", "jaybizzle/crawler-detect": "^1.2.116",
"laminas/laminas-config": "^3.8", "laminas/laminas-config": "^3.8",
"laminas/laminas-config-aggregator": "^1.13", "laminas/laminas-config-aggregator": "^1.13",
@ -42,7 +41,8 @@
"pagerfanta/core": "^3.8", "pagerfanta/core": "^3.8",
"pugx/shortid-php": "^1.1", "pugx/shortid-php": "^1.1",
"ramsey/uuid": "^4.7", "ramsey/uuid": "^4.7",
"shlinkio/shlink-common": "dev-main#2323ff3 as 6.0", "shlinkio/doctrine-specification": "^2.1.1",
"shlinkio/shlink-common": "dev-main#178b332 as 6.0",
"shlinkio/shlink-config": "dev-main#6b287b3 as 2.6", "shlinkio/shlink-config": "dev-main#6b287b3 as 2.6",
"shlinkio/shlink-event-dispatcher": "dev-main#46f5e21 as 4.0", "shlinkio/shlink-event-dispatcher": "dev-main#46f5e21 as 4.0",
"shlinkio/shlink-importer": "^5.2.1", "shlinkio/shlink-importer": "^5.2.1",
@ -71,7 +71,7 @@
"phpunit/phpunit": "^10.4", "phpunit/phpunit": "^10.4",
"roave/security-advisories": "dev-master", "roave/security-advisories": "dev-master",
"shlinkio/php-coding-standard": "~2.3.0", "shlinkio/php-coding-standard": "~2.3.0",
"shlinkio/shlink-test-utils": "^3.10", "shlinkio/shlink-test-utils": "^3.11",
"symfony/var-dumper": "^6.4", "symfony/var-dumper": "^6.4",
"veewee/composer-run-parallel": "^1.3" "veewee/composer-run-parallel": "^1.3"
}, },

View file

@ -10,7 +10,7 @@ use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Schema\AbstractSchemaManager; use Doctrine\DBAL\Schema\AbstractSchemaManager;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\Persistence\Mapping\ClassMetadataFactory; use Doctrine\ORM\Mapping\ClassMetadataFactory;
use Exception; use Exception;
use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\Attributes\Test;

View file

@ -6,7 +6,7 @@ namespace ShlinkMigrations;
use Doctrine\DBAL\Exception; use Doctrine\DBAL\Exception;
use Doctrine\DBAL\Platforms\MySQLPlatform; use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Platforms\SqlitePlatform; use Doctrine\DBAL\Platforms\SQLitePlatform;
use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Schema\SchemaException; use Doctrine\DBAL\Schema\SchemaException;
use Doctrine\Migrations\AbstractMigration; use Doctrine\Migrations\AbstractMigration;
@ -32,7 +32,7 @@ class Version20160819142757 extends AbstractMigration
is_subclass_of($platformClass, MySQLPlatform::class) => $column is_subclass_of($platformClass, MySQLPlatform::class) => $column
->setPlatformOption('charset', 'utf8mb4') ->setPlatformOption('charset', 'utf8mb4')
->setPlatformOption('collation', 'utf8mb4_bin'), ->setPlatformOption('collation', 'utf8mb4_bin'),
is_subclass_of($platformClass, SqlitePlatform::class) => $column->setPlatformOption('collate', 'BINARY'), is_subclass_of($platformClass, SQLitePlatform::class) => $column->setPlatformOption('collate', 'BINARY'),
default => null, default => null,
}; };
} }

View file

@ -8,7 +8,6 @@ use Doctrine\DBAL\Exception;
use Doctrine\DBAL\Platforms\MySQLPlatform; use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration; use Doctrine\Migrations\AbstractMigration;
use PDO;
use Shlinkio\Shlink\Common\Exception\InvalidArgumentException; use Shlinkio\Shlink\Common\Exception\InvalidArgumentException;
use Shlinkio\Shlink\Common\Util\IpAddress; use Shlinkio\Shlink\Common\Util\IpAddress;
@ -33,11 +32,11 @@ final class Version20180913205455 extends AbstractMigration
$st = $this->connection->executeQuery($qb->getSQL()); $st = $this->connection->executeQuery($qb->getSQL());
$qb = $this->connection->createQueryBuilder(); $qb = $this->connection->createQueryBuilder();
$qb->update('visits', 'v') $qb->update('visits')
->set('v.remote_addr', ':obfuscatedAddr') ->set('remote_addr', ':obfuscatedAddr')
->where('v.id=:id'); ->where('id=:id');
while ($row = $st->fetch(PDO::FETCH_ASSOC)) { while ($row = $st->fetchAssociative()) {
$addr = $row['remote_addr'] ?? null; $addr = $row['remote_addr'] ?? null;
if ($addr === null) { if ($addr === null) {
continue; continue;
@ -46,7 +45,7 @@ final class Version20180913205455 extends AbstractMigration
$qb->setParameters([ $qb->setParameters([
'id' => $row['id'], 'id' => $row['id'],
'obfuscatedAddr' => $this->determineAddress((string) $addr), 'obfuscatedAddr' => $this->determineAddress((string) $addr),
])->execute(); ])->executeQuery();
} }
} }

View file

@ -32,7 +32,7 @@ final class Version20200105165647 extends AbstractMigration
$qb = $this->connection->createQueryBuilder(); $qb = $this->connection->createQueryBuilder();
$qb->update('visit_locations') $qb->update('visit_locations')
->set($columnName, ':zeroValue') ->set($columnName, ':zeroValue')
->where($qb->expr()->orX( ->where($qb->expr()->or(
$qb->expr()->eq($columnName, ':emptyString'), $qb->expr()->eq($columnName, ':emptyString'),
$qb->expr()->isNull($columnName), $qb->expr()->isNull($columnName),
)) ))

View file

@ -29,10 +29,11 @@ final class Version20200323190014 extends AbstractMigration
->andWhere($qb->expr()->eq('region_name', ':emptyString')) ->andWhere($qb->expr()->eq('region_name', ':emptyString'))
->andWhere($qb->expr()->eq('city_name', ':emptyString')) ->andWhere($qb->expr()->eq('city_name', ':emptyString'))
->andWhere($qb->expr()->eq('timezone', ':emptyString')) ->andWhere($qb->expr()->eq('timezone', ':emptyString'))
->andWhere($qb->expr()->eq('lat', 0)) ->andWhere($qb->expr()->eq('lat', ':latLong'))
->andWhere($qb->expr()->eq('lon', 0)) ->andWhere($qb->expr()->eq('lon', ':latLong'))
->setParameter('isEmpty', true) ->setParameter('isEmpty', true)
->setParameter('emptyString', '') ->setParameter('emptyString', '')
->setParameter('latLong', 0)
->executeStatement(); ->executeStatement();
} }

View file

@ -5,8 +5,8 @@ declare(strict_types=1);
namespace ShlinkMigrations; namespace ShlinkMigrations;
use Cake\Chronos\Chronos; use Cake\Chronos\Chronos;
use Doctrine\DBAL\Driver\Result;
use Doctrine\DBAL\Platforms\MySQLPlatform; use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Result;
use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Types\Types; use Doctrine\DBAL\Types\Types;
use Doctrine\Migrations\AbstractMigration; use Doctrine\Migrations\AbstractMigration;
@ -33,7 +33,7 @@ final class Version20201102113208 extends AbstractMigration
public function postUp(Schema $schema): void public function postUp(Schema $schema): void
{ {
// If there's only one API key and it's active, link all existing URLs with it // If there's only one API key, and it's active, link all existing URLs with it
$qb = $this->connection->createQueryBuilder(); $qb = $this->connection->createQueryBuilder();
$qb->select('id') $qb->select('id')
->from('api_keys') ->from('api_keys')
@ -47,8 +47,7 @@ final class Version20201102113208 extends AbstractMigration
'expiration' => Chronos::now()->toDateTimeString(), 'expiration' => Chronos::now()->toDateTimeString(),
]); ]);
/** @var Result $result */ $result = $qb->executeQuery();
$result = $qb->execute();
$id = $this->resolveOneApiKeyId($result); $id = $this->resolveOneApiKeyId($result);
if ($id === null) { if ($id === null) {
return; return;
@ -58,7 +57,7 @@ final class Version20201102113208 extends AbstractMigration
$qb->update('short_urls') $qb->update('short_urls')
->set(self::API_KEY_COLUMN, ':apiKeyId') ->set(self::API_KEY_COLUMN, ':apiKeyId')
->setParameter('apiKeyId', $id) ->setParameter('apiKeyId', $id)
->execute(); ->executeQuery();
} }
private function resolveOneApiKeyId(Result $result): string|int|null private function resolveOneApiKeyId(Result $result): string|int|null

View file

@ -55,15 +55,15 @@ class ShortUrlListRepository extends EntitySpecificationRepository implements Sh
if (OrderableField::isBasicField($fieldName)) { if (OrderableField::isBasicField($fieldName)) {
$qb->orderBy('s.' . $fieldName, $order); $qb->orderBy('s.' . $fieldName, $order);
} elseif (OrderableField::isVisitsField($fieldName)) { } elseif (OrderableField::isVisitsField($fieldName)) {
$leftJoinConditions = [$qb->expr()->eq('v.shortUrl', 's')];
if ($fieldName === OrderableField::NON_BOT_VISITS->value) {
$leftJoinConditions[] = $qb->expr()->eq('v.potentialBot', 'false');
}
// FIXME This query is inefficient. // FIXME This query is inefficient.
// Diagnostic: It might need to use a sub-query, as done with the tags list query. // Diagnostic: It might need to use a sub-query, as done with the tags list query.
$qb->addSelect('COUNT(DISTINCT v)') $qb->addSelect('COUNT(DISTINCT v)')
->leftJoin('s.visits', 'v', Join::WITH, $qb->expr()->andX( ->leftJoin('s.visits', 'v', Join::WITH, $qb->expr()->andX(...$leftJoinConditions))
$qb->expr()->eq('v.shortUrl', 's'),
$fieldName === OrderableField::NON_BOT_VISITS->value
? $qb->expr()->eq('v.potentialBot', 'false')
: null,
))
->groupBy('s') ->groupBy('s')
->orderBy('COUNT(DISTINCT v)', $order); ->orderBy('COUNT(DISTINCT v)', $order);
} }

View file

@ -72,7 +72,7 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU
/** /**
* @param LockMode::PESSIMISTIC_WRITE|null $lockMode * @param LockMode::PESSIMISTIC_WRITE|null $lockMode
*/ */
private function doShortCodeIsInUse(ShortUrlIdentifier $identifier, ?Specification $spec, ?int $lockMode): bool private function doShortCodeIsInUse(ShortUrlIdentifier $identifier, ?Specification $spec, ?LockMode $lockMode): bool
{ {
$qb = $this->createFindOneQueryBuilder($identifier, $spec)->select('s.id'); $qb = $this->createFindOneQueryBuilder($identifier, $spec)->select('s.id');
$query = $qb->getQuery(); $query = $qb->getQuery();

View file

@ -79,6 +79,7 @@ class PersistenceShortUrlRelationResolver implements ShortUrlRelationResolverInt
return new Collections\ArrayCollection(array_map(function (string $tagName) use ($repo): Tag { return new Collections\ArrayCollection(array_map(function (string $tagName) use ($repo): Tag {
$this->lock($this->tagLocks, 'tag_' . $tagName); $this->lock($this->tagLocks, 'tag_' . $tagName);
/** @var Tag|null $existingTag */
$existingTag = $repo->findOneBy(['name' => $tagName]); $existingTag = $repo->findOneBy(['name' => $tagName]);
if ($existingTag) { if ($existingTag) {
$this->releaseLock($this->tagLocks, 'tag_' . $tagName); $this->releaseLock($this->tagLocks, 'tag_' . $tagName);

View file

@ -82,12 +82,12 @@ class TagRepository extends EntitySpecificationRepository implements TagReposito
: $visitsSubQb->expr()->and( : $visitsSubQb->expr()->and(
$commonJoinCondition, $commonJoinCondition,
$visitsSubQb->expr()->eq('v.potential_bot', $conn->quote('0')), $visitsSubQb->expr()->eq('v.potential_bot', $conn->quote('0')),
); )->__toString();
return $visitsSubQb return $visitsSubQb
->select('st.tag_id AS tag_id', 'COUNT(DISTINCT v.id) AS ' . $aggregateAlias) ->select('st.tag_id AS tag_id', 'COUNT(DISTINCT v.id) AS ' . $aggregateAlias)
->from('visits', 'v') ->from('visits', 'v')
->join('v', 'short_urls', 's', $visitsJoin) // @phpstan-ignore-line ->join('v', 'short_urls', 's', $visitsJoin)
->join('s', 'short_urls_in_tags', 'st', $visitsSubQb->expr()->eq('st.short_url_id', 's.id')) ->join('s', 'short_urls_in_tags', 'st', $visitsSubQb->expr()->eq('st.short_url_id', 's.id'))
->groupBy('st.tag_id'); ->groupBy('st.tag_id');
}; };

View file

@ -14,7 +14,7 @@ use Shlinkio\Shlink\Core\Config\NotFoundRedirects;
use Shlinkio\Shlink\Core\Domain\DomainService; use Shlinkio\Shlink\Core\Domain\DomainService;
use Shlinkio\Shlink\Core\Domain\Entity\Domain; use Shlinkio\Shlink\Core\Domain\Entity\Domain;
use Shlinkio\Shlink\Core\Domain\Model\DomainItem; use Shlinkio\Shlink\Core\Domain\Model\DomainItem;
use Shlinkio\Shlink\Core\Domain\Repository\DomainRepositoryInterface; use Shlinkio\Shlink\Core\Domain\Repository\DomainRepository;
use Shlinkio\Shlink\Core\Exception\DomainNotFoundException; use Shlinkio\Shlink\Core\Exception\DomainNotFoundException;
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;
@ -34,7 +34,7 @@ class DomainServiceTest extends TestCase
#[Test, DataProvider('provideExcludedDomains')] #[Test, DataProvider('provideExcludedDomains')]
public function listDomainsDelegatesIntoRepository(array $domains, array $expectedResult, ?ApiKey $apiKey): void public function listDomainsDelegatesIntoRepository(array $domains, array $expectedResult, ?ApiKey $apiKey): void
{ {
$repo = $this->createMock(DomainRepositoryInterface::class); $repo = $this->createMock(DomainRepository::class);
$repo->expects($this->once())->method('findDomains')->with($apiKey)->willReturn($domains); $repo->expects($this->once())->method('findDomains')->with($apiKey)->willReturn($domains);
$this->em->expects($this->once())->method('getRepository')->with(Domain::class)->willReturn($repo); $this->em->expects($this->once())->method('getRepository')->with(Domain::class)->willReturn($repo);
@ -126,7 +126,7 @@ class DomainServiceTest extends TestCase
public function getOrCreateAlwaysPersistsDomain(?Domain $foundDomain, ?ApiKey $apiKey): void public function getOrCreateAlwaysPersistsDomain(?Domain $foundDomain, ?ApiKey $apiKey): void
{ {
$authority = 'example.com'; $authority = 'example.com';
$repo = $this->createMock(DomainRepositoryInterface::class); $repo = $this->createMock(DomainRepository::class);
$repo->method('findOneByAuthority')->with($authority, $apiKey)->willReturn( $repo->method('findOneByAuthority')->with($authority, $apiKey)->willReturn(
$foundDomain, $foundDomain,
); );
@ -148,7 +148,7 @@ class DomainServiceTest extends TestCase
$domain = Domain::withAuthority($authority); $domain = Domain::withAuthority($authority);
$domain->setId('1'); $domain->setId('1');
$apiKey = ApiKey::fromMeta(ApiKeyMeta::withRoles(RoleDefinition::forDomain($domain))); $apiKey = ApiKey::fromMeta(ApiKeyMeta::withRoles(RoleDefinition::forDomain($domain)));
$repo = $this->createMock(DomainRepositoryInterface::class); $repo = $this->createMock(DomainRepository::class);
$repo->method('findOneByAuthority')->with($authority, $apiKey)->willReturn(null); $repo->method('findOneByAuthority')->with($authority, $apiKey)->willReturn(null);
$this->em->expects($this->once())->method('getRepository')->with(Domain::class)->willReturn($repo); $this->em->expects($this->once())->method('getRepository')->with(Domain::class)->willReturn($repo);
$this->em->expects($this->never())->method('persist'); $this->em->expects($this->never())->method('persist');
@ -163,7 +163,7 @@ class DomainServiceTest extends TestCase
public function configureNotFoundRedirectsConfiguresFetchedDomain(?Domain $foundDomain, ?ApiKey $apiKey): void public function configureNotFoundRedirectsConfiguresFetchedDomain(?Domain $foundDomain, ?ApiKey $apiKey): void
{ {
$authority = 'example.com'; $authority = 'example.com';
$repo = $this->createMock(DomainRepositoryInterface::class); $repo = $this->createMock(DomainRepository::class);
$repo->method('findOneByAuthority')->with($authority, $apiKey)->willReturn($foundDomain); $repo->method('findOneByAuthority')->with($authority, $apiKey)->willReturn($foundDomain);
$this->em->expects($this->once())->method('getRepository')->with(Domain::class)->willReturn($repo); $this->em->expects($this->once())->method('getRepository')->with(Domain::class)->willReturn($repo);
$this->em->expects($this->once())->method('persist')->with($foundDomain ?? $this->isInstanceOf(Domain::class)); $this->em->expects($this->once())->method('persist')->with($foundDomain ?? $this->isInstanceOf(Domain::class));

View file

@ -16,12 +16,12 @@ use RuntimeException;
use Shlinkio\Shlink\Core\Importer\ImportedLinksProcessor; use Shlinkio\Shlink\Core\Importer\ImportedLinksProcessor;
use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl; use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl;
use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortCodeUniquenessHelperInterface; use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortCodeUniquenessHelperInterface;
use Shlinkio\Shlink\Core\ShortUrl\Repository\ShortUrlRepositoryInterface; use Shlinkio\Shlink\Core\ShortUrl\Repository\ShortUrlRepository;
use Shlinkio\Shlink\Core\ShortUrl\Resolver\SimpleShortUrlRelationResolver; use Shlinkio\Shlink\Core\ShortUrl\Resolver\SimpleShortUrlRelationResolver;
use Shlinkio\Shlink\Core\Util\DoctrineBatchHelperInterface; use Shlinkio\Shlink\Core\Util\DoctrineBatchHelperInterface;
use Shlinkio\Shlink\Core\Visit\Entity\Visit; use Shlinkio\Shlink\Core\Visit\Entity\Visit;
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\VisitRepository;
use Shlinkio\Shlink\Importer\Model\ImportedShlinkOrphanVisit; use Shlinkio\Shlink\Importer\Model\ImportedShlinkOrphanVisit;
use Shlinkio\Shlink\Importer\Model\ImportedShlinkUrl; use Shlinkio\Shlink\Importer\Model\ImportedShlinkUrl;
use Shlinkio\Shlink\Importer\Model\ImportedShlinkVisit; use Shlinkio\Shlink\Importer\Model\ImportedShlinkVisit;
@ -42,13 +42,13 @@ class ImportedLinksProcessorTest extends TestCase
private ImportedLinksProcessor $processor; private ImportedLinksProcessor $processor;
private MockObject & EntityManagerInterface $em; private MockObject & EntityManagerInterface $em;
private MockObject & ShortCodeUniquenessHelperInterface $shortCodeHelper; private MockObject & ShortCodeUniquenessHelperInterface $shortCodeHelper;
private MockObject & ShortUrlRepositoryInterface $repo; private MockObject & ShortUrlRepository $repo;
private MockObject & StyleInterface $io; private MockObject & StyleInterface $io;
protected function setUp(): void protected function setUp(): void
{ {
$this->em = $this->createMock(EntityManagerInterface::class); $this->em = $this->createMock(EntityManagerInterface::class);
$this->repo = $this->createMock(ShortUrlRepositoryInterface::class); $this->repo = $this->createMock(ShortUrlRepository::class);
$this->shortCodeHelper = $this->createMock(ShortCodeUniquenessHelperInterface::class); $this->shortCodeHelper = $this->createMock(ShortCodeUniquenessHelperInterface::class);
$batchHelper = $this->createMock(DoctrineBatchHelperInterface::class); $batchHelper = $this->createMock(DoctrineBatchHelperInterface::class);
@ -281,7 +281,7 @@ class ImportedLinksProcessorTest extends TestCase
sprintf('<info>Imported %s</info> orphan visits.', $expectedImportedVisits), sprintf('<info>Imported %s</info> orphan visits.', $expectedImportedVisits),
); );
$visitRepo = $this->createMock(VisitRepositoryInterface::class); $visitRepo = $this->createMock(VisitRepository::class);
$visitRepo->expects($importOrphanVisits ? $this->once() : $this->never())->method( $visitRepo->expects($importOrphanVisits ? $this->once() : $this->never())->method(
'findMostRecentOrphanVisit', 'findMostRecentOrphanVisit',
)->willReturn($lastOrphanVisit); )->willReturn($lastOrphanVisit);

View file

@ -11,11 +11,11 @@ use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Shlinkio\Shlink\Core\Domain\Entity\Domain; use Shlinkio\Shlink\Core\Domain\Entity\Domain;
use Shlinkio\Shlink\Core\Domain\Repository\DomainRepositoryInterface; use Shlinkio\Shlink\Core\Domain\Repository\DomainRepository;
use Shlinkio\Shlink\Core\Options\UrlShortenerOptions; use Shlinkio\Shlink\Core\Options\UrlShortenerOptions;
use Shlinkio\Shlink\Core\ShortUrl\Resolver\PersistenceShortUrlRelationResolver; use Shlinkio\Shlink\Core\ShortUrl\Resolver\PersistenceShortUrlRelationResolver;
use Shlinkio\Shlink\Core\Tag\Entity\Tag; use Shlinkio\Shlink\Core\Tag\Entity\Tag;
use Shlinkio\Shlink\Core\Tag\Repository\TagRepositoryInterface; use Shlinkio\Shlink\Core\Tag\Repository\TagRepository;
use function count; use function count;
@ -50,7 +50,7 @@ class PersistenceShortUrlRelationResolverTest extends TestCase
#[Test, DataProvider('provideFoundDomains')] #[Test, DataProvider('provideFoundDomains')]
public function findsOrCreatesDomainWhenValueIsProvided(?Domain $foundDomain, string $authority): void public function findsOrCreatesDomainWhenValueIsProvided(?Domain $foundDomain, string $authority): void
{ {
$repo = $this->createMock(DomainRepositoryInterface::class); $repo = $this->createMock(DomainRepository::class);
$repo->expects($this->once())->method('findOneBy')->with(['authority' => $authority])->willReturn($foundDomain); $repo->expects($this->once())->method('findOneBy')->with(['authority' => $authority])->willReturn($foundDomain);
$this->em->expects($this->once())->method('getRepository')->with(Domain::class)->willReturn($repo); $this->em->expects($this->once())->method('getRepository')->with(Domain::class)->willReturn($repo);
@ -78,7 +78,7 @@ class PersistenceShortUrlRelationResolverTest extends TestCase
// One of the tags will already exist. The rest will be new // One of the tags will already exist. The rest will be new
$expectedPersistedTags = $expectedLookedOutTags - 1; $expectedPersistedTags = $expectedLookedOutTags - 1;
$tagRepo = $this->createMock(TagRepositoryInterface::class); $tagRepo = $this->createMock(TagRepository::class);
$tagRepo->expects($this->exactly($expectedLookedOutTags))->method('findOneBy')->with( $tagRepo->expects($this->exactly($expectedLookedOutTags))->method('findOneBy')->with(
$this->isType('array'), $this->isType('array'),
)->willReturnCallback(function (array $criteria): ?Tag { )->willReturnCallback(function (array $criteria): ?Tag {
@ -116,7 +116,7 @@ class PersistenceShortUrlRelationResolverTest extends TestCase
#[Test] #[Test]
public function newDomainsAreMemoizedUntilStateIsCleared(): void public function newDomainsAreMemoizedUntilStateIsCleared(): void
{ {
$repo = $this->createMock(DomainRepositoryInterface::class); $repo = $this->createMock(DomainRepository::class);
$repo->expects($this->exactly(3))->method('findOneBy')->with($this->isType('array'))->willReturn(null); $repo->expects($this->exactly(3))->method('findOneBy')->with($this->isType('array'))->willReturn(null);
$this->em->method('getRepository')->with(Domain::class)->willReturn($repo); $this->em->method('getRepository')->with(Domain::class)->willReturn($repo);
@ -135,7 +135,7 @@ class PersistenceShortUrlRelationResolverTest extends TestCase
#[Test] #[Test]
public function newTagsAreMemoizedUntilStateIsCleared(): void public function newTagsAreMemoizedUntilStateIsCleared(): void
{ {
$tagRepo = $this->createMock(TagRepositoryInterface::class); $tagRepo = $this->createMock(TagRepository::class);
$tagRepo->expects($this->exactly(6))->method('findOneBy')->with($this->isType('array'))->willReturn(null); $tagRepo->expects($this->exactly(6))->method('findOneBy')->with($this->isType('array'))->willReturn(null);
$this->em->method('getRepository')->with(Tag::class)->willReturn($tagRepo); $this->em->method('getRepository')->with(Tag::class)->willReturn($tagRepo);

View file

@ -18,7 +18,7 @@ use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl;
use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlCreation; use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlCreation;
use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlIdentifier; use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlIdentifier;
use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlMode; use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlMode;
use Shlinkio\Shlink\Core\ShortUrl\Repository\ShortUrlRepositoryInterface; use Shlinkio\Shlink\Core\ShortUrl\Repository\ShortUrlRepository;
use Shlinkio\Shlink\Core\ShortUrl\ShortUrlResolver; use Shlinkio\Shlink\Core\ShortUrl\ShortUrlResolver;
use Shlinkio\Shlink\Core\Visit\Entity\Visit; use Shlinkio\Shlink\Core\Visit\Entity\Visit;
use Shlinkio\Shlink\Core\Visit\Model\Visitor; use Shlinkio\Shlink\Core\Visit\Model\Visitor;
@ -32,12 +32,12 @@ class ShortUrlResolverTest extends TestCase
{ {
private ShortUrlResolver $urlResolver; private ShortUrlResolver $urlResolver;
private MockObject & EntityManagerInterface $em; private MockObject & EntityManagerInterface $em;
private MockObject & ShortUrlRepositoryInterface $repo; private MockObject & ShortUrlRepository $repo;
protected function setUp(): void protected function setUp(): void
{ {
$this->em = $this->createMock(EntityManagerInterface::class); $this->em = $this->createMock(EntityManagerInterface::class);
$this->repo = $this->createMock(ShortUrlRepositoryInterface::class); $this->repo = $this->createMock(ShortUrlRepository::class);
$this->urlResolver = new ShortUrlResolver($this->em, new UrlShortenerOptions()); $this->urlResolver = new ShortUrlResolver($this->em, new UrlShortenerOptions());
} }

View file

@ -19,7 +19,7 @@ use Shlinkio\Shlink\Core\Exception\ShortUrlNotFoundException;
use Shlinkio\Shlink\Core\Exception\TagNotFoundException; use Shlinkio\Shlink\Core\Exception\TagNotFoundException;
use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl; use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl;
use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlIdentifier; use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlIdentifier;
use Shlinkio\Shlink\Core\ShortUrl\Repository\ShortUrlRepositoryInterface; use Shlinkio\Shlink\Core\ShortUrl\Repository\ShortUrlRepository;
use Shlinkio\Shlink\Core\Tag\Entity\Tag; use Shlinkio\Shlink\Core\Tag\Entity\Tag;
use Shlinkio\Shlink\Core\Tag\Repository\TagRepository; use Shlinkio\Shlink\Core\Tag\Repository\TagRepository;
use Shlinkio\Shlink\Core\Visit\Entity\Visit; use Shlinkio\Shlink\Core\Visit\Entity\Visit;
@ -90,7 +90,7 @@ class VisitsStatsHelperTest extends TestCase
$identifier = ShortUrlIdentifier::fromShortCodeAndDomain($shortCode); $identifier = ShortUrlIdentifier::fromShortCodeAndDomain($shortCode);
$spec = $apiKey?->spec(); $spec = $apiKey?->spec();
$repo = $this->createMock(ShortUrlRepositoryInterface::class); $repo = $this->createMock(ShortUrlRepository::class);
$repo->expects($this->once())->method('shortCodeIsInUse')->with($identifier, $spec)->willReturn(true); $repo->expects($this->once())->method('shortCodeIsInUse')->with($identifier, $spec)->willReturn(true);
$list = array_map( $list = array_map(
@ -123,7 +123,7 @@ class VisitsStatsHelperTest extends TestCase
$shortCode = '123ABC'; $shortCode = '123ABC';
$identifier = ShortUrlIdentifier::fromShortCodeAndDomain($shortCode); $identifier = ShortUrlIdentifier::fromShortCodeAndDomain($shortCode);
$repo = $this->createMock(ShortUrlRepositoryInterface::class); $repo = $this->createMock(ShortUrlRepository::class);
$repo->expects($this->once())->method('shortCodeIsInUse')->with($identifier, null)->willReturn(false); $repo->expects($this->once())->method('shortCodeIsInUse')->with($identifier, null)->willReturn(false);
$this->em->expects($this->once())->method('getRepository')->with(ShortUrl::class)->willReturn($repo); $this->em->expects($this->once())->method('getRepository')->with(ShortUrl::class)->willReturn($repo);

View file

@ -14,7 +14,7 @@ use Shlinkio\Shlink\Common\Exception\InvalidArgumentException;
use Shlinkio\Shlink\Core\Domain\Entity\Domain; use Shlinkio\Shlink\Core\Domain\Entity\Domain;
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\ApiKey\Repository\ApiKeyRepositoryInterface; use Shlinkio\Shlink\Rest\ApiKey\Repository\ApiKeyRepository;
use Shlinkio\Shlink\Rest\Entity\ApiKey; use Shlinkio\Shlink\Rest\Entity\ApiKey;
use Shlinkio\Shlink\Rest\Service\ApiKeyService; use Shlinkio\Shlink\Rest\Service\ApiKeyService;
@ -22,12 +22,12 @@ class ApiKeyServiceTest extends TestCase
{ {
private ApiKeyService $service; private ApiKeyService $service;
private MockObject & EntityManager $em; private MockObject & EntityManager $em;
private MockObject & ApiKeyRepositoryInterface $repo; private MockObject & ApiKeyRepository $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(ApiKeyRepositoryInterface::class); $this->repo = $this->createMock(ApiKeyRepository::class);
$this->service = new ApiKeyService($this->em); $this->service = new ApiKeyService($this->em);
} }