ruwork/paginator-bundle
Symfony bundle that integrates the Ruwork paginator, providing service configuration and Twig helpers for splitting result sets into pages and rendering page navigation. It makes pagination easy to wire into Symfony applications while keeping the underlying paginator library reusable.
ruwork/paginator-bundle is archived (last release 2018‑07‑22, MIT, zero dependents). Treat it as a reference implementation to read, not a dependency to adopt. If you are evaluating it for a legacy project that already has it in composer.lock, start here; otherwise skip to "Implementation Patterns" for what to copy instead.
If it is already installed:
Confirm the bundle is registered. In a Symfony 3/4 app this is a line in app/AppKernel.php or config/bundles.php:
new Ruwork\PaginatorBundle\RuworkPaginatorBundle(),
If the namespace is wrong you will get a Class not found at cache warmup, not at runtime — always run bin/console cache:clear after touching this file.
Read vendor/ruwork/paginator-bundle/composer.json first. The require block tells you the maximum supported symfony/* and php constraint, which is the single most important fact about whether the package can run on your stack.
Discover the real service surface before writing code:
bin/console debug:container --show-private | grep -i pagin
bin/console debug:container ruwork_paginator.paginator --show-arguments
Old Ruwork bundles typically expose one service plus a Page/Pagination value object and a Twig extension. The exact IDs are not documented anywhere reliable — the container is the documentation.
Look at the bundle class and its DependencyInjection/*Extension.php. That file lists every parameter you can override and every service definition, in about 60 lines. Read it before the README.
First use case to validate the package works at all: paginate a Doctrine QueryBuilder in a controller and render links in Twig. If you can get one page of results plus a correct page count out of it, the rest of the API is predictable.
Controller-side pagination (the canonical use). The bundle is designed around "give it a query/count and a current page number, get back a page of items and metadata." The current page comes from the request, and the paginator should be treated as a stateless service:
public function index(Request $request, PaginatorInterface $paginator)
{
$qb = $this->getDoctrine()->getRepository(Post::class)
->createQueryBuilder('p')
->orderBy('p.createdAt', 'DESC');
$page = $paginator->paginate($qb, $request->query->getInt('page', 1));
return $this->render('post/index.html.twig', ['page' => $page]);
}
If autowiring fails (likely — the bundle predates autoconfigure conventions), alias the interface to the concrete service in config/services.yaml:
Ruwork\Paginator\PaginatorInterface:
alias: ruwork_paginator.paginator
public: true
Query stability matters more than the API. With ORDER BY created_at DESC and no tiebreaker, page 2 will repeat or skip rows when new records land between requests. Always add a deterministic secondary sort (->addOrderBy('p.id', 'DESC')). This is the number-one bug in every paginated listing, regardless of library.
Array pagination for non-Doctrine sources. When the data comes from an API or an in-memory collection, do the slicing yourself and only use the bundle for the page-link rendering — the arithmetic (offset, page count, boundary clamping) is the trivial part, and coupling your data source to an abandoned bundle's array adapter is not worth it.
Twig rendering. The bundle ships a Twig extension for emitting page links. Preserve existing query parameters so filters survive pagination:
{{ ruwork_pagination(page, {q: app.request.query.get('q')}) }}
The exact function name will differ — run bin/console debug:twig | grep -i pagin to get it, and check the extension class for supported options before guessing.
The pattern worth stealing, if you are replacing it. The reusable idea in these bundles is the separation between pagination metadata (current page, total pages, has-next/has-previous) and the page payload. Implement that as a small readonly value object in your own code:
final class Page
{
public function __construct(
public readonly array $items,
public readonly int $number,
public readonly int $perPage,
public readonly int $total,
) {}
public function pageCount(): int { return (int) ceil($this->total / $this->perPage); }
public function hasNext(): bool { return $this->number < $this->pageCount(); }
}
That is roughly 30 lines and removes any reason to depend on this package.
"php": "^7.1" and uses Twig_Extension (the pre-namespace class removed in Twig 3). Check with composer why-not php 8.2 and composer why-not twig/twig 3. If you are on PHP 8, expect to patch the vendor directory — and vendor patches on an archived package are a maintenance debt, not a fix.debug:container and debug:twig against the installed version.Doctrine\ORM\Tools\Pagination\Paginator and whatever class this bundle exposes are easy to confuse in a use statement, and the failure mode is a confusing TypeError deep in a template. Import with an alias in controllers.templates/bundles/RuworkPaginatorBundle/... — instead of editing vendor files. If it has no template and renders markup in PHP, that is your cue to stop and write the markup yourself in Twig.?page=0, ?page=-5, and ?page=999999 are the three requests your paginator will get from crawlers within a week. Coerce to max(1, min($page, $pageCount)) before querying, or you will issue a query with a negative OFFSET and get a DB-level error.bin/console cache:clear --env=prod surfaces these; bin/console about and a single request in dev may not.babdev/pagerfanta-bundle (actively maintained, Pagerfanta 3, PHP 8 + Symfony 6/7) covers the same ground with a documented API. knplabs/knp-paginator-bundle is the other mainstream option and has a gentler template story. For pure array pagination, pagerfanta/pagerfanta alone is enough.App\Paginator, drop the bundle class in favour of an explicit services.yaml, and delete the Twig extension in favour of a Twig Component/macro. That converts an unmaintained dependency into ~200 lines you own — which is the only sustainable outcome for a package with zero dependents and no release in seven years.How can I help you explore Laravel packages today?