povs/lister-bundle
povs/lister-bundle offers a reusable list-building layer for PHP apps, letting you define columns, filters, sorting, and pagination for query results and render consistent, configurable list views for admin panels and search pages.
The first thing to internalize: this is a Symfony bundle, not a Laravel package. There is no service provider, no facade, no config/lister.php. A Laravel developer's "day-to-day leverage" falls into two realistic tracks — (a) you maintain a legacy Symfony app alongside Laravel and want the lister there, or (b) you want to borrow the pattern and re-implement it natively in Laravel. Attempting composer require povs/lister-bundle inside a modern Laravel app is the wrong first move; its Symfony 2/3-era constraints will fight with illuminate/* and modern PHP.
Minimal steps:
composer.json first. Check PHP and symfony/* version pins. If it's ~2.3 or ~2.8, treat it as reference code, not a dependency.Lister/ (or Model/, Manager/) for the base lister class and any ListerManager. That class is the portable part — pagination, sort, filter, export logic. Everything in DependencyInjection/, Resources/config/services.yml, and the bundle class itself is Symfony wiring you won't reuse.QueryBuilder. Map that concept to Laravel's Illuminate\Database\Eloquent\Builder + LengthAwarePaginator.If you're on the Symfony side: register the bundle in AppKernel, define a lister service pointing at a Doctrine query builder, and pass request params in. If you're on Laravel: skip step 4's install entirely and port the class into app/Support/Listing/.
Adapter around the lister, not direct use. Wrap the lister behind your own interface (ListingInterface with paginate(), sort(), filter(), export()). This lets you start with ported code and swap to native Laravel pagination later without touching controllers.
Query-builder-first, params-second. The idiomatic flow is: build the base query (Doctrine QB / Eloquent builder) → hand it to the lister with a validated params object → lister applies order, offset/limit, and where-clauses → returns rows plus page metadata. Keep the params object validated before it reaches the lister.
Sorting via whitelist. Never pass a raw ?sort= column into the lister. Maintain an explicit map:
$sortable = ['name' => 'users.name', 'created' => 'users.created_at'];
$column = $sortable[$request->sort] ?? 'users.id';
$direction = $request->direction === 'desc' ? 'desc' : 'asc';
Filtering as composable clauses. Mirror the bundle's filter concept with small filter classes or when() chains. Each filter is a (Builder $q, mixed $value): Builder unit — testable independently and reusable between the lister and plain controllers.
Chunked export. Export is where naive ports explode. Always pull the unpaginated query through chunkById(1000) (Laravel) or iterate() (Doctrine) and stream the response:
return response()->streamDownload(function () use ($query) {
$out = fopen('php://output', 'w');
$query->chunkById(1000, function ($rows) use ($out) {
foreach ($rows as $row) fputcsv($out, [...]);
});
fclose($out);
}, 'export.csv');
Config-driven listers. The bundle's real value is the idea of declaring per-entity listing config (columns, sortable fields, filters, export format) once and reusing it across index/export/API endpoints. Reproduce that with a small config array or a Lister subclass per entity.
Integration tip — request decoupling. If ported code expects a Symfony Request or returns a Symfony Response, convert at the boundary. Your controller should hand in a plain array of params and receive a LengthAwarePaginator/array — nothing Symfony-shaped should leak into Laravel-land.
league/csv + fputcsv instead.bind()/singleton() wiring, and the lister may expect container-injected dependencies (entity manager, request stack) you must supply by hand.Request query strings. Extract total, perPage, currentPage into your own DTO before hitting the view.page, sort, order, limit). If your frontend uses sortBy/dir, normalize in a request-mapping layer rather than patching the lister.$qb->getQuery()->getSQL() with getDQL() for params. Laravel equivalent: ->toSql() + ->getBindings(). Most "wrong page" bugs are offset math or a missing distinct() on a join.getResult() on a large table will OOM. The bundle may not chunk for you — verify, and if it doesn't, don't trust its exporter.app/Support/Listing/ — you get the design without the dead dependency graph.How can I help you explore Laravel packages today?