View source
<?php
declare (strict_types=1);
namespace Drupal\jsonapi_resources\Unstable\Entity\Query\Pagination;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Entity\Query\QueryInterface;
use Drupal\Core\Http\Exception\CacheableBadRequestHttpException;
use Drupal\Core\Url;
use Drupal\jsonapi\JsonApiResource\Link;
use Drupal\jsonapi\JsonApiResource\LinkCollection;
use Drupal\jsonapi\Query\OffsetPage;
use Drupal\jsonapi_resources\Entity\Query\PaginatorInterface;
use Drupal\jsonapi_resources\Entity\Query\PaginatorMetadata;
use Drupal\jsonapi_resources\Unstable\Entity\Query\CacheabilityCapturingExecutor;
use Symfony\Component\HttpFoundation\Request;
final class OffsetLimitPaginator implements PaginatorInterface {
protected $request;
protected $entityQueryExecutor;
protected function __construct(Request $request, CacheabilityCapturingExecutor $entity_query_executor) {
$this->request = $request;
$this->entityQueryExecutor = $entity_query_executor;
}
public static function create(Request $request, CacheabilityCapturingExecutor $entity_query_executor) {
return new static($request, $entity_query_executor);
}
public function applyToQuery(QueryInterface $query, CacheableMetadata $cacheable_metadata) : void {
$cacheable_metadata
->addCacheContexts([
'url.query_args:page',
]);
$pagination = $this->request->query
->has('page') ? OffsetPage::createFromQueryParameter($this->request->query
->get('page')) : new OffsetPage(OffsetPage::DEFAULT_OFFSET, OffsetPage::SIZE_MAX);
$query
->range($pagination
->getOffset(), $pagination
->getSize() + 1);
$metadata = new PaginatorMetadata();
$metadata->pageSizeMax = $pagination
->getSize();
$metadata->pageLocation = $pagination
->getOffset();
$query
->addMetaData(PaginatorMetadata::KEY, $metadata);
}
public function getPaginationLinks(QueryInterface $executed_query, CacheableMetadata $cacheable_metadata, $calculate_last_link = FALSE) : LinkCollection {
$paginator_metadata = $executed_query
->getMetaData(PaginatorMetadata::KEY);
assert($paginator_metadata instanceof PaginatorMetadata);
$has_next_page = !empty($paginator_metadata->hasNextPage);
if ($calculate_last_link && $has_next_page) {
$count_query = $executed_query
->range(NULL, NULL)
->count();
$total_count = (int) $this->entityQueryExecutor
->executeQueryAndCaptureCacheability($count_query, $cacheable_metadata);
if (empty($total_count)) {
return new LinkCollection([]);
}
}
$pager_links = new LinkCollection([]);
$offset = $paginator_metadata->pageLocation;
$size = $paginator_metadata->pageSizeMax;
if ($size <= 0) {
$cacheability = (new CacheableMetadata())
->addCacheContexts([
'url.query_args:page',
]);
throw new CacheableBadRequestHttpException($cacheability, sprintf('The page size needs to be a positive integer.'));
}
$query = (array) $this->request->query
->getIterator();
if ($has_next_page) {
$next_url = static::getRequestLink($this->request, static::getPagerQueries('next', $offset, $size, $query));
$pager_links = $pager_links
->withLink('next', new Link(new CacheableMetadata(), $next_url, 'next'));
if (!empty($total_count)) {
$last_url = static::getRequestLink($this->request, static::getPagerQueries('last', $offset, $size, $query, $total_count));
$pager_links = $pager_links
->withLink('last', new Link(new CacheableMetadata(), $last_url, 'last'));
}
}
if ($offset > 0) {
$first_url = static::getRequestLink($this->request, static::getPagerQueries('first', $offset, $size, $query));
$pager_links = $pager_links
->withLink('first', new Link(new CacheableMetadata(), $first_url, 'first'));
$prev_url = static::getRequestLink($this->request, static::getPagerQueries('prev', $offset, $size, $query));
$pager_links = $pager_links
->withLink('prev', new Link(new CacheableMetadata(), $prev_url, 'prev'));
}
return $pager_links;
}
protected static function getPagerQueries($link_id, $offset, $size, array $query = [], $total = 0) {
$extra_query = [];
switch ($link_id) {
case 'next':
$extra_query = [
'page' => [
'offset' => $offset + $size,
'limit' => $size,
],
];
break;
case 'first':
$extra_query = [
'page' => [
'offset' => 0,
'limit' => $size,
],
];
break;
case 'last':
if ($total) {
$extra_query = [
'page' => [
'offset' => (ceil($total / $size) - 1) * $size,
'limit' => $size,
],
];
}
break;
case 'prev':
$extra_query = [
'page' => [
'offset' => max($offset - $size, 0),
'limit' => $size,
],
];
break;
}
return array_merge($query, $extra_query);
}
protected static function getRequestLink(Request $request, $query = NULL) {
if ($query === NULL) {
return Url::fromUri($request
->getUri());
}
$uri_without_query_string = $request
->getSchemeAndHttpHost() . $request
->getBaseUrl() . $request
->getPathInfo();
return Url::fromUri($uri_without_query_string)
->setOption('query', $query);
}
}