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

Api Filters Bundle Laravel Package

isma/api-filters-bundle

A Laravel package for clean, query-string-driven API filtering. Turn request parameters into Eloquent queries with reusable filters, sorting, and pagination—keeping controllers thin and endpoints consistent. Simple to configure, easy to extend, and built for real-world REST APIs.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

Architecture fit

  • The core pattern — resolve HTTP query parameters into typed filter objects, then apply them to a query builder via interchangeable strategy classes — remains a clean, well-understood approach. It maps directly to the problem most CRUD APIs eventually hit: ad-hoc if ($request->has('status')) chains accumulating in controllers.
  • v1.3.0 does not change the central framing issue: this is a Symfony bundle, not a Laravel package. A Symfony bundle depends on symfony/http-kernel, symfony/dependency-injection, and symfony/config, and is activated through AppKernel/bundles.php registration plus a Configuration tree. Laravel has no equivalent activation path, so the bundle's wiring layer remains inert under Laravel even if Composer installs it cleanly. Only the concept and possibly some framework-agnostic classes are portable.
  • The strategy pattern implies a tagged-service / registry model — idiomatic Symfony DI. Laravel's container uses service providers and contextual binding; the registration mechanics do not translate, only the object graph does.
  • The query builder is still the decisive unknown. If strategies target Doctrine ORM's QueryBuilder (andWhere, setParameter, expr()), they are not usable against Eloquent's Builder. v1.3.0 gives us indirect evidence that the coupling is Doctrine-flavored rather than abstract: ilike / ieq / inoteq are operator names that map almost one-to-one onto PostgreSQL semantics and onto Doctrine's Comparison/expression helpers. If the core were builder-agnostic, we would expect a documented operator-enum extension point instead of hardcoded additions. Still an inference, not a confirmed fact — but it shifts the prior toward Doctrine-specific.
  • What v1.3.0 does change is the operator surface. Three case-insensitivity operators are now part of the public query syntax. That is not a cosmetic addition: it changes how clients express filters, it introduces collation/dialect-dependent behavior, and it expands the set of SQL patterns a client can induce. Any reimplementation of the pattern must now decide, explicitly, whether to mirror these operators — and if it does, it inherits the portability problem described below.

Integration feasibility

  • Unchanged: low-to-moderate for a Laravel target. The realistic path is not "install and configure" but "extract and adapt": vendor the strategy interface and resolver contracts, reimplement appliers over Eloquent.
  • The surface area is still small — a resolver, a strategy contract, a handful of concrete filters, and a config block — so a reimplementation is measured in days, not months. v1.3.0 adds three operators, which nudges the estimate up but not materially.
  • The case-insensitive operators add a real portability wrinkle that did not exist at v1.2.1. ilike is native PostgreSQL. On MySQL it must be emulated with LOWER(col) LIKE LOWER(?), which is a function call on the column and therefore non-sargable — it defeats the index. ieq has the same problem, though it is usually benign because equality on a small-cardinality indexed column is cheap regardless. inoteq (case-insensitive not-equal) is worst-case: it is a negation, which is inherently low-selectivity and now also non-indexable. A Laravel target on MySQL will silently inherit a performance cliff if these operators are exposed without review, and nothing in the release notes suggests dialect awareness was considered.
  • Adoption evidence has marginally improved but remains thin: two merged PRs in the v1.3.0 window show the maintainer is committing, but both are from the same single author and dependent count is still effectively zero. Treat it as an actively-tended reference implementation, not a dependency.
  • Release metadata dating (the earlier 2026-03-19 anomaly) should still be reconciled against the actual tag history, though the existence of a coherent v1.2.1 → v1.3.0 compare link is a mild positive signal on repository hygiene.

