Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Filtration Bundle Laravel Package

ite/filtration-bundle

ITE FiltrationBundle is a Symfony bundle that adds configurable filtering to Doctrine entity queries. Define text, choice, date, boolean, and relation filters, then apply them to query builders to build dynamic search forms and listings with minimal setup.

View on GitHub
Deep Wiki
Context7

Getting Started

First, sanity-check the stack. Despite how this package is often categorized, ite/filtration-bundle is a Symfony bundle wired through Symfony's DI container and targeting Doctrine. If you are on Laravel/Eloquent, you cannot composer require your way to value here — your job is to read it as a reference implementation of a declarative filter/sort/paginate contract and port the idea. The day-to-day "leverage" in a Laravel codebase is the pattern, not the dependency.

Minimal steps (Symfony/Doctrine context):

  1. Read the repo's composer.json before installing. Confirm Symfony and doctrine/orm constraints against your version. With 1 star and 0 dependents, expect stale pins — budget a half-day spike and treat a Composer constraint failure as the decision point.
  2. composer require ite/filtration-bundle, then register the bundle (config/bundles.php on Symfony 4+, or AppKernel::registerBundles() on legacy).
  3. Look first at how filter definitions are declared (YAML config / annotations on the entity / a PHP config class) and how a filter set is registered as a service. That declaration file is the actual product surface — everything else is plumbing.
  4. Find the service that receives a QueryBuilder (or ArrayCollection) and applies filters/sort/pagination. That single "apply" call is the integration point.

First use case — an admin list endpoint:

// Controller: parse nothing yourself, hand the request-derived criteria to the filter service.
$qb = $this->userRepository->createQueryBuilder('u');
$result = $this->filterApplicator->apply($qb, 'users_list_filters', $request);
// $result: items + pagination metadata (page, total, per_page)

Start with the smallest possible filter set — one exact-match field (status) and one sortable field — and confirm it against a QueryBuilder. Only then wire in a second filter set for a different endpoint to prove reuse, because reusable filter sets are the whole point of adopting it.

For the Laravel developer porting this: the first durable artifact is a FilterDefinition value object (field → type/operators/allowed) plus a ListQuery object (filters, sorts, page). Build those, then one Eloquent adapter behind them. That is a 1–2 week bounded internal package, and it removes the abandoned-dependency risk entirely.

Implementation Patterns

Declarative filter definitions per resource. Define the allowlist in config, not in the controller. Each field declares its type and the operators it accepts (eq, in, gte/lte for dates, like for text). Keep these definitions next to the entity config or in a dedicated filters.yaml — the point is that the definition is data, so it can be reviewed, diffed, and reused.

One filter set, many surfaces. The high-leverage pattern is registering a named filter set (e.g. orders_list) and reusing it across the admin UI, an export builder, and a JSON API. Same parameter names, same semantics, same pagination envelope. This is what eliminates the if ($request->has('status')) chains that drift between screens.

Apply at the query/domain layer, not the controller. Keep the controller thin:

public function index(Request $request, OrderFilterSet $filters): Response
{
    $qb = $this->orders->createQueryBuilder('o');          // query layer
    $page = $filters->apply($qb, $request->query->all());  // filter layer owns parsing
    return $this->render('orders/index.html.twig', ['page' => $page]);
}

This is what lets the same filtering be reused by a CLI export or a message handler, not just HTTP.

Dual-source capability — use it deliberately. The interesting feature is that the same filter definition applies to an ArrayCollection (in-memory) and a QueryBuilder (database). The productive workflow is:

  • Tests: build an ArrayCollection fixture, apply the filter set, assert results. No database, fast.
  • Production: the identical filter set against the QueryBuilder.

If you lean on this, keep the definition identical and the data small in tests. Do not treat it as a license to filter in memory in production (see Gotchas).

Authorization-aware filtering. The bundle has no opinion on row-level visibility. Compose it: apply a tenant/role constraint to the QueryBuilder before handing it to the filter applicator, or wrap the applicator in a decorator that injects the scope. Do not rely on the filter layer to enforce visibility — it only knows about whitelisted columns.

Custom filter logic via extension points. Where the built-in operators don't cover a case (full-text, JSON containment, computed ranges), the extensible architecture usually lets you register a custom filter type or decorate the applicator. Add these as small, tested classes rather than special-casing in controllers — otherwise you've reintroduced the duplication you adopted this to remove.

Integration tip — normalize the response shape. Wrap the applicator's output in your own pagination DTO (items, page, per_page, total, sort). This decouples your API contract from the bundle's internal return type, which matters a great deal when the package is unmaintained.

Gotchas and Tips

Framework mismatch is the headline gotcha. This is a Symfony bundle against Doctrine. In Laravel you either port the design or use a maintained, Laravel-native equivalent (spatie/laravel-query-builder for whitelisted filter/sort parsing). Do not attempt to bridge Doctrine into Eloquent — that is a rewrite wearing an install.

Never trust client-supplied field names. Filter and sort parameters are user-controlled by definition. Dynamic column interpolation into a QueryBuilder is a direct SQL-injection vector. Enforce a server-side allowlist from your filter definition and add a test that asserts unknown fields are rejected (throw, don't silently ignore). This is the single highest-severity failure mode and the easiest to miss because the abstraction looks like it handles it.

ArrayCollection filtering does not scale. It loads the whole result set into PHP memory per request. If an endpoint ever takes the in-memory path in production, cap it or eliminate it before launch. Reserve ArrayCollection for tests and small fixed sets.

Watch for silent divergence between the two sources. Same request against an ArrayCollection and a QueryBuilder can produce different results (null handling, case sensitivity, collation). This is the hardest bug class to trace. Mitigation: one code path per endpoint — don't mix.

Unindexed filter fields become full scans. The field allowlist is a security control; pair it with an index review. The moment a filterable column is exposed in the UI, ensure it's indexed.

Deep pagination degrades. LIMIT/OFFSET is fine early; plan keyset pagination for large tables, and don't let the abstraction hide that ordering cost.

Debugging: enable Doctrine's SQL logger and dump the generated DQL/SQL for a handful of representative filter combinations. Because the filter layer builds the query for you, "why did this return nothing?" is almost always a wrong operator mapping or an unexpected AND/OR grouping — reading the emitted SQL is faster than stepping through the applicator.

Config quirks: the usual failure is service registration — a filter set that isn't tagged/registered will look wired but silently ignore its filters. Verify by asserting a known filter actually narrows results, not just that the service resolves. On Symfony 4+/5, confirm the bundle is in config/bundles.php; older bundles frequently ship a DependencyInjection/Extension.php expecting legacy config locations.

Own it or skip it. With 1 star and 0 dependents, there is no security response and no compatibility cadence — every PHP/Symfony upgrade becomes your patch. If you adopt: pin a version, vendor/fork with a named internal owner, keep the applicator behind your own DTO so you can swap it, and set a sunset date. For most teams the correct move is to extract the contract and implement it in-house — the transferable idea (declarative, whitelisted, server-driven filtering applied uniformly) is worth the sprint; the package is not worth the dependency.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package