makaveli / laravel-query-builder
Query Builder for Laravel
Requires
- php: ^8.2
- laravel/framework: ^10.10|^11.0|^12.0
- makaveli/laravel-core: 1.0.3
Requires (Dev)
- mockery/mockery: ^1.5
- orchestra/testbench: ^8.0
- phpunit/phpunit: ^10.0
README
🌍 Languages
- 🇺🇸 English (default)
- 🇷🇺 Русская версия
Table of Contents
- Introduction
- Installation
- Configuration
- Core Components
- Quick Start
- Integration with BaseRepository
- Recommendations
- Useful Links
Introduction
makaveli/laravel-query-builder is a powerful and convenient query builder for Laravel Eloquent. It allows you to extract all filtering, sorting, and pagination logic into separate classes that extend BaseQueryBuilder. This approach keeps controllers and repositories clean while making the filtering code readable, extensible, and safe.
Key goals of the library:
- Readability – each
$this->apply...()method describes one clear condition. - Security – parameters are only taken from the passed
$paramsarray. - Extensibility – adding a new filter is easy by implementing
FilterInterface. - Reusability – a single filter class can be used in different contexts (admin panel, API, reports).
- Type Safety – all methods are strictly typed, and built‑in type checks (
CheckTypes) prevent errors.
The library integrates closely with makaveli/laravel-core. It requires PHP 8.2+ and Laravel 10+.
Installation
-
Install the library via Composer:
composer require makaveli/laravel-query-builder
-
(Optional) Publish the configuration file if you want to change the default settings:
php artisan vendor:publish --tag=query-builder-config
This will create the file
config/query-builder.phpin your project.
Configuration
The file config/query-builder.php contains the following settings:
return [ // Date and time formats supported by CheckTypes 'check-types' => [ 'date-formats' => ['Y-m-d', 'd.m.Y', 'm/d/Y', 'd F Y', 'Y-m-d H:i:s'], 'time-formats' => ['H:i', 'H:i:s', 'H:i:s.u'] ], // Pagination key mapping for PaginatedCollection 'pagination_map' => [ 'current_page' => 'current_page', 'first_page_url' => 'first_page_url', 'from' => 'from', 'last_page' => 'last_page', 'last_page_url' => 'last_page_url', 'links' => 'links', 'next_page_url' => 'next_page_url', 'path' => 'path', 'per_page' => 'per_page', 'prev_page_url' => 'prev_page_url', 'to' => 'to', 'total' => 'total', ], ];
You can change the supported date/time formats and the pagination response structure according to your API requirements.
Core Components
BaseQueryBuilder
An abstract base class from which all concrete filter classes inherit. It holds an instance of $query (Eloquent Builder) and the array $params (request parameters). It contains many protected methods for applying filters (e.g., applyInteger(), applyLike(), applyWhereHasWhere(), applyDateRange(), etc.).
A detailed description of all methods with examples is provided in a separate document: base-query-builder.md.
Filterable Trait
The trait is added to a model and allows calling filters via the static filter() method:
trait Filterable { public static function filter(array $params): mixed { $query = static::query(); $filterClass = static::getFilterClass(); if (class_exists($filterClass)) { return new $filterClass($query, $params); } return $query; } abstract public static function getFilterClass(): string; }
The model must implement getFilterClass(), returning the fully qualified class name of the filter class.
CheckTypes
The helper class QueryBuilder\Helpers\CheckTypes contains static methods for checking data types. It is used internally by filters to ensure that a passed value matches the expected type (integer, float, string, date, time, array, etc.). This improves security and prevents query errors.
PaginatedCollection
The class QueryBuilder\Resources\PaginatedCollection extends the standard ResourceCollection and makes it easy to adapt the paginated response structure to frontend requirements. It uses the configuration key pagination_map to rename response fields.
Usage example:
use QueryBuilder\Resources\PaginatedCollection; return new PaginatedCollection( $paginator, ArticleResource::collection($paginator, ['author']) );
The result will contain the keys defined in pagination_map, and the actual data will be inside the data key.
DTOs for Complex Parameters
The package provides several DTO classes for passing complex parameters to filters:
AvailableSortandAvailableSorts– for describing allowed sorts on fields of related tables.DeepWhereHasWhereParamandDeepWhereHasWhereParams– for building nestedwhereHasconditions.
These DTOs are used in the methods sortByRelationField() and applyDeepWhereHasWhere().
Filters
All filters reside in the namespace QueryBuilder\Filters and are organized into categories:
| Category | # of Filters | Example Methods / Features |
|---|---|---|
| Datetime | ~20 | applyToday(), applyLastWeek(), applyDateRange(), applyCurrentHour(), applyTimeStartEnd(), applyLastMonth() |
| Numeric | ~15 | applyNumericRange(), applyMultipleOf(), applyEvenNumeric(), applyArrayInteger(), applyNumericNot(), applyNumericNotIn() |
| String | ~10 | applyLike(), applyLikeStart(), applyLikeEnd(), applyRegex(), applyFullTextSearch() |
| Logic | ~8 | applyNull(), applyTrue(), applyFalse(), applyNotNull(), applyEmpty() |
| Relation | ~10 | applyDeepWhereHasWhere(), applyCrossUponCrossWhereHasWhere(), applyWhereHasLikeArray() |
| Combine | ~8 | applyWhereHasWhere(), applyWhereHasLike(), applyWhereHasNull() |
| System | ~12 | applyLimit(), applySelect(), applyDistinct(), with(), withCount(), inRandomOrder(), applyWhereNot(), applyWhereNotIn() |
| Geo | 2 | applyGeoRadius(), applyGeoBoundingBox() |
| Special | 4 | applyIpAddress(), applyRating(), applyStock(), applyDomain() |
| Custom | 2 | applyArchived(), applyWhereNotMe() |
A complete list of all filters with detailed descriptions, parameters, and examples can be found in base-query-builder.md.
Quick Start
1. Create a filter class
<?php namespace App\Modules\Article\Filters; use Illuminate\Pagination\LengthAwarePaginator; use QueryBuilder\BaseQueryBuilder; class ArticleFilters extends BaseQueryBuilder { public function list(): LengthAwarePaginator { // Eager loading $this->with(['author', 'category']); // Count related records $this->withCount(['comments', 'likes']); // Search across multiple fields $this->applyLike(['title', 'content'], 'search'); // Filter by integer field $this->applyInteger('category_id'); // Filter by boolean field $this->applyBoolean('is_published'); // Show deleted records if requested $this->applyWithDeleted(); // Sorting $this->sortBy(['title', 'created_at'], 'created_at'); // Pagination with support for all-rows return $this->applyPaginate(canAllRows: true); } }
2. Add the Filterable trait to the model
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; use QueryBuilder\Traits\Filterable; class Article extends Model { use Filterable; public static function getFilterClass(): string { return \App\Modules\Article\Filters\ArticleFilters::class; } }
3. Use it in a controller or repository
public function index(Request $request): JsonResponse { $filters = Article::filter($request->all()); $articles = $filters->list(); // Returns a LengthAwarePaginator return ApiResponse::success($articles); }
Integration with BaseRepository
The makaveli/laravel-core package provides a BaseRepository with a getPaginatedList() method that can easily be adapted to work with filters:
use Core\Repositories\BaseRepository; use Illuminate\Contracts\Pagination\LengthAwarePaginator; class ArticleRepository extends BaseRepository { public function __construct() { parent::__construct(Article::class); } }
Now you can pass a DTO containing the request parameters to the repository’s getPaginatedList method, and the repository will automatically apply all filters, sorts, and pagination as defined in your filter class’s list method.
Recommendations
- Always extend
BaseQueryBuilderfor each module/entity. - Use
applyOrWhereGrouped()to combine conditions intoORgroups – this improves readability and correctly groups complex expressions. - Explicitly list allowed sorting fields in
sortBy()for security. - When working with soft deletes, call
applyWithDeleted()orapplyOnlyDeleted()depending on the context. - For complex relationship filters, use the ready‑made methods like
applyWhereHasWhere(),applyWhereHasLike(), etc. – they already check for the presence of the parameter and apply the filter correctly. - Create custom filters if the built‑in ones are insufficient – just implement
FilterInterfaceand register them in your filter class. - For APIs, use
PaginatedCollectionto unify the pagination response format.
Useful Links
- Library repository: https://github.com/Ma1kaveli/laravel-query-builder
- Documentation for BaseQueryBuilder methods: base-query-builder.md
- Core utilities and DTOs (recommended): makaveli/laravel-core
Good luck with your projects and happy filtering! 🚀