Skip to content

Instantly share code, notes, and snippets.

@lpj145
Created January 1, 2021 01:50
Show Gist options
  • Save lpj145/dd251bceed38ea711ebef50005dccd00 to your computer and use it in GitHub Desktop.
Save lpj145/dd251bceed38ea711ebef50005dccd00 to your computer and use it in GitHub Desktop.
<?php
declare(strict_types=1);
namespace Api2Bundle\Service;
use Doctrine\ORM\AbstractQuery;
use Doctrine\ORM\QueryBuilder;
use Doctrine\ORM\Tools\Pagination\Paginator;
use Symfony\Component\HttpFoundation\ParameterBag;
use Symfony\Component\HttpFoundation\Request;
class PaginateService
{
const DEFAULT_PAGE_SIZE = 20;
/**
* @var QueryBuilder
*/
private $query;
private function __construct(QueryBuilder $query) {
$this->query = $query;
}
public function paginate(Request $request, ?callable $hydrateCallback = null): array
{
$params = $request->query;
if ($params->has('no_page')) {
return is_callable($hydrateCallback)
? $hydrateCallback($this->mountData())
: $this->mountData();
}
$currentPage = $params->get('page', 1);
$pageSize = $params->get('size', self::DEFAULT_PAGE_SIZE);
if (!is_numeric($currentPage)) {
$currentPage = 1;
}
if (!is_numeric($pageSize)) {
$pageSize = self::DEFAULT_PAGE_SIZE;
}
$this->query->setFirstResult($pageSize * ($currentPage - 1));
$this->query->setMaxResults($pageSize);
$data = $this->mountData();
return [
'data' => is_callable($hydrateCallback) ? $hydrateCallback($data) : $data,
'meta' => $this->mountMeta(
$this->getBaseUrl($request),
$params,
count($data),
(int)$currentPage,
(int)$pageSize
)
];
}
protected function mountData(): array
{
return $this->query
->getQuery()
->getResult(AbstractQuery::HYDRATE_ARRAY);
}
protected function mountMeta(
string $baseUrl,
ParameterBag $params,
int $itemsCount,
?int $currentPage = 1,
?int $pageSize = 0
): array
{
$paginator = new Paginator($this->query);
$resultCount = $paginator->count();
$lastPage = (int)ceil($resultCount / $pageSize);
$lastPage = $lastPage > 0 ? $lastPage : 1;
$toRecordsNumber = $this->calculateToRecordNumber($resultCount, $pageSize, $currentPage, $lastPage);
$nextPageUrl = null;
$prevPageUrl = null;
if ($resultCount > $pageSize && $currentPage < $lastPage) {
$nextPageUrl = $this->getUrlWithPageQuery($baseUrl, $params->all(), $currentPage + 1);
}
if ($currentPage > 1 && $resultCount > $pageSize) {
$prevPageUrl = $this->getUrlWithPageQuery($baseUrl, $params->all(), $currentPage - 1);
}
return [
'total'=> $resultCount,
'per_page' => $pageSize,
'current_page' => $currentPage,
'last_page' => $lastPage,
'first_page_url' => $this->getUrlWithPageQuery($baseUrl, $params->all(), 1),
'last_page_url' => $this->getUrlWithPageQuery($baseUrl, $params->all(), $lastPage),
'next_page_url' => $nextPageUrl,
'prev_page_url' => $prevPageUrl,
'items_count' => $itemsCount,
'from' => $this->calculateFromRecordNumber($itemsCount, $toRecordsNumber),
'to' => $toRecordsNumber
];
}
private function calculateFromRecordNumber(int $itemsCount, int $toNumber): int
{
return $toNumber === 0 ? 0 : $toNumber === $itemsCount ? 1 : $toNumber - $itemsCount;
}
private function calculateToRecordNumber($resultCount, $pageSize, $currentPage, $lastPage): int
{
return $currentPage === $lastPage
? $resultCount
: $pageSize * $currentPage;
}
private function getBaseUrl(Request $request): string
{
return $request->getUriForPath($request->getPathInfo());
}
private function getUrlWithPageQuery(string $url, array $params, int $page): string
{
$params['page'] = $page;
return $url.'?'.http_build_query($params);
}
public static function factory(QueryBuilder $query): PaginateService
{
return new static($query);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment