timgws/query-builder-parser
QueryBuilderParser is a Laravel/PHP package that converts jQuery QueryBuilder rules into safe SQL or MongoDB queries using a whitelist of allowed fields. It integrates with Laravel's query builder and jenssegers/mongodb for filtering and search.
Architecture fit. QueryBuilderParser is a translator, not a framework: it compiles a jQuery-QueryBuilder JSON rule tree into an Illuminate\Database\Query\Builder that the caller still owns, constrains, and executes. That is the least invasive possible insertion point for a filtering capability — it sits between the HTTP boundary and the query builder already in use, and yields a chainable object rather than results. It fits naturally where read paths are already DB::table(...)->where(...)->get() and where the filterable surface is a flat or near-flat column set. It fits poorly where read paths are expressed through Eloquent relationship graphs, global scopes, soft deletes, or row-level tenancy policies, because the parser emits raw column predicates against a single builder with no awareness of model-level scoping. The JoinSupportingQueryBuilderParser variant extends the fit to correlated exists / not exists subqueries via an explicitly declared join map — that covers a large class of multi-table filtering, but it is configuration-driven, not relationship-aware inference.
Integration feasibility. A proof of concept is genuinely a day or less: instantiate with a column allowlist, pass the posted rules plus a builder into parse(), call get(). The contract is one class, one method, one array. The DataTables pattern is documented end to end on both sides (client getRules() serialization and fnServerParams injection), which removes the usual ambiguity around the frontend half of the first integration. What is not in the package is everything that converts a prototype into a shippable feature: caps on rule-tree depth and node count, per-field operator allowlisting, in-list cardinality limits, value type coercion, connection-level statement timeouts, translation of parser exceptions into user-facing validation errors, and audit logging of filter usage. Those should be scoped as deliverable work items, not dismissed as glue.
Technical risk. Three risks dominate the assessment.
WHERE or an unbounded set of correlated subqueries, which is a database availability vector. Wrapper-level limits plus a per-connection statement timeout are prerequisites to any customer-facing exposure, not follow-ups.Two secondary risks: semantic portability across engines (LIKE, case sensitivity, date handling differ), and a stale MongoDB branch — jenssegers/mongodb has been superseded by mongodb/laravel-mongodb, so the Mongo path should be treated as a separate integration with its own evaluation rather than a free option.
Key questions.
whereHas) or against computed/aggregated values? The parser does not shorten that path.Stack fit. Adopt when the stack is Laravel on Illuminate Database and filtering is implemented as controller → query builder → get(). The release history is the strongest positive signal available: 1.6.3 added Laravel 10, 2.1.0 added Laravel 11, and 2.2.0 tracks Laravel 12 and 13, with a May 2026 release date. Maintaining parity across consecutive framework majors is the single most important compatibility property for a Laravel utility package, and it has been sustained. Out-of-Laravel use via Illuminate Database standalone is technically supported but is not a path worth taking — the value proposition is already thin, and standalone use forfeits the framework-level features (container, config, testing helpers) that make the wrapper cheap to build.
Migration path. Greenfield: introduce the package behind an internal FilterService that owns allowlist resolution, rule validation, and exception mapping, and never let controllers call the parser directly. That indirection is what makes the eventual fork-and-own or replacement decision a one-file change. Brownfield: identify endpoints that already accept ad-hoc filter parameters and consolidate them onto the parser one endpoint at a time, preserving existing parameter names during transition so clients are unaffected. Do not attempt a big-bang replacement of bespoke filter code.
Compatibility. Verify three things before committing: the package major matches the Laravel major in use (the 1.x → 2.x line follows framework versions); MySQL/MariaDB/Postgres LIKE and collation semantics for the operators actually exposed (this is where cross-environment surprises appear, particularly begins_with/ends_with and case sensitivity); and date handling, since 1.5 introduced automatic Carbon conversion for SQL builders, which interacts with timezone configuration. MongoDB should be descoped from the initial integration entirely unless a Mongo datastore is a hard requirement — and if it is, budget a separate spike against mongodb/laravel-mongodb.
Sequencing.
in cardinality cap, type coercion, exception-to-4xx mapping, statement timeout at the connection, structured logging of rule payloads and generated SQL.Maintenance. Low code churn, meaningful ownership burden. The upstream repository changes infrequently and follows framework majors, which is a light dependency-management load. The recurring cost is our allowlist: it must be reviewed whenever schema or product requirements change, and it must be tested for drift. Budget roughly a small, fixed recurring allocation for dependency bumps plus allowlist review, and treat the wrapper service as first-class owned code with its own tests. Pin the version; upgrade deliberately alongside Laravel upgrades, not automatically.
Support. No vendor, no SLA, no support contract — GitHub issues only. Internal support means our team owns any defect, including SQL-generation bugs. Two concrete mitigations: (a) a fork under our organization with a documented patch-and-release procedure, and (b) a characterization test suite that pins generated SQL for a broad operator/matrix so an upstream bump or an internal patch is verified against known-good output. Both are cheap and both convert an uncontrolled external dependency into a controlled one.
Scaling. The parser is CPU-trivial — it builds a query object, it does not execute queries. Scaling cost lives entirely in the generated SQL: unbounded in lists become expensive IN (...) clauses, deeply nested OR groups defeat index selection, and the join-supporting variant can produce correlated subqueries that are orders of magnitude more expensive than a flat predicate on a large table. Mitigate with list-length caps, a maximum nesting depth, query-plan review for the specific endpoints exposed, and a statement timeout as the backstop. Because the parser is stateless and per-request, there is no horizontal scaling concern on the PHP side.
Failure modes. Enumerate them explicitly:
LIKE behavior. Mitigate by running integration tests against the production engine.Ramp-up. Negligible for a Laravel developer: the API is one constructor and one method, and the README's DataTables example is directly adaptable. The learning curve is not the library — it is the wrapper's rules (which operators are permitted per field, coercion policy, caps, error semantics) and the frontend's JSON contract. Expect roughly a day for a mid-level Laravel engineer to be productive, plus a short pairing session to hand over the allowlist ownership and the drift-detection test. Document the wrapper's contract in the team wiki, not the package's; the package README is sufficient for its own surface.
How can I help you explore Laravel packages today?