Refactored ShortUrlRepository to wrap args into DTOs

This commit is contained in:
Alejandro Celaya
2022-01-17 20:10:41 +01:00
parent 0727c7bdfb
commit 661b07e12f
9 changed files with 251 additions and 119 deletions

View File

@@ -18,6 +18,11 @@ final class Ordering
return new self($field, $dir ?? self::DEFAULT_DIR);
}
public static function emptyInstance(): self
{
return self::fromTuple([null, null]);
}
public function orderField(): ?string
{
return $this->field;

View File

@@ -11,12 +11,13 @@ use Doctrine\ORM\QueryBuilder;
use Happyr\DoctrineSpecification\Repository\EntitySpecificationRepository;
use Happyr\DoctrineSpecification\Specification\Specification;
use Shlinkio\Shlink\Common\Doctrine\Type\ChronosDateTimeType;
use Shlinkio\Shlink\Common\Util\DateRange;
use Shlinkio\Shlink\Core\Entity\ShortUrl;
use Shlinkio\Shlink\Core\Model\Ordering;
use Shlinkio\Shlink\Core\Model\ShortUrlIdentifier;
use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
use Shlinkio\Shlink\Core\Model\ShortUrlsParams;
use Shlinkio\Shlink\Core\ShortUrl\Persistence\ShortUrlsCountFiltering;
use Shlinkio\Shlink\Core\ShortUrl\Persistence\ShortUrlsListFiltering;
use Shlinkio\Shlink\Importer\Model\ImportedShlinkUrl;
use function array_column;
@@ -26,27 +27,18 @@ use function Functional\contains;
class ShortUrlRepository extends EntitySpecificationRepository implements ShortUrlRepositoryInterface
{
/**
* @param string[] $tags
* @return ShortUrl[]
*/
public function findList(
?int $limit = null,
?int $offset = null,
?string $searchTerm = null,
array $tags = [],
?string $tagsMode = null,
?Ordering $orderBy = null,
?DateRange $dateRange = null,
?Specification $spec = null,
): array {
$qb = $this->createListQueryBuilder($searchTerm, $tags, $tagsMode, $dateRange, $spec);
public function findList(ShortUrlsListFiltering $filtering): array
{
$qb = $this->createListQueryBuilder($filtering);
$qb->select('DISTINCT s')
->setMaxResults($limit)
->setFirstResult($offset);
->setMaxResults($filtering->limit())
->setFirstResult($filtering->offset());
// In case the ordering has been specified, the query could be more complex. Process it
if ($orderBy?->hasOrderField()) {
return $this->processOrderByForList($qb, $orderBy);
if ($filtering->orderBy()->hasOrderField()) {
return $this->processOrderByForList($qb, $filtering->orderBy());
}
// With no explicit order by, fallback to dateCreated-DESC
@@ -77,30 +69,21 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU
return $qb->getQuery()->getResult();
}
public function countList(
?string $searchTerm = null,
array $tags = [],
?string $tagsMode = null,
?DateRange $dateRange = null,
?Specification $spec = null,
): int {
$qb = $this->createListQueryBuilder($searchTerm, $tags, $tagsMode, $dateRange, $spec);
public function countList(ShortUrlsCountFiltering $filtering): int
{
$qb = $this->createListQueryBuilder($filtering);
$qb->select('COUNT(DISTINCT s)');
return (int) $qb->getQuery()->getSingleScalarResult();
}
private function createListQueryBuilder(
?string $searchTerm,
array $tags,
?string $tagsMode,
?DateRange $dateRange,
?Specification $spec,
): QueryBuilder {
private function createListQueryBuilder(ShortUrlsCountFiltering $filtering): QueryBuilder
{
$qb = $this->getEntityManager()->createQueryBuilder();
$qb->from(ShortUrl::class, 's')
->where('1=1');
$dateRange = $filtering->dateRange();
if ($dateRange?->startDate() !== null) {
$qb->andWhere($qb->expr()->gte('s.dateCreated', ':startDate'));
$qb->setParameter('startDate', $dateRange->startDate(), ChronosDateTimeType::CHRONOS_DATETIME);
@@ -110,6 +93,8 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU
$qb->setParameter('endDate', $dateRange->endDate(), ChronosDateTimeType::CHRONOS_DATETIME);
}
$searchTerm = $filtering->searchTerm();
$tags = $filtering->tags();
// Apply search term to every searchable field if not empty
if (! empty($searchTerm)) {
// Left join with tags only if no tags were provided. In case of tags, an inner join will be done later
@@ -131,13 +116,13 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU
// Filter by tags if provided
if (! empty($tags)) {
$tagsMode = $tagsMode ?? ShortUrlsParams::TAGS_MODE_ANY;
$tagsMode = $filtering->tagsMode() ?? ShortUrlsParams::TAGS_MODE_ANY;
$tagsMode === ShortUrlsParams::TAGS_MODE_ANY
? $qb->join('s.tags', 't')->andWhere($qb->expr()->in('t.name', $tags))
: $this->joinAllTags($qb, $tags);
}
$this->applySpecification($qb, $spec, 's');
$this->applySpecification($qb, $filtering->apiKey()?->spec(), 's');
return $qb;
}

View File

@@ -7,34 +7,20 @@ namespace Shlinkio\Shlink\Core\Repository;
use Doctrine\Persistence\ObjectRepository;
use Happyr\DoctrineSpecification\Repository\EntitySpecificationRepositoryInterface;
use Happyr\DoctrineSpecification\Specification\Specification;
use Shlinkio\Shlink\Common\Util\DateRange;
use Shlinkio\Shlink\Core\Entity\ShortUrl;
use Shlinkio\Shlink\Core\Model\Ordering;
use Shlinkio\Shlink\Core\Model\ShortUrlIdentifier;
use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
use Shlinkio\Shlink\Core\ShortUrl\Persistence\ShortUrlsCountFiltering;
use Shlinkio\Shlink\Core\ShortUrl\Persistence\ShortUrlsListFiltering;
use Shlinkio\Shlink\Importer\Model\ImportedShlinkUrl;
interface ShortUrlRepositoryInterface extends ObjectRepository, EntitySpecificationRepositoryInterface
{
public function findList(
?int $limit = null,
?int $offset = null,
?string $searchTerm = null,
array $tags = [],
?string $tagsMode = null,
?Ordering $orderBy = null,
?DateRange $dateRange = null,
?Specification $spec = null,
): array;
public function findList(ShortUrlsListFiltering $filtering): array;
public function countList(
?string $searchTerm = null,
array $tags = [],
?string $tagsMode = null,
?DateRange $dateRange = null,
?Specification $spec = null,
): int;
public function countList(ShortUrlsCountFiltering $filtering): int;
// TODO Use ShortUrlIdentifier here
public function findOneWithDomainFallback(string $shortCode, ?string $domain = null): ?ShortUrl;
public function findOne(ShortUrlIdentifier $identifier, ?Specification $spec = null): ?ShortUrl;

View File

@@ -12,10 +12,10 @@ use Shlinkio\Shlink\Core\Exception\ShortUrlNotFoundException;
use Shlinkio\Shlink\Core\Model\ShortUrlEdit;
use Shlinkio\Shlink\Core\Model\ShortUrlIdentifier;
use Shlinkio\Shlink\Core\Model\ShortUrlsParams;
use Shlinkio\Shlink\Core\Paginator\Adapter\ShortUrlRepositoryAdapter;
use Shlinkio\Shlink\Core\Repository\ShortUrlRepository;
use Shlinkio\Shlink\Core\Service\ShortUrl\ShortUrlResolverInterface;
use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlTitleResolutionHelperInterface;
use Shlinkio\Shlink\Core\ShortUrl\Paginator\Adapter\ShortUrlRepositoryAdapter;
use Shlinkio\Shlink\Core\ShortUrl\Resolver\ShortUrlRelationResolverInterface;
use Shlinkio\Shlink\Rest\Entity\ApiKey;

View File

@@ -2,11 +2,13 @@
declare(strict_types=1);
namespace Shlinkio\Shlink\Core\Paginator\Adapter;
namespace Shlinkio\Shlink\Core\ShortUrl\Paginator\Adapter;
use Pagerfanta\Adapter\AdapterInterface;
use Shlinkio\Shlink\Core\Model\ShortUrlsParams;
use Shlinkio\Shlink\Core\Repository\ShortUrlRepositoryInterface;
use Shlinkio\Shlink\Core\ShortUrl\Persistence\ShortUrlsCountFiltering;
use Shlinkio\Shlink\Core\ShortUrl\Persistence\ShortUrlsListFiltering;
use Shlinkio\Shlink\Rest\Entity\ApiKey;
class ShortUrlRepositoryAdapter implements AdapterInterface
@@ -21,25 +23,12 @@ class ShortUrlRepositoryAdapter implements AdapterInterface
public function getSlice(int $offset, int $length): iterable
{
return $this->repository->findList(
$length,
$offset,
$this->params->searchTerm(),
$this->params->tags(),
$this->params->tagsMode(),
$this->params->orderBy(),
$this->params->dateRange(),
$this->apiKey?->spec(),
ShortUrlsListFiltering::fromLimitsAndParams($length, $offset, $this->params, $this->apiKey),
);
}
public function getNbResults(): int
{
return $this->repository->countList(
$this->params->searchTerm(),
$this->params->tags(),
$this->params->tagsMode(),
$this->params->dateRange(),
$this->apiKey?->spec(),
);
return $this->repository->countList(ShortUrlsCountFiltering::fromParams($this->params, $this->apiKey));
}
}

View File

@@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\Core\ShortUrl\Persistence;
use Shlinkio\Shlink\Common\Util\DateRange;
use Shlinkio\Shlink\Core\Model\ShortUrlsParams;
use Shlinkio\Shlink\Rest\Entity\ApiKey;
class ShortUrlsCountFiltering
{
public function __construct(
private ?string $searchTerm = null,
private array $tags = [],
private ?string $tagsMode = null,
private ?DateRange $dateRange = null,
private ?ApiKey $apiKey = null,
) {
}
public static function fromParams(ShortUrlsParams $params, ?ApiKey $apiKey): self
{
return new self($params->searchTerm(), $params->tags(), $params->tagsMode(), $params->dateRange(), $apiKey);
}
public function searchTerm(): ?string
{
return $this->searchTerm;
}
public function tags(): array
{
return $this->tags;
}
public function tagsMode(): ?string
{
return $this->tagsMode;
}
public function dateRange(): ?DateRange
{
return $this->dateRange;
}
public function apiKey(): ?ApiKey
{
return $this->apiKey;
}
}

View File

@@ -0,0 +1,55 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\Core\ShortUrl\Persistence;
use Shlinkio\Shlink\Common\Util\DateRange;
use Shlinkio\Shlink\Core\Model\Ordering;
use Shlinkio\Shlink\Core\Model\ShortUrlsParams;
use Shlinkio\Shlink\Rest\Entity\ApiKey;
class ShortUrlsListFiltering extends ShortUrlsCountFiltering
{
public function __construct(
private ?int $limit,
private ?int $offset,
private Ordering $orderBy,
?string $searchTerm = null,
array $tags = [],
?string $tagsMode = null,
?DateRange $dateRange = null,
?ApiKey $apiKey = null,
) {
parent::__construct($searchTerm, $tags, $tagsMode, $dateRange, $apiKey);
}
public static function fromLimitsAndParams(int $limit, int $offset, ShortUrlsParams $params, ?ApiKey $apiKey): self
{
return new self(
$limit,
$offset,
$params->orderBy(),
$params->searchTerm(),
$params->tags(),
$params->tagsMode(),
$params->dateRange(),
$apiKey,
);
}
public function offset(): ?int
{
return $this->offset;
}
public function limit(): ?int
{
return $this->limit;
}
public function orderBy(): Ordering
{
return $this->orderBy;
}
}