smoked/filter-manager-bundle
A Symfony bundle for building configurable, reusable filters and applying them to Doctrine queries. Define filter sets, generate filter forms, and compose query criteria through a clean, service-based API for admin lists and search screens, with extensible filter types.
The first thing to internalize: despite the description calling it "framework-agnostic core with simple integration," this ships as a Symfony bundle. In a Laravel app there is no auto-discovery, service provider, or publishable config. Your first step is not composer require — it is reading the source on GitHub.
Minimal steps
composer.json before installing. Check require for symfony/*, php, and doctrine/* constraints, and check autoload.psr-4 to see the namespace split between the bundle wrapper (...\Bundle\...) and the framework-agnostic core (...\FilterManager\...). The bundle namespace is the part you ignore; the core is what you may reuse.app/Support/FilterManager, keep the MIT header, and wire it yourself. Pin to a commit SHA rather than a floating constraint.status (equals), price (range), category (in). Define them once, compose them into a named set plp_default, and apply that set to an Eloquent builder. One day, feature-flagged.Where to look first in the source
apply()/filter() signature and what it receives: a value object, a request, or a raw query builder. This single decision determines whether the package is adaptable or a dead end for you.DependencyInjection/ extension — confirms what the bundle adds on top and therefore what you can drop.Realistic first wiring (verify names against source — the shape is what matters):
$registry = new FilterRegistry();
$registry->add('status', new EqualsFilter('status'));
$registry->add('price', new RangeFilter('price'));
$registry->add('category', new InFilter('category_id'));
$set = $registry->set('plp_default', ['status', 'price', 'category']);
$criteria = $set->fromRequest($request->query()); // or ->resolve($input)
$query = Product::query();
foreach ($criteria as $filter) {
$filter->applyToEloquent($query); // adapter you write
}
The deliverable of "getting started" here is a time-boxed spike plus a short internal note — the filter registration model, the query-construction contract, and the gaps against your catalog schema. That note is more durable than the decision to adopt or not.
Given the package's age and adoption, the patterns that matter are the ones that keep it swappable.
1. Wrap the package behind your own interface. No controller or model should touch package classes directly. Define a single seam and let the package be one implementation. This is the difference between a reversible experiment and a rewrite when the package rots.
interface ProductFilterResolver
{
public function resolve(array $input): ProductFilterSet;
}
2. Adapter pattern for the query layer. The bundle's native habitat is Doctrine. For Eloquent, write a small adapter translating each filter into a where / whereBetween / whereIn closure. Keep the adapter dumb — no business logic lives there.
3. Named sets as configuration, not code. Define filter sets (plp_default, admin_search) in a config array or table, not scattered conditionals. This is the actual value proposition — centralization — and it survives even if you drop the package later.
4. Validate input at the boundary. Allowlist every filter name and value in your resolver before it reaches the package. Treat the package's input as untrusted; it almost certainly does not validate column names or sort keys.
5. Feature-flag the whole path. You get a clean rollback and a place to A/B against the from-scratch implementation you will probably end up writing anyway.
6. Characterization tests first. Before wiring it in, pin the observable behavior: given these inputs and this set, these records come back in this order. Those tests are your safety net when you replace the package.
Integration tips
GET /products?filter[...] and for pagination/cursor interaction.The Symfony bundle wrapper is dead weight in Laravel. Don't try to boot it. *Bundle, DI extension, and service registration are irrelevant — extract the core or skip the package.
No auto-discovery, no service provider, no config publishing. You wire everything by hand and own the wiring.
Zero adoption means no known-issues map. There is no Stack Overflow answer, no issue thread, no example app. Your source reading is the documentation — write your own internal note after the spike, while it's fresh.
Query safety is unverified. A "filter manager" by definition turns user input into DB predicates. Assume column names and sort keys are interpolated unless you see an allowlist. Never pass raw request keys through.
No release cadence. Do not take a floating ^1.0 constraint. Pin to a SHA if you vendor it at all, and keep the source under version control in your repo so upstream changes remain mergeable if it revives.
Version rot. Check PHP and symfony/* constraints against your runtime. If it predates PHP 8 typed properties or uses ContainerAware, budget for patching the core — which further erodes the "just install it" value.
Testing is your responsibility. Assume no upstream suite. Your characterization tests are the only tests.
N+1 and facets. If filters touch relations (variants, attribute values), watch for N+1. If you need filter-value counts, this package will not give them — plan for a search engine or a separate aggregation query.
The failure mode to grep for is silent no-ops. Add an assertion that a non-empty filter set actually produced a WHERE clause, or log a warning when a registered filter resolves to nothing. A filter that quietly stops applying after a schema change returns unfiltered results and users may not notice.
The one extension point that matters is the filter contract. If it takes a query builder, it is opinionated and hard to swap; if it returns a value object, you can adapt it to Eloquent, Doctrine, or a search backend. Determine which before committing.
Don't gate roadmap on it. Zero stars, zero dependents, no maintainer signal — treat it as a read-only design reference, not a dependency. Under an SCA/compliance process it is more likely to be flagged than approved.
How can I help you explore Laravel packages today?