diff --git a/CHANGELOG.md b/CHANGELOG.md index 2913aae2..9c41f6d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,23 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com), and this project adheres to [Semantic Versioning](https://semver.org). +## [Unreleased] +### Added +* *Nothing* + +### Changed +* [#1036](https://github.com/shlinkio/shlink/issues/1036) Updated to `happyr/doctrine-specification` 2.0. + +### Deprecated +* *Nothing* + +### Removed +* *Nothing* + +### Fixed +* *Nothing* + + ## [2.6.1] - 2021-02-22 ### Added * *Nothing* diff --git a/composer.json b/composer.json index a22c26ae..d544ac41 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ "endroid/qr-code": "dev-master#0f1613a as 3.10", "geoip2/geoip2": "^2.9", "guzzlehttp/guzzle": "^7.0", - "happyr/doctrine-specification": "2.0.x-dev#cb116d3 as 2.0", + "happyr/doctrine-specification": "^2.0", "laminas/laminas-config": "^3.3", "laminas/laminas-config-aggregator": "^1.1", "laminas/laminas-diactoros": "^2.1.3", diff --git a/config/autoload/entity-manager.global.php b/config/autoload/entity-manager.global.php index 639df7ec..c3d2ab83 100644 --- a/config/autoload/entity-manager.global.php +++ b/config/autoload/entity-manager.global.php @@ -4,7 +4,7 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Common; -use Happyr\DoctrineSpecification\EntitySpecificationRepository; +use Happyr\DoctrineSpecification\Repository\EntitySpecificationRepository; return [ diff --git a/module/Core/src/Domain/Repository/DomainRepository.php b/module/Core/src/Domain/Repository/DomainRepository.php index f2152fbe..2e4f3bb2 100644 --- a/module/Core/src/Domain/Repository/DomainRepository.php +++ b/module/Core/src/Domain/Repository/DomainRepository.php @@ -5,7 +5,7 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Domain\Repository; use Doctrine\ORM\Query\Expr\Join; -use Happyr\DoctrineSpecification\EntitySpecificationRepository; +use Happyr\DoctrineSpecification\Repository\EntitySpecificationRepository; use Shlinkio\Shlink\Core\Entity\Domain; use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Rest\Entity\ApiKey; diff --git a/module/Core/src/Domain/Repository/DomainRepositoryInterface.php b/module/Core/src/Domain/Repository/DomainRepositoryInterface.php index 13917dc6..1d201520 100644 --- a/module/Core/src/Domain/Repository/DomainRepositoryInterface.php +++ b/module/Core/src/Domain/Repository/DomainRepositoryInterface.php @@ -5,7 +5,7 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Domain\Repository; use Doctrine\Persistence\ObjectRepository; -use Happyr\DoctrineSpecification\EntitySpecificationRepositoryInterface; +use Happyr\DoctrineSpecification\Repository\EntitySpecificationRepositoryInterface; use Shlinkio\Shlink\Core\Entity\Domain; use Shlinkio\Shlink\Rest\Entity\ApiKey; diff --git a/module/Core/src/Repository/ShortUrlRepository.php b/module/Core/src/Repository/ShortUrlRepository.php index f7a089b7..24b20a38 100644 --- a/module/Core/src/Repository/ShortUrlRepository.php +++ b/module/Core/src/Repository/ShortUrlRepository.php @@ -6,7 +6,7 @@ namespace Shlinkio\Shlink\Core\Repository; use Doctrine\ORM\Query\Expr\Join; use Doctrine\ORM\QueryBuilder; -use Happyr\DoctrineSpecification\EntitySpecificationRepository; +use Happyr\DoctrineSpecification\Repository\EntitySpecificationRepository; use Happyr\DoctrineSpecification\Specification\Specification; use Shlinkio\Shlink\Common\Doctrine\Type\ChronosDateTimeType; use Shlinkio\Shlink\Common\Util\DateRange; diff --git a/module/Core/src/Repository/ShortUrlRepositoryInterface.php b/module/Core/src/Repository/ShortUrlRepositoryInterface.php index e5662e20..ca04ffda 100644 --- a/module/Core/src/Repository/ShortUrlRepositoryInterface.php +++ b/module/Core/src/Repository/ShortUrlRepositoryInterface.php @@ -5,7 +5,7 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Repository; use Doctrine\Persistence\ObjectRepository; -use Happyr\DoctrineSpecification\EntitySpecificationRepositoryInterface; +use Happyr\DoctrineSpecification\Repository\EntitySpecificationRepositoryInterface; use Happyr\DoctrineSpecification\Specification\Specification; use Shlinkio\Shlink\Common\Util\DateRange; use Shlinkio\Shlink\Core\Entity\ShortUrl; diff --git a/module/Core/src/Repository/TagRepository.php b/module/Core/src/Repository/TagRepository.php index dd15c292..d21122d0 100644 --- a/module/Core/src/Repository/TagRepository.php +++ b/module/Core/src/Repository/TagRepository.php @@ -4,9 +4,8 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Repository; -use Happyr\DoctrineSpecification\EntitySpecificationRepository; +use Happyr\DoctrineSpecification\Repository\EntitySpecificationRepository; use Happyr\DoctrineSpecification\Spec; -use Happyr\DoctrineSpecification\Specification\Specification; use Shlinkio\Shlink\Core\Entity\Tag; use Shlinkio\Shlink\Core\Tag\Model\TagInfo; use Shlinkio\Shlink\Core\Tag\Spec\CountTagsWithName; @@ -33,7 +32,7 @@ class TagRepository extends EntitySpecificationRepository implements TagReposito /** * @return TagInfo[] */ - public function findTagsWithInfo(?Specification $spec = null): array + public function findTagsWithInfo(?ApiKey $apiKey = null): array { $qb = $this->createQueryBuilder('t'); $qb->select('t AS tag', 'COUNT(DISTINCT s.id) AS shortUrlsCount', 'COUNT(DISTINCT v.id) AS visitsCount') @@ -42,7 +41,9 @@ class TagRepository extends EntitySpecificationRepository implements TagReposito ->groupBy('t') ->orderBy('t.name', 'ASC'); - $this->applySpecification($qb, $spec, 't'); + if ($apiKey !== null) { + $this->applySpecification($qb, $apiKey->spec(false, 'shortUrls'), 't'); + } $query = $qb->getQuery(); diff --git a/module/Core/src/Repository/TagRepositoryInterface.php b/module/Core/src/Repository/TagRepositoryInterface.php index 86898ed1..924706ff 100644 --- a/module/Core/src/Repository/TagRepositoryInterface.php +++ b/module/Core/src/Repository/TagRepositoryInterface.php @@ -5,8 +5,7 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Repository; use Doctrine\Persistence\ObjectRepository; -use Happyr\DoctrineSpecification\EntitySpecificationRepositoryInterface; -use Happyr\DoctrineSpecification\Specification\Specification; +use Happyr\DoctrineSpecification\Repository\EntitySpecificationRepositoryInterface; use Shlinkio\Shlink\Core\Tag\Model\TagInfo; use Shlinkio\Shlink\Rest\Entity\ApiKey; @@ -17,7 +16,7 @@ interface TagRepositoryInterface extends ObjectRepository, EntitySpecificationRe /** * @return TagInfo[] */ - public function findTagsWithInfo(?Specification $spec = null): array; + public function findTagsWithInfo(?ApiKey $apiKey = null): array; public function tagExists(string $tag, ?ApiKey $apiKey = null): bool; } diff --git a/module/Core/src/Repository/VisitRepository.php b/module/Core/src/Repository/VisitRepository.php index b869093e..cd51f60d 100644 --- a/module/Core/src/Repository/VisitRepository.php +++ b/module/Core/src/Repository/VisitRepository.php @@ -6,7 +6,7 @@ namespace Shlinkio\Shlink\Core\Repository; use Doctrine\ORM\Query\ResultSetMappingBuilder; use Doctrine\ORM\QueryBuilder; -use Happyr\DoctrineSpecification\EntitySpecificationRepository; +use Happyr\DoctrineSpecification\Repository\EntitySpecificationRepository; use Happyr\DoctrineSpecification\Specification\Specification; use Shlinkio\Shlink\Common\Util\DateRange; use Shlinkio\Shlink\Core\Entity\ShortUrl; diff --git a/module/Core/src/Repository/VisitRepositoryInterface.php b/module/Core/src/Repository/VisitRepositoryInterface.php index 3ecf0bca..96fb21ee 100644 --- a/module/Core/src/Repository/VisitRepositoryInterface.php +++ b/module/Core/src/Repository/VisitRepositoryInterface.php @@ -5,7 +5,7 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Repository; use Doctrine\Persistence\ObjectRepository; -use Happyr\DoctrineSpecification\EntitySpecificationRepositoryInterface; +use Happyr\DoctrineSpecification\Repository\EntitySpecificationRepositoryInterface; use Happyr\DoctrineSpecification\Specification\Specification; use Shlinkio\Shlink\Common\Util\DateRange; use Shlinkio\Shlink\Core\Entity\Visit; diff --git a/module/Core/src/ShortUrl/Spec/BelongsToApiKey.php b/module/Core/src/ShortUrl/Spec/BelongsToApiKey.php index 9e094b90..4aa3579f 100644 --- a/module/Core/src/ShortUrl/Spec/BelongsToApiKey.php +++ b/module/Core/src/ShortUrl/Spec/BelongsToApiKey.php @@ -4,21 +4,21 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\ShortUrl\Spec; -use Happyr\DoctrineSpecification\BaseSpecification; use Happyr\DoctrineSpecification\Filter\Filter; use Happyr\DoctrineSpecification\Spec; +use Happyr\DoctrineSpecification\Specification\BaseSpecification; use Shlinkio\Shlink\Rest\Entity\ApiKey; class BelongsToApiKey extends BaseSpecification { private ApiKey $apiKey; - private string $dqlAlias; + private ?string $dqlAlias; public function __construct(ApiKey $apiKey, ?string $dqlAlias = null) { $this->apiKey = $apiKey; - $this->dqlAlias = $dqlAlias ?? 's'; - parent::__construct($this->dqlAlias); + $this->dqlAlias = $dqlAlias; + parent::__construct(); } protected function getSpec(): Filter diff --git a/module/Core/src/ShortUrl/Spec/BelongsToApiKeyInlined.php b/module/Core/src/ShortUrl/Spec/BelongsToApiKeyInlined.php index 197031f3..579407cd 100644 --- a/module/Core/src/ShortUrl/Spec/BelongsToApiKeyInlined.php +++ b/module/Core/src/ShortUrl/Spec/BelongsToApiKeyInlined.php @@ -5,10 +5,10 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\ShortUrl\Spec; use Doctrine\ORM\QueryBuilder; -use Happyr\DoctrineSpecification\Specification\Specification; +use Happyr\DoctrineSpecification\Filter\Filter; use Shlinkio\Shlink\Rest\Entity\ApiKey; -class BelongsToApiKeyInlined implements Specification +class BelongsToApiKeyInlined implements Filter { private ApiKey $apiKey; @@ -22,8 +22,4 @@ class BelongsToApiKeyInlined implements Specification // Parameters in this query need to be inlined, not bound, as we need to use it as sub-query later return (string) $qb->expr()->eq('s.authorApiKey', '\'' . $this->apiKey->getId() . '\''); } - - public function modify(QueryBuilder $qb, string $dqlAlias): void - { - } } diff --git a/module/Core/src/ShortUrl/Spec/BelongsToDomain.php b/module/Core/src/ShortUrl/Spec/BelongsToDomain.php index 81b4388a..7745ff27 100644 --- a/module/Core/src/ShortUrl/Spec/BelongsToDomain.php +++ b/module/Core/src/ShortUrl/Spec/BelongsToDomain.php @@ -4,20 +4,20 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\ShortUrl\Spec; -use Happyr\DoctrineSpecification\BaseSpecification; use Happyr\DoctrineSpecification\Filter\Filter; use Happyr\DoctrineSpecification\Spec; +use Happyr\DoctrineSpecification\Specification\BaseSpecification; class BelongsToDomain extends BaseSpecification { private string $domainId; - private string $dqlAlias; + private ?string $dqlAlias; public function __construct(string $domainId, ?string $dqlAlias = null) { $this->domainId = $domainId; - $this->dqlAlias = $dqlAlias ?? 's'; - parent::__construct($this->dqlAlias); + $this->dqlAlias = $dqlAlias; + parent::__construct(); } protected function getSpec(): Filter diff --git a/module/Core/src/ShortUrl/Spec/BelongsToDomainInlined.php b/module/Core/src/ShortUrl/Spec/BelongsToDomainInlined.php index a8ef527e..cb69a359 100644 --- a/module/Core/src/ShortUrl/Spec/BelongsToDomainInlined.php +++ b/module/Core/src/ShortUrl/Spec/BelongsToDomainInlined.php @@ -5,9 +5,9 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\ShortUrl\Spec; use Doctrine\ORM\QueryBuilder; -use Happyr\DoctrineSpecification\Specification\Specification; +use Happyr\DoctrineSpecification\Filter\Filter; -class BelongsToDomainInlined implements Specification +class BelongsToDomainInlined implements Filter { private string $domainId; @@ -21,8 +21,4 @@ class BelongsToDomainInlined implements Specification // Parameters in this query need to be inlined, not bound, as we need to use it as sub-query later return (string) $qb->expr()->eq('s.domain', '\'' . $this->domainId . '\''); } - - public function modify(QueryBuilder $qb, string $dqlAlias): void - { - } } diff --git a/module/Core/src/Spec/InDateRange.php b/module/Core/src/Spec/InDateRange.php index 44944aed..953ed9f2 100644 --- a/module/Core/src/Spec/InDateRange.php +++ b/module/Core/src/Spec/InDateRange.php @@ -4,8 +4,8 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Spec; -use Happyr\DoctrineSpecification\BaseSpecification; use Happyr\DoctrineSpecification\Spec; +use Happyr\DoctrineSpecification\Specification\BaseSpecification; use Happyr\DoctrineSpecification\Specification\Specification; use Shlinkio\Shlink\Common\Util\DateRange; diff --git a/module/Core/src/Tag/Spec/CountTagsWithName.php b/module/Core/src/Tag/Spec/CountTagsWithName.php index a3f90a78..8dd3e44d 100644 --- a/module/Core/src/Tag/Spec/CountTagsWithName.php +++ b/module/Core/src/Tag/Spec/CountTagsWithName.php @@ -4,8 +4,8 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Tag\Spec; -use Happyr\DoctrineSpecification\BaseSpecification; use Happyr\DoctrineSpecification\Spec; +use Happyr\DoctrineSpecification\Specification\BaseSpecification; use Happyr\DoctrineSpecification\Specification\Specification; class CountTagsWithName extends BaseSpecification diff --git a/module/Core/src/Tag/TagService.php b/module/Core/src/Tag/TagService.php index ae46a312..4619bd9d 100644 --- a/module/Core/src/Tag/TagService.php +++ b/module/Core/src/Tag/TagService.php @@ -52,7 +52,7 @@ class TagService implements TagServiceInterface { /** @var TagRepositoryInterface $repo */ $repo = $this->em->getRepository(Tag::class); - return $repo->findTagsWithInfo($apiKey !== null ? $apiKey->spec() : null); + return $repo->findTagsWithInfo($apiKey); } /** diff --git a/module/Core/src/Visit/Spec/CountOfOrphanVisits.php b/module/Core/src/Visit/Spec/CountOfOrphanVisits.php index fb8ee3bd..97712944 100644 --- a/module/Core/src/Visit/Spec/CountOfOrphanVisits.php +++ b/module/Core/src/Visit/Spec/CountOfOrphanVisits.php @@ -4,8 +4,8 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Visit\Spec; -use Happyr\DoctrineSpecification\BaseSpecification; use Happyr\DoctrineSpecification\Spec; +use Happyr\DoctrineSpecification\Specification\BaseSpecification; use Happyr\DoctrineSpecification\Specification\Specification; use Shlinkio\Shlink\Common\Util\DateRange; use Shlinkio\Shlink\Core\Spec\InDateRange; diff --git a/module/Core/src/Visit/Spec/CountOfShortUrlVisits.php b/module/Core/src/Visit/Spec/CountOfShortUrlVisits.php index 6a125ee9..ea4a4800 100644 --- a/module/Core/src/Visit/Spec/CountOfShortUrlVisits.php +++ b/module/Core/src/Visit/Spec/CountOfShortUrlVisits.php @@ -4,8 +4,8 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Visit\Spec; -use Happyr\DoctrineSpecification\BaseSpecification; use Happyr\DoctrineSpecification\Spec; +use Happyr\DoctrineSpecification\Specification\BaseSpecification; use Happyr\DoctrineSpecification\Specification\Specification; use Shlinkio\Shlink\Rest\ApiKey\Spec\WithApiKeySpecsEnsuringJoin; use Shlinkio\Shlink\Rest\Entity\ApiKey; diff --git a/module/Core/test/Service/Tag/TagServiceTest.php b/module/Core/test/Service/Tag/TagServiceTest.php index 5f518184..9b484791 100644 --- a/module/Core/test/Service/Tag/TagServiceTest.php +++ b/module/Core/test/Service/Tag/TagServiceTest.php @@ -60,7 +60,7 @@ class TagServiceTest extends TestCase { $expected = [new TagInfo(new Tag('foo'), 1, 1), new TagInfo(new Tag('bar'), 3, 10)]; - $find = $this->repo->findTagsWithInfo($apiKey === null ? null : $apiKey->spec())->willReturn($expected); + $find = $this->repo->findTagsWithInfo($apiKey)->willReturn($expected); $result = $this->service->tagsInfo($apiKey); diff --git a/module/Rest/src/ApiKey/Role.php b/module/Rest/src/ApiKey/Role.php index ff3211ba..c3677029 100644 --- a/module/Rest/src/ApiKey/Role.php +++ b/module/Rest/src/ApiKey/Role.php @@ -21,15 +21,18 @@ class Role self::DOMAIN_SPECIFIC => 'Domain only', ]; - public static function toSpec(ApiKeyRole $role, bool $inlined): Specification + public static function toSpec(ApiKeyRole $role, bool $inlined, ?string $context = null): Specification { if ($role->name() === self::AUTHORED_SHORT_URLS) { - return $inlined ? new BelongsToApiKeyInlined($role->apiKey()) : new BelongsToApiKey($role->apiKey()); + $apiKey = $role->apiKey(); + return $inlined ? Spec::andX(new BelongsToApiKeyInlined($apiKey)) : new BelongsToApiKey($apiKey, $context); } if ($role->name() === self::DOMAIN_SPECIFIC) { $domainId = self::domainIdFromMeta($role->meta()); - return $inlined ? new BelongsToDomainInlined($domainId) : new BelongsToDomain($domainId); + return $inlined + ? Spec::andX(new BelongsToDomainInlined($domainId)) + : new BelongsToDomain($domainId, $context); } return Spec::andX(); diff --git a/module/Rest/src/ApiKey/Spec/WithApiKeySpecsEnsuringJoin.php b/module/Rest/src/ApiKey/Spec/WithApiKeySpecsEnsuringJoin.php index 64359d15..a1f9b361 100644 --- a/module/Rest/src/ApiKey/Spec/WithApiKeySpecsEnsuringJoin.php +++ b/module/Rest/src/ApiKey/Spec/WithApiKeySpecsEnsuringJoin.php @@ -4,8 +4,8 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Rest\ApiKey\Spec; -use Happyr\DoctrineSpecification\BaseSpecification; use Happyr\DoctrineSpecification\Spec; +use Happyr\DoctrineSpecification\Specification\BaseSpecification; use Happyr\DoctrineSpecification\Specification\Specification; use Shlinkio\Shlink\Rest\Entity\ApiKey; @@ -25,7 +25,7 @@ class WithApiKeySpecsEnsuringJoin extends BaseSpecification { return $this->apiKey === null || $this->apiKey->isAdmin() ? Spec::andX() : Spec::andX( Spec::join($this->fieldToJoin, 's'), - $this->apiKey->spec(), + $this->apiKey->spec(false, $this->fieldToJoin), ); } } diff --git a/module/Rest/src/Entity/ApiKey.php b/module/Rest/src/Entity/ApiKey.php index 62729031..d153b7f1 100644 --- a/module/Rest/src/Entity/ApiKey.php +++ b/module/Rest/src/Entity/ApiKey.php @@ -92,9 +92,9 @@ class ApiKey extends AbstractEntity return $this->key; } - public function spec(bool $inlined = false): Specification + public function spec(bool $inlined = false, ?string $context = null): Specification { - $specs = $this->roles->map(fn (ApiKeyRole $role) => Role::toSpec($role, $inlined))->getValues(); + $specs = $this->roles->map(fn (ApiKeyRole $role) => Role::toSpec($role, $inlined, $context))->getValues(); return Spec::andX(...$specs); } diff --git a/module/Rest/test/ApiKey/RoleTest.php b/module/Rest/test/ApiKey/RoleTest.php index 4cb9ba1b..60a55ca5 100644 --- a/module/Rest/test/ApiKey/RoleTest.php +++ b/module/Rest/test/ApiKey/RoleTest.php @@ -35,7 +35,7 @@ class RoleTest extends TestCase yield 'inline author role' => [ new ApiKeyRole(Role::AUTHORED_SHORT_URLS, [], $apiKey), true, - new BelongsToApiKeyInlined($apiKey), + Spec::andX(new BelongsToApiKeyInlined($apiKey)), ]; yield 'not inline author role' => [ new ApiKeyRole(Role::AUTHORED_SHORT_URLS, [], $apiKey), @@ -45,7 +45,7 @@ class RoleTest extends TestCase yield 'inline domain role' => [ new ApiKeyRole(Role::DOMAIN_SPECIFIC, ['domain_id' => '123'], $apiKey), true, - new BelongsToDomainInlined('123'), + Spec::andX(new BelongsToDomainInlined('123')), ]; yield 'not inline domain role' => [ new ApiKeyRole(Role::DOMAIN_SPECIFIC, ['domain_id' => '456'], $apiKey),