Skip to content

Basic usage ​

The spatie-compatible API is the QueryBuilder class. It wraps an Eloquent query and applies the request parameters you allow:

php
use App\Models\User;
use MyProgs\QueryBuilder\AllowedFilter;
use MyProgs\QueryBuilder\AllowedSort;
use MyProgs\QueryBuilder\QueryBuilder;

class UserController
{
    public function index()
    {
        return QueryBuilder::for(User::class)
            ->allowedFields('id', 'name', 'email', 'posts.id', 'posts.title')
            ->allowedIncludes('posts', 'posts.comments')
            ->allowedFilters('name', AllowedFilter::exact('id'), AllowedFilter::scope('active'))
            ->allowedSorts('name', AllowedSort::field('created', 'created_at'))
            ->defaultSort('-created')
            ->paginate();
    }
}
GET /users?fields[users]=id,name&fields[posts]=title&include=posts&filter[name]=john&sort=-created

The request selects users.id and users.name for the users and posts.title for their posts. Because posts is included, posts.user_id is added to the posts query, so every user comes back with its posts loaded.

The subject ​

QueryBuilder::for() takes:

  • a model class: QueryBuilder::for(User::class);
  • an Eloquent builder: QueryBuilder::for(User::where('active', true));
  • a relation: QueryBuilder::for($team->users()).

The request is taken from the container. Pass another one as the second argument: QueryBuilder::for(User::class, $request).

Using the result ​

QueryBuilder forwards every other method to the Eloquent builder, so you can chain where(), with(), scopes, and finish with get(), first(), paginate(), simplePaginate(), cursorPaginate() and the like:

php
$users = QueryBuilder::for(User::class)
    ->allowedFilters('name')
    ->where('active', true)
    ->get();

Filters and sorts are applied when you call allowedFilters() and allowedSorts(). Fields and includes are applied right before the first call that reads the query. See Differences from spatie.

  • getEloquentBuilder() returns the Eloquent builder with everything applied.
  • getSubject() returns the builder or relation as it was given, without the pending fields and includes.
  • clone() returns an independent copy, as clone $query does.

Errors ​

A parameter that is not allowed throws an exception that renders as a 400 response:

ExceptionWhen
InvalidFilterQueryan unknown filter
InvalidSortQueryan unknown sort
InvalidIncludeQueryan unknown include
InvalidFieldQueryan unknown field
MalformedQueryParameteran array where a string is expected

All of them extend MyProgs\QueryBuilder\Exceptions\InvalidQuery, an HttpException. Unknown filters, sorts and includes can be ignored instead, see Configuration.

Reading the request ​

QueryBuilderRequest gives the parsed parameters as collections, for code that needs them outside a query:

php
use MyProgs\QueryBuilder\QueryBuilderRequest;

$request = QueryBuilderRequest::fromRequest(request());

$request->filters();   // ['name' => 'john']
$request->sorts();     // ['-created']
$request->includes();  // ['posts']
$request->fields();    // ['users' => ['id', 'name'], 'posts' => ['title']]

The names and the delimiter come from the query-builder config.