nfqde/entity-list-bundle
nfqde/entity-list-bundle is a Symfony bundle for building reusable entity lists with configurable data sources, filters, sorting, pagination, and rendering helpers, simplifying admin and backend list screens.
Decide your posture before installing anything. nfqde/entity-list-bundle is a Symfony bundle over Doctrine ORM — there is no service provider, no Eloquent adapter, and no Laravel config. It is archived with 0 stars and 0 dependents. Two legitimate day-to-day uses remain:
spatie/laravel-query-builder + Eloquent scopes. This is the recommended path for a Laravel developer.If you are genuinely in a Symfony app:
composer require nfqde/entitylist-bundle
composer show nfqde/entitylist-bundle # check resolved symfony/* and doctrine/orm versions FIRST
Register the bundle in config/bundles.php (Symfony 4+). If Composer pulls a Symfony 3/4-era dependency tree, stop — the package is not viable on a modern PHP/Symfony runtime without a fork.
Where to look first, in this order: the bundle's EntityList base/abstract class, the filter classes (annotation-driven), and the example list + controller in the repo's tests or docs. That trio defines the entire contract — you will rarely need to read the rest.
First use case: a paginated admin grid over one Doctrine entity with 2–3 column filters (e.g. User filtered by email substring and status, sorted by created date). Subclass the base list, declare the repository, add filterByX() methods, wire one controller action, and confirm the paging metadata in the response.
Laravel equivalent first use case — the same screen, same day, no fork:
// app/Lists/UserList.php
final class UserList
{
public function query(Request $request): LengthAwarePaginator
{
return QueryBuilder::for(User::class)
->allowedFilters([AllowedFilter::partial('email'), AllowedFilter::exact('status')])
->allowedSorts(['created_at'])
->defaultSort('-created_at')
->paginate(min((int) $request->integer('per_page', 25), 100))
->withQueryString();
}
}
That is the entire reusable core of the bundle — the rest is Symfony container wiring you would not use anyway.
Subclass-and-declare (the bundle's model). One list class per screen. You supply the entity/repository and filter definitions; the bundle handles request parsing, paging, and serialization. Resist building a god-list; a class that filters users and orders and invoices becomes untestable within a sprint.
Filters are declarative and request-driven. Configure the filter on the list class (historically via annotation plus an optional form/filter type), add a filterByX(QueryBuilder $qb, $value) method, and the HTTP layer is handled for you. Day-to-day work is: add a property, add the annotation, add the method, ship.
Two output shapes, one controller action.
Wrap the list in an application service. Do not inject the list directly into controllers. A UserListFactory (or UserList service) gives you a seam to swap the library out later — which matters a lot given the archived status.
// Symfony: the seam
final class UserListFactory
{
public function create(Request $request): EntityListInterface
{
return new UserList($this->em->getRepository(User::class), $request);
}
}
For Laravel on Doctrine (rare, but real). If you run laravel-doctrine/orm, wrap the bundle call in a Laravel service and normalize its output to an array/Collection. Feed it a Symfony Request explicitly, or bypass the HTTP layer entirely and drive the QueryBuilder yourself — never let the bundle's request assumptions leak into Illuminate\Http\Request.
Custom filters as classes, not conditionals. Register a custom filter class rather than stuffing branches into filterByX(). The latter is impossible to unit-test and becomes the first thing that breaks.
One integration test per list. Hit the controller and assert: row count for a given filter, paging metadata across pages 1/2/last, sort direction, and empty-result behavior. The bundle's own test suite will not protect you against a Doctrine upgrade.
Laravel-idiomatic building blocks when reimplementing:
| Bundle concern | Laravel equivalent |
|---|---|
| Declarative list definition | Eloquent local scope or dedicated List class |
| Filter parameter allowlisting | spatie/laravel-query-builder AllowedFilter |
| Sort allowlisting | AllowedSort |
| Paging | paginate() / cursorPaginator() |
| Fetch-joined display columns | with() eager loading |
| Filter/sort metadata for the grid | A toArray() contract on the list class |
"nfqde/entity-list-bundle": "x.y.*"), commit composer.lock, and write down the replacement plan on day one.doctrine/annotations deprecation and removal will bite. Any fork must convert annotations to PHP attributes before you touch PHP 8.2+.ORDER BY can error out or leak schema. Always validate against an explicit allowlist — never pass the raw parameter through.COUNT(*) over joins lies. Paging totals computed on a query with joins/filters can over- or under-count. Verify totals against the exact filtered query, especially after you add fetch-joins for performance.leftJoin/addSelect for any column rendered in the grid, or production will find it for you.per_page to something sane (e.g. 100). Unbounded page size is a cheap denial-of-service against your database.f[email]= style keys and document the contract, or you will collide with unrelated parameters.How can I help you explore Laravel packages today?