Technical risk

  • Single-maintainer / bus factor 1. Unchanged, and v1.3.0 reinforces it: both feature PRs are authored by the same person who owns the repo. There is no second committer, no reviewer, no dependent community to absorb a break.
  • Injection surface — now measurably larger. Filters map request keys onto column names and operators. v1.3.0 adds three operators to whatever set was previously parseable. If operator selection is driven by the request payload rather than an allowlist, the attack surface for column enumeration, unauthorized field access, and expensive-query injection grew in this release. The inoteq negation operator is the specific concern: negations force the planner away from index scans on large tables, making it the cheapest possible client-side lever for a costly query. Verify the allowlist before trusting the request-to-column mapping — the question is now more urgent than it was.
  • New: dialect semantics are an implicit API contract. ilike means "case-insensitive match" — but how it means that is database-dependent. If the product runs PostgreSQL in one environment and MySQL in another, the same filter string produces different result sets and different query plans. That is a correctness risk, not just a performance one, and it is invisible until someone compares outputs across environments.
  • Semantic drift. HTTP query syntax (?filter[status][eq]=active, comma-separated IN, nested relations, and now ilike/ieq/inoteq) becomes a public API contract the moment a client integrates. Changing it later — or declining to support an operator a client has already adopted — is a breaking change against your own consumers.
  • Framework mismatch cost is real but bounded, and unchanged by this release. The main risk is still a team planning a "drop-in" integration that stalls mid-sprint.

Key questions

  1. Is there a framework-agnostic core package, or is the query-builder coupling Doctrine-specific throughout? (v1.3.0 raises the probability of the latter — operators appear hardcoded rather than registered through an extension point.)
  2. Does the package enforce an allowlist of filterable fields and operators, or does it accept arbitrary keys and operators from the request?
  3. New: how are ilike / ieq / inoteq compiled for non-PostgreSQL platforms? Is there dialect detection, a documented MySQL/ SQLite strategy, or an unstated PostgreSQL assumption?
  4. What is the failure contract for unknown or malformed filter parameters — exception, silent ignore, or 400?
  5. Are there tests, CI, and static analysis covering the new operators? Were the v1.3.0 PRs reviewed by anyone other than the author?
  6. What are the PHP and Symfony version constraints, and does the maintainer publish a support/deprecation policy?
  7. New: do the new operators come with any guidance on indexability, or does the package make it trivially easy to expose a non-sargable filter to production?
  8. Is any part of our estate actually on Symfony, or PostgreSQL, or both? That combination is the only case where adopting the artifact directly is defensible.

Integration Approach

Stack fit

  • Direct fit: Symfony 5/6/7 applications using Doctrine ORM and PostgreSQL. Adopt as intended; v1.3.0's operators are native there.
  • Degraded fit: Symfony/Doctrine on MySQL or SQLite. Still adoptable, but audit the case-insensitive operators for index usage before exposing them, and consider removing them from the allowlist.
  • Poor fit: Laravel 10/11/12 with Eloquent. Installing the bundle yields Composer-installed code that Laravel never boots. The value is the pattern, not the artifact.
  • Neutral: Laravel with Doctrine ORM (via laravel-doctrine). Here the query-builder layer may genuinely apply, and only the DI/config wiring needs replacing with a service provider. Still the highest-leverage adoption scenario and still the one to evaluate first.

Migration path

Ranked, with the tradeoff stated:

  1. Adopt as-is — only if the target service is Symfony/Doctrine. Zero porting cost; accept the maintenance risk.
  2. Bridge — implement the package's strategy contract in a Laravel service provider, adapt Eloquent behind the contract. Preserves upstream interfaces for future updates; adds an indirection layer. v1.3.0 demonstrates the contract will grow (three operators in one minor release), which cuts both ways: you get features free, but your adapter must track them.
  3. Reimplement the pattern — extract resolvers and strategy classes into an internal package. Highest control, lowest dependency risk, and given the small surface area this is usually the rational choice at zero dependents. Cost revision: budget an extra half-day to decide the operator set deliberately, including whether ilike/ieq/inoteq are in scope and, if so, how they compile on the databases you actually run.
  4. Do nothing — if filtering needs are limited to two or three fixed parameters, a strategy framework is over-engineering. Be honest about this; v1.3.0's added operators are irrelevant if you never needed the first three.

Compatibility

  • Verify PHP constraint against the runtime matrix; verify Symfony constraint against any Symfony components already pulled in transitively by Laravel (Laravel uses several Symfony components, so version conflicts are plausible and will surface as Composer solver failures, not runtime bugs).
  • Confirm PSR-7/PSR-15 usage if the resolver touches HTTP abstractions — Laravel uses its own Request, so a PSR-based resolver needs a thin adapter.
  • Check the minor-version bump itself. v1.2.1 → v1.3.0 is semver-correct for additive features, but additive operators are still a behavioral change if operator parsing is permissive: a client that was previously sending ?filter[name][ilike]=x and being silently ignored will now get filtered results. Audit whether any existing consumer sends unrecognized operators before bumping.
  • Pin the version explicitly. No floating constraints on a zero-dependent package — and note that this release is a concrete demonstration that upstream moves.

Sequencing

  1. Spike (2-3 days, unchanged): clone, read the strategy and resolver contracts, determine query-builder coupling, run the test suite. Add: check how ilike/ieq/inoteq are compiled and whether the operator set is extensible via config or hardcoded. Output: a go/no-go on bridging versus reimplementing, plus a decision on operator scope.
  2. Contract design: define the filterable-field allowlist, the operator allowlist, and the public query syntax before writing code. This is the durable decision; the implementation is replaceable. v1.3.0 is evidence that the operator set will keep growing — write the contract so that adding an operator is a deliberate act on your side, not an automatic inheritance.
  3. Proof (1 sprint): implement three or four real filters against the actual model, including a relation filter and one case-insensitive filter, and benchmark against the current hand-rolled code. The case-insensitive case is the one most likely to reveal an index problem; benchmark it on production-shaped data, not a fixture table.
  4. Policy layer: allowlist (fields and operators), operator validation, pagination caps, and error contract wired in before any external consumer touches it.
  5. Rollout behind a flag: enable per-endpoint, not globally. Compare response shapes and query counts against the existing implementation.
  6. Deprecate the ad-hoc filtering code once the new path is proven on the endpoints that matter.

Operational Impact

Maintenance

  • Budget for patch ownership. At zero dependents, upstream fixes arrive on the maintainer's schedule, not yours. v1.3.0 is a genuine positive signal — the project is not abandoned — but it does not change the structure of the risk, only the odds.
  • If bridging, every upstream interface change is a potential break in your adapter, and v1.3.0 shows the pace of change is real. If reimplementing, you own roughly 500-1000 lines but eliminate the dependency. For a component this small, ownership is often cheaper than tracking.
  • Pin to an exact version and require a deliberate upgrade decision, with a changelog review, per bump. For feature releases like this one, the review must include: which operators were added, and do we want them.

Support

  • No vendor, no SLA, no community of scale. Support is entirely internal. Two merged PRs by the repo owner does not constitute a support channel.
  • Mitigate by documenting the extracted pattern internally and keeping the adopted surface minimal — the fewer upstream classes you depend on, the less there is to support.
  • Maintain a short decision record explaining why the package was adopted or adapted, so the next engineer does not re-litigate it. Include the operator-scope decision; it is the part most likely to be questioned later.

Scaling

  • Strategy resolution is O(number of filters) per request — negligible. The scaling risk is entirely in the generated SQL.
  • Relation filters remain the danger zone: each one can introduce a join. N relation filters can produce combinatorial join explosions and duplicate rows requiring DISTINCT, which defeats index usage.
  • ilike and ieq are non-sargable on MySQL/SQLite by construction. They wrap the column in LOWER(), so no index is used on that predicate regardless of how well the column is indexed. On a large table this converts an index seek into a full scan. inoteq compounds the problem: a negation is already low-selectivity, and a case-insensitive negation cannot use an index at all. Treat these three operators as opt-in per field, not on by default, and only enable them where the table is small or the column is already in a functional index.
  • On PostgreSQL — the assumed target — ILIKE has the same index limitation unless a pg_trgm GIN index exists. The operator being "native" does not make it fast. Do not conflate "supported" with "indexed."
  • Unbounded IN lists (client sends 5,000 IDs) and unbounded page sizes remain the classic denial-of-service vectors. Cap both at the policy layer, not in the strategy.
  • Filters must map to indexed columns. Without a review step, the framework makes it trivially easy to expose an unindexed — or, as of v1.3.0, an inherently unindexable — column as filterable and discover it under production load.
  • Watch query count per request: filters that trigger lazy relation loading turn a single query into N+1.

Failure modes

  • Unknown parameter: silently ignored (client thinks filtering worked, gets wrong data) or thrown (500 on a typo). Neither is acceptable — require an explicit 400 with a message.
  • Unknown operator — newly relevant. At v1.2.1 a client sending ilike was sending garbage. After v1.3.0 the same request returns filtered results. If the upgrade is applied without a consumer audit, previously-ignored parameters silently become active predicates. This is the most likely way v1.3.0 causes a production incident: not a crash, a quietly different result set.
  • Type coercion: ?price[gte]=abc reaching the query builder produces a database error that surfaces as a 500 and may leak schema details in the response. Validate and reject at the resolver boundary.
  • Operator injection: if operators are parsed from the request rather than from an allowlist, clients can request LIKE '%...', negations, or case-insensitive scans that force full table reads. The v1.3.0 additions enlarge the set of expensive operators available to an attacker by three.
  • Cross-environment inconsistency: the same filter returns different results on PostgreSQL versus MySQL, and performs differently again. If staging and production differ in database, filtering bugs will reproduce in one place and not the other.
  • Unauthorized field access: a filter on internal_notes or deleted_at is both an information leak and a business-logic bypass. The allowlist must be the default, with explicit opt-in per field.
  • Contract break on upgrade: a minor-version bump changes parameter parsing and silently alters results for existing clients. v1.3.0 is exactly this shape — additive, semver-legal, behaviorally meaningful. Pin and test.
  • Cache key explosion: if filter query strings are used in cache keys, arbitrary parameter combinations blow up the cache. Normalize before keying — and note that the operator set, and therefore the key space, just grew.

Ramp-up

  • The pattern itself is a few hours of learning for any mid-level engineer — strategy objects are familiar.
  • The real ramp-up cost is the Doctrine-to-Eloquent translation if bridging, plus the discipline of defining the field allowlist, operator allowlist, and query contract up front. Estimate one to two days of onboarding for a developer new to the codebase, plus the spike.
  • v1.3.0 adds a small but genuine learning burden that did not exist before: someone on the team must understand why case-insensitive operators interact badly with indexes, or the team will enable them everywhere and learn it from a slow-query log. That is an hour of explanation and a permanent line in the review checklist.
  • Because the concept is simple, the internal documentation burden is low — which remains a further argument that reimplementation is cheap and the external dependency buys little.

Bottom line: v1.3.0 is a legitimate, semver-correct feature release from an actively-committing maintainer, and it modestly improves the maintenance-risk picture. It does not change the core recommendation: the design idea is worth adopting, the artifact is not, for a Laravel target. The new operators add a decision the previous assessment did not have to make — whether case-insensitive filtering is in scope at all, and how it compiles on the databases you actually run — and they widen the operator-injection surface enough that the allowlist requirement moves from "verify" to "blocking." Recommend the same two-to-three day spike, now explicitly tasked with determining query-builder coupling and operator extension mechanics, defaulting to reimplementing the strategy pattern as an internal package with a deliberately chosen operator set.

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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource