metaclass-nl/filter-bundle
Metaclass Filter Bundle provides reusable, query-string-driven filtering for Laravel Eloquent models. Define filters once and compose them to build clean search and listing endpoints, keeping controllers lean and filter logic consistent across your app.
Reality check first: metaclass-nl/filter-bundle is a Symfony bundle built for API Platform, not a Laravel package. There is no service provider, no config/filter-bundle.php, and no php artisan command. You "leverage" it as a Laravel developer in one of three ways: (1) you maintain a Symfony/API Platform service alongside your Laravel app and consume it over HTTP, (2) you're migrating an API Platform codebase into or out of Laravel and need to understand the filter semantics, or (3) you're porting the idea — composable boolean filter logic on query strings — into Eloquent query scopes. Everything below assumes scenario (1) or (2).
Minimal steps to get hands-on:
composer require metaclass-nl/filter-bundle in the Symfony/API Platform project (not your Laravel composer.json).config/bundles.php:
return [
// ...
Metaclass\FilterBundle\FilterBundle::class => ['all' => true],
];
README.md first, then src/ — look for the filter attribute/class and the DependencyInjection/ extension to see what services and parameters it registers. Check composer.json for the required api-platform/core and symfony/* version constraints before you wire anything.#[ApiFilter(...)] on the entity or the equivalent YAML/XML config) and hit the collection endpoint with a query string that combines two conditions using the bundle's logical operator syntax. Confirm the generated SQL/Hydra docs reflect the filter before you roll it out to other resources.If you can't install API Platform at all, the practical fallback is to read the filter logic in src/, then reproduce the query-string grammar with a Laravel Builder macro + a request validator. That's often faster than standing up Symfony purely for this.
Declarative filter attachment. The bundle is designed to be applied per-resource, the same way API Platform's built-in filters are. Prefer the attribute on the entity over YAML so the filter sits next to the model:
#[ApiResource]
#[ApiFilter(SomeFilter::class, properties: ['status', 'createdAt', 'owner'])]
class Order { /* ... */ }
Check the README for the exact filter class name and its properties semantics — the bundle's value-add is the logic layer on top of individual properties.
Logical composition in the query string. The day-to-day pattern is: front-end sends a single query string describing an arbitrary boolean tree, and the filter translates it into a WHERE (... AND ...) OR (...) clause. Keep the grammar shallow on the client (2–3 levels) and document it in your API contract; deep nested trees are where both performance and debugging fall apart.
Reuse across resources. If you have many resources with the same filterable fields (a createdAt, an owner, a status enum), define a base entity or a trait carrying the #[ApiFilter] attribute, and inherit it. This keeps the bundle configuration in one place instead of sprinkled across dozens of resources.
Polyglot integration (Laravel + API Platform). Treat the Symfony service as the filter/search backend: Laravel calls it with an HTTP client and passes through the query string. Cache the responses with Cache::remember() keyed on the normalized query string, and add a thin Laravel-side validator that whitelists filterable fields before forwarding — do not blindly proxy arbitrary query params.
Porting the pattern to Eloquent. If you're reimplementing rather than adopting, the shape is a Builder macro that parses a structured filter payload ({and: [...], or: [...]}) into where/orWhere calls, plus a scopeFilterable() that whitelists columns. Validate the tree server-side with a FormRequest; never interpolate field names from the request.
Testing. Because the output is query logic, functional tests over HTTP are more valuable than unit tests of the filter class. Assert the result set, not the generated SQL — API Platform's DBAL/SQL output changes between versions.
composer require then php artisan vendor:publish, this package will disappoint. Budget for reading source rather than expecting docs.composer.json constraints and the package's last release date against your api-platform/core version before assuming it works. A green composer require does not mean the filter is wired.src/ end-to-end once; it's small enough. Vendor-patch or fork if you hit an edge case, and pin the exact version in composer.json.filterLogic) and verify in the Hydra/OpenAPI output that it appears once.(a OR b) OR (c AND d)-style queries that bypass indexes. Enforce: max depth, whitelisted properties only, EXPLAIN the top five query shapes your front-end generates, and add composite indexes for the common combinations.X-Debug-Token) for a trace. Log the raw query string alongside the compiled clause — the mapping between them is where bugs hide.services.yaml is the clean seam; editing vendor code is not.Builder macro, ship it as an internal package, and you'll get Laravel-native validation, testing, and upgrade paths for less effort than running a second framework to host one filter.How can I help you explore Laravel packages today?