structuraphp / query-builder
Turns HTTP query strings into Eloquent queries: filters, scopes, sorting, relations and pagination
Requires
- php: ^8.4
- ext-filter: *
- laravel/framework: ^13.0
- nesbot/carbon: ^3.0
Requires (Dev)
- brianium/paratest: ^7.22
- dedoc/scramble: ^0.13
- friendsofphp/php-cs-fixer: ^3.94
- infection/infection: ^0.35
- larastan/larastan: ^3.13
- orchestra/testbench-core: ^11.0
- phpstan/phpstan: ^2.1
- phpstan/phpstan-phpunit: ^2.0
- phpstan/phpstan-strict-rules: ^2.0
- phpunit/phpunit: ^11.5|^12.5|^13.0
- rector/rector: ^2.3
- shipmonk/composer-dependency-analyser: ^1.8
- tomasvotruba/cognitive-complexity: ^1.3
Suggests
- dedoc/scramble: Documents the scalar form of the filter values (ScalarFilterValueExtension, RelationParametersExtension, NumberBoundsExtension, PaginationParametersExtension)
Provides
None
Conflicts
None
Replaces
None
README
Query Builder
Turn HTTP query strings into safe, declarative Eloquent queries.
English · Français
About
Your API lists need filters, sorting, relations and pagination. Writing them by hand in every controller is repetitive; letting the client drive the query is risky. Query Builder gives you both: the client composes its query from the URL, and you declare exactly what it may do, in one class.
-- GET /articles?title[value]=laravel&title[mode]=like&popular=1&sort[date]=desc&with=author SELECT * FROM articles WHERE articles.title LIKE '%laravel%' ESCAPE '!' AND views >= 100 ORDER BY articles.created_at DESC LIMIT 25 OFFSET 0 SELECT id, name FROM authors WHERE authors.id IN (…)
Anything not declared is rejected: the client can never filter, sort or load
a sensitive column (password). Invalid input is a Laravel validation error
(422), never a 500.
Requirements
| PHP Version | Query Builder 0.x |
|---|---|
| <= 8.3 | ✗ Unsupported |
| 8.4 / 8.5 | ✓ Supported |
- Laravel 13
- MySQL, PostgreSQL or SQLite
Quick Start
Installation
composer require structuraphp/query-builder
1. Declare the query
final class ArticleQuery implements FromModel, WithFilter, WithScope, WithSort, WithRelation, WithPaginate { public function getModel(): string { return Article::class; } public function filter(FilterInterface $filter): void { $filter->filter('title')->type(Type::String); } public function scope(ScopeInterface $scope): void { $scope->scope('popular'); // Article::scopePopular() } public function sort(SortInterface $sort): void { $sort->sort('date')->column('created_at'); $sort->default('date', Direction::Desc); } public function relation(RelationInterface $relation): void { $relation->relation('author')->fields(['id', 'name']); } public function paginate(PaginationInterface $pagination): void { $pagination->limit(25)->maxLimit(100); } }
2. Create the request
final class ArticleQueryRequest extends QueryRequest { protected function getQuery(): string { return ArticleQuery::class; } }
The validation rules are generated from the declaration.
3. Use it in a controller
public function index(ArticleQueryRequest $request) { $parameters = $request->parameters(); $query = $parameters->builder(); // an Eloquent Builder, still yours $query->where('tenant_id', $request->user()->tenant_id); return $parameters->paginate($query); }
That's it: the request above returns a paginated, filtered and sorted list of articles, with their authors.
Features
- 🔒 Declared, never guessed. The client only uses what the query class
declares. Public names stay apart from the schema (
column()), and the loaded columns of a relation are yours (fields()). - ✅ Laravel validation. Rules, messages and attributes are generated:
the client gets field-level 422 errors. Add your own rules and messages,
and validate without HTTP too (
QueryParameters::fromArray()). - 🔎 Typed filters. String, integer, float, boolean and date filters with
their operators;
likesearches escaped and case-insensitive; date comparisons by day that keep your indexes usable. - 🧩 Scopes with real arguments. Call model scopes,
#[Scope]methods or methods of your custom builder. Arguments are validated and converted from the PHP signature: enums, dates, arrays, nested structures, or your own objects through a cast. - 🔐 Server-side context. Give scope arguments from the code (the current user, ids computed by a service), never from the client, and compute them only when the scope is requested.
- ↕️ Sorting. Declared sorts on columns or relation counts, priority from the URL, default sorts.
- 🔗 Relations. The client picks the relations to eager load or count, among the declared ones.
- 📄 Pagination. Offset, cursor (stable, without count query) or none, with a limit the client cannot exceed.
- 🧱 Domain-friendly.
QueryParametersis a value object without HTTP dependency: pass it to a service, keep refining the builder. - 📘 OpenAPI ready. Scramble documents your query requests like any form request.
📖 Documentation
The usage guide details every feature with its configuration, example URLs, and the SQL or validation error they produce: filters, scopes, context, sorting, relations, pagination and OpenAPI. The PHPStan rules report configuration errors in CI.
🤖 AI agents
With Laravel Boost,
php artisan boost:install gives your AI agents the library's guidelines and
its structura-query-builder skill.
License
Query Builder is open-source software licensed under the MIT license.