chamber-orchestra/pagination-bundle
Installation:
composer require chamber-orchestra/pagination-bundle
Enable the bundle in config/bundles.php:
return [
// ...
ChamberOrchestra\PaginationBundle\PaginationBundle::class => ['all' => true],
];
First Use Case: Paginate a Doctrine query:
use ChamberOrchestra\PaginationBundle\Paginator\DoctrinePaginator;
$paginator = new DoctrinePaginator($entityManager->getRepository(Post::class)->findAll(), $request->query->getInt('page', 1), 10);
$pagination = $paginator->paginate();
Twig Rendering: Pass the pagination object to Twig:
{{ render_pagination(pagination) }}
Override default templates by copying src/Resources/views/ to your project’s templates/bundles/pagination/.
Page-Based Pagination:
$paginator = new ArrayPaginator($items, $page = 1, $perPage = 10);
$pagination = $paginator->paginate();
lengthAwarePaginate() for total item counts.Cursor (Keyset) Pagination:
$paginator = new DoctrineCursorPaginator(
$entityManager->getRepository(Post::class),
$request->query->get('cursor'),
10,
['id' => 'ASC']
);
$pagination = $paginator->paginate();
ULID for cursor generation (via ChamberOrchestra\PaginationBundle\Pagination\Cursor).Type-Based Factory:
$factory = new PaginationFactory();
$pagination = $factory->create('doctrine', $queryBuilder, $page, $perPage);
array, doctrine, query.OptionsResolver Integration:
Configure paginator options via Symfony’s OptionsResolver:
# config/packages/pagination.yaml
chamber_orchestra_pagination:
default_per_page: 20
max_per_page: 100
Override globally or per-paginator:
$paginator->setOptions(['per_page' => 50]);
Doctrine QueryBuilder:
$qb = $entityManager->createQueryBuilder()
->select('p')
->from(Post::class, 'p')
->orderBy('p.createdAt', 'ASC');
$pagination = $factory->create('doctrine', $qb, $page, $perPage);
Custom Twig Extensions:
Extend the PaginationExtension to add custom rendering logic:
use ChamberOrchestra\PaginationBundle\Twig\PaginationExtension;
class CustomPaginationExtension extends PaginationExtension {
public function getCustomLinks(PaginationInterface $pagination) { ... }
}
Register as a service and tag it with twig.extension.
API Responses: Serialize pagination metadata for APIs:
return [
'data' => $pagination->getItems(),
'meta' => [
'current_page' => $pagination->getCurrentPage(),
'per_page' => $pagination->getPerPage(),
'total' => $pagination->getTotalItems(),
'last_page' => $pagination->getLastPage(),
],
];
Cursor Pagination Quirks:
ORDER BY id ASC) to avoid inconsistent cursors.$cursor = $pagination->getCursor();
$nextCursor = $pagination->getNextCursor();
Doctrine Paginator Pitfalls:
join() or fetch('array') to avoid lazy-loading issues.count() for large datasets:
$paginator->setOptions(['count' => false]);
Twig Template Overrides:
{% extends 'bundles/pagination/pagination.html.twig' %}
php bin/console cache:clear
Performance:
setOptions(['count' => false]) for read-heavy APIs.$paginator->setLogger($logger);
$paginator->validateOptions();
phpstan or IDE hints (e.g., ->getItems() vs. ->getData()).Custom Paginator:
Implement PaginatorInterface:
class CustomPaginator implements PaginatorInterface {
public function paginate(): PaginationInterface { ... }
}
Register as a service:
services:
ChamberOrchestra\PaginationBundle\Paginator\CustomPaginator:
tags: ['chamber_orchestra.paginator']
Custom Cursor Strategy:
Extend CursorStrategyInterface for non-ULID cursors:
class CustomCursorStrategy implements CursorStrategyInterface {
public function generateCursor(array $items): string { ... }
public function parseCursor(string $cursor): array { ... }
}
Modify Pagination Metadata:
Override PaginationInterface methods:
class ExtendedPagination implements PaginationInterface {
public function getTotalItems(): int { ... }
}
config/packages/pagination.yaml:
chamber_orchestra_pagination:
default_per_page: 15
max_per_page: 50
cursor_strategy: 'ulid' # or 'custom'
config/packages/translation.yaml:
imports:
- { resource: "@PaginationBundle/Resources/translations/messages.en.yaml" }
Override keys in translations/messages.en.yaml:
pagination:
previous: '« Previous'
next: 'Next »'
if ($request->query->has('cursor')) {
$pagination = $cursorPaginator->paginate();
} else {
$pagination = $pagePaginator->paginate();
}
public function handle(Request $request, callable $next) {
$request->attributes->set('pagination', $this->createPaginator($request));
return $next($request);
}
$mockPagination = $this->createMock(PaginationInterface::class);
$mockPagination->method('getItems')->willReturn([$item1, $item2]);
How can I help you explore Laravel packages today?