Merge pull request #1038 from acelaya-forks/feature/happyr-spec-2

Feature/happyr spec 2
This commit is contained in:
Alejandro Celaya 2021-02-26 20:47:29 +01:00 committed by GitHub
commit c931874bac
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 62 additions and 50 deletions

View file

@ -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*

View file

@ -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",

View file

@ -4,7 +4,7 @@ declare(strict_types=1);
namespace Shlinkio\Shlink\Common;
use Happyr\DoctrineSpecification\EntitySpecificationRepository;
use Happyr\DoctrineSpecification\Repository\EntitySpecificationRepository;
return [

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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();

View file

@ -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;
}

View file

@ -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;

View file

@ -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;

View file

@ -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

View file

@ -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
{
}
}

View file

@ -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

View file

@ -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
{
}
}

View file

@ -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;

View file

@ -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

View file

@ -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);
}
/**

View file

@ -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;

View file

@ -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;

View file

@ -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);

View file

@ -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();

View file

@ -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),
);
}
}

View file

@ -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);
}

View file

@ -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),