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

Filter Bundle Laravel Package

gns/filter-bundle

gns/filter-bundle provides reusable filter definitions for Symfony and Doctrine, letting you apply request-based criteria to query builders and listings. It keeps search and filtering logic centralized, configurable, and out of controllers.

View on GitHub
Deep Wiki
Context7

Getting Started

Reality check before anything else: gns/filter-bundle is a Symfony bundle, not a Laravel package. It ships no ServiceProvider, no facade, no config publish tag, and its service wiring is done through Symfony's DependencyInjection extension + bundle registration in config/bundles.php. composer require gns/filter-bundle inside a Laravel app will install the source but give you nothing usable — the container won't boot its services, and any filter that assumes Doctrine's QueryBuilder/EntityManager has no analogue in Eloquent. Treat this as a reference implementation to port, not a dependency to install.

Where to look first, in this order:

  1. README.md — the only documentation. Confirm whether filters are declared via annotations/attributes, YAML config, or explicit service definitions.
  2. src/ — the filter base class/interface and any DependencyInjection/ extension. This is the actual spec you'll be reimplementing.
  3. composer.json — check require for symfony/http-kernel, doctrine/orm, and the PHP constraint. That tells you how much of the design is Doctrine-shaped.

First use case to target: an index endpoint that currently hand-rolls query-string handling, e.g. GET /api/orders?status=paid&created_after=2024-01-01&sort=-total. That is exactly the problem class this bundle addresses, and it's the one where a port pays for itself immediately.

Minimal path to a working equivalent in Laravel:

// app/Filtering/Filter.php
interface Filter
{
    public function key(): string;           // query param name
    public function apply(Builder $q, mixed $value): void;
}
// app/Filtering/FilterPipeline.php
final class FilterPipeline
{
    /** @param Filter[] $filters */
    public function __construct(private array $filters) {}

    public function apply(Builder $query, array $input): Builder
    {
        foreach ($this->filters as $filter) {
            if (array_key_exists($filter->key(), $input) && $input[$filter->key()] !== '') {
                $filter->apply($query, $input[$filter->key()]);
            }
        }
        return $query;
    }
}

Then bind a per-model filter set and call it from the controller. That's the whole idea, and it's ~50 lines.

Implementation Patterns

Pattern 1 — Port the filter objects, keep the ergonomics. The bundle's value is the shape of the API (declarative filters, allowlisted keys, composable). Mirror it with one Filter class per concern rather than one giant switch:

final class StatusFilter implements Filter
{
    public function key(): string { return 'status'; }
    public function apply(Builder $q, mixed $v): void {
        $q->whereIn('status', Arr::wrap($v));
    }
}

Register them per-resource via a small factory or a FilterSet class, and resolve through the container so filters can take dependencies (e.g. a Closure table for permission-scoped filtering).

Pattern 2 — Translate Symfony-style config into Laravel config. If the bundle declares filters in YAML, mirror that in config/filters.php and resolve class names from it. This keeps the "declare, don't code" benefit while staying inside Laravel's config cache.

Pattern 3 — Query-string → FormRequest → FilterPipeline. Validate the keys, not just the values. Build the allowed key list from the registered filters so the two can never drift:

$allowed = collect($filters)->map->key()->all();
$input   = $request->only($allowed);
return $pipeline->apply(Order::query(), $input)->paginate();

Pattern 4 — Anti-corruption layer. If you already run a Symfony service alongside Laravel, expose the filtered result as JSON from Symfony and consume it in Laravel. Do not try to share the bundle's container with Laravel's — the two DI models don't bridge cleanly.

Sorting and pagination: these almost always live alongside filtering. Implement them as separate, equally-small concerns (Sort, PerPage) with hardcoded allowlists of sortable columns. Never let a raw ?sort= value reach orderBy().

Integration tips:

  • Put filters under app/Filtering/ and bind them with a #[Singleton]/service provider keyed by resource name, so app(FilterPipeline::class.':orders') is unnecessary — instead use a FilterRegistry that returns pipelines by key.
  • Add a php artisan make:filter stub early; it's the difference between a pattern and a convention.
  • Test filters directly against an in-memory SQLite connection — one test per filter, asserting the generated SQL or the result set. This is far cheaper than feature-testing every endpoint.

Gotchas and Tips

  • It is effectively unmaintained. Last release 2020-02-08, 1 star, 0 dependents. There is no upgrade path, no issue triage, and no Laravel adapter. Any bug you hit is yours to fix, in a codebase you don't own.
  • Doctrine assumptions are the biggest porting hazard. If the filters operate on Doctrine\ORM\QueryBuilder, method names (andWhere, setParameter, expr()->in()) won't exist on Eloquent's Builder. You must rewrite the body of every filter even if you keep the interface.
  • Annotation-era code. A 2020 bundle very likely uses doctrine/annotations or docblock-based filter declarations. On PHP 8.2+ with modern Symfony, those are deprecated or removed — another reason not to run it directly.
  • Filtering is an injection surface. Unvalidated filter keys let a client probe columns (?password_like=...), and unvalidated values let them scan via LIKE. Always: allowlist keys, cast/validate values, and never interpolate column names from request input.
  • LIKE filters need wildcard discipline. Escape % and _ in user input or a search for 100% will match everything.
  • Watch N+1 after filtering. Filters that touch relations (?customer.country=DE) silently turn into joins or lazy loads. Assert query count in tests with DB::listen or Laravel's QueryLog.
  • Pagination + filtering must share state. If your client sends ?page=5&status=paid, changing status must reset page, or users get empty pages. Handle it in the request-normalisation step, not the view.
  • Debugging: log the final SQL and bindings for any filtered request behind a flag. Because the filter pipeline is dynamic, "why is this row missing" is almost always a filter silently applying a default (e.g. status = 'active' when the parameter is absent). Make implicit defaults explicit and documented.
  • Extension points worth preserving from the original design: a stable Filter interface, a registry keyed by resource, and a single place where the allowlist is assembled. Everything else in the bundle is Doctrine glue you should discard.
  • Verdict: worth ~2 hours of reading src/ for API inspiration; not worth a composer require in a Laravel app. The concept is sound and small — reimplement it, don't adopt it.
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.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons