ashleydawson/simple-pagination-bundle
Install the package:
composer require ashleydawson/simple-pagination-bundle
Register the bundle in config/bundles.php (Symfony 4/5):
return [
// ...
AshleyDawson\SimplePaginationBundle\AshleyDawsonSimplePaginationBundle::class => ['all' => true],
];
(For Symfony 3, add to AppKernel.php as shown in the README.)
Basic usage with an array:
$paginator = $this->container->get('ashley_dawson_simple_pagination.paginator');
$items = ['item1', 'item2', ..., 'item12'];
$paginator
->setItemTotalCallback(fn() => count($items))
->setSliceCallback(fn($offset, $length) => array_slice($items, $offset, $length));
$pagination = $paginator->paginate($request->query->getInt('page', 1));
Render in Twig:
{% for item in pagination.items %}{{ item }}{% endfor %}
{{ simple_pagination_render(pagination, '_route_name', 'page', app.request.query.all) }}
COUNT(*) in setItemTotalCallback.setSliceCallback with setFirstResult() and setMaxResults().$query = $entityManager->createQueryBuilder()
->select('u')
->from('App\Entity\User', 'u')
->where('u.active = :active')
->setParameter('active', true);
$paginator
->setItemTotalCallback(fn() => $query->select('COUNT(u.id)')->getQuery()->getSingleScalarResult())
->setSliceCallback(fn($offset, $length) => $query->setFirstResult($offset)->setMaxResults($length)->getQuery()->getResult());
$paginator
->setItemsPerPage($request->query->getInt('per_page', 10))
->setPagesInRange($request->query->getInt('pages_in_range', 5));
config/packages/ashley_dawson_simple_pagination.yaml:
ashley_dawson_simple_pagination:
defaults:
items_per_page: 20
pages_in_range: 3
config/services.yaml:
services:
App\Service\AdminPaginator:
class: AshleyDawson\SimplePagination\Paginator
calls:
- [setItemsPerPage, [50]]
- [setPagesInRange, [10]]
public function __construct(private Paginator $paginator) {}
AshleyDawsonSimplePaginationBundle:Pagination:default.html.twig.{{ simple_pagination_render(pagination, 'route_name', 'page', app.request.query.all, 'App:Pagination:custom.html.twig') }}
return $this->json([
'data' => $pagination->items,
'meta' => [
'total' => $pagination->totalItems,
'page' => $pagination->currentPage,
'last_page' => $pagination->lastPage,
],
]);
Query Reuse:
setItemTotalCallback and setSliceCallback (e.g., COUNT vs. SELECT *).$countQuery = clone $query->select('COUNT(u.id)');
$sliceQuery = clone $query->select('u');
Offset Calculation:
items_per_page isn’t aligned with database limits (e.g., MySQL’s LIMIT).($page - 1) * $itemsPerPage explicitly in callbacks.Caching:
setItemTotalCallback may hit the database twice (once for count, once for slice) if not optimized.getSingleScalarResult() efficiently.Symfony 5+ Deprecations:
get() method is deprecated in favor of getContainer()->get().autowire: true in services.Twig Function Parameters:
simple_pagination_render fails if routeName doesn’t exist or pageParameterName is misconfigured.{% if app.request.query.get('page') is not null %}
{{ simple_pagination_render(pagination, 'valid_route', 'page') }}
{% endif %}
setItemTotalCallback and setSliceCallback are called correctly:
->setItemTotalCallback(fn() => (int)($count = $query->getQuery()->getSingleScalarResult()) && $this->logger->debug('Total items:', ['count' => $count]))
$pagination object to inspect:
$this->logger->debug('Pagination data:', [
'currentPage' => $pagination->currentPage,
'totalItems' => $pagination->totalItems,
'items' => count($pagination->items),
]);
totalItems = 0).totalItems <= itemsPerPage).Custom Pagination Logic:
Paginator class to add methods like paginateWithSort() or paginateWithFilters().class FilteredPaginator extends Paginator {
public function paginateWithFilter($page, array $filters) {
$this->setSliceCallback(fn($offset, $length) => $this->applyFilters($filters)->setFirstResult($offset)->setMaxResults($length)->getResult());
return $this->paginate($page);
}
}
Override Twig Templates:
AshleyDawsonSimplePaginationBundle:Pagination:default.html.twig to templates/pagination/default.html.twig to customize pagination links.aria-current to active page links.Async Pagination:
Messenger component to defer heavy pagination tasks (e.g., for admin dashboards):
$message = new PaginateUsersMessage($offset, $length);
$this->messageBus->dispatch($message);
// Return cached data while processing in background.
Integration with API Platform:
Pagination object to generate HATEOAS links or JSON:API pagination headers:
return new JsonResponse([
'data' => $pagination->items,
'links' => [
'first' => $this->generateUrl('api_users', ['page' => 1]),
'last' => $this->generateUrl('api_users', ['page' => $pagination->lastPage]),
],
]);
COUNT(*) in setItemTotalCallback for large tables. Use:
->select('COUNT(u.id)')->where('u.deleted_at IS NULL')
totalItems count if it rarely changes:
$cache = $this->container->get('cache.app');
$cacheKey = 'user_count_active';
$total = $cache->get($cacheKey, function() use ($query) {
return $query->select('COUNT(u.id)')->getQuery()->getSingleScalarResult();
});
How can I help you explore Laravel packages today?