1- # A drop in query builder for Laravel models.
1+ # Query Builder for Laravel
22
3- Add a query builder and report table for youir models.
3+ Drop-in Livewire components for building query builders, tables, and reports on Eloquent models.
44
55## Installation
66
7- You can install the package via composer:
8-
97``` bash
108composer require act-training/query-builder
119```
1210
13- Optionally, you can publish the views using
11+ Optionally, publish the views:
1412
1513``` bash
1614php artisan vendor:publish --tag=" query-builder-views"
1715```
1816
19- ## Usage
20- Models should use the AppliesCriteria trait.
17+ ## Model Setup
18+
19+ Models should use the ` AppliesCriteria ` trait:
2120
2221``` php
23- class User extends Authenticatable
22+ use ACTTraining\QueryBuilder\Support\Criteria\AppliesCriteria;
23+
24+ class Employee extends Model
2425{
2526 use AppliesCriteria;
26-
27- ...
28-
2927}
3028```
3129
32- Create a Livewire component and make sure it extends QueryBuilder.
30+ ## Components
31+
32+ The package provides three main abstract Livewire components to extend:
33+
34+ | Component | Purpose |
35+ | ---| ---|
36+ | ` QueryBuilder ` | Full query builder with criteria-based AND/OR filtering |
37+ | ` TableBuilder ` | Simpler table with URL-persisted filters and search |
38+ | ` ReportBuilder ` | Dynamic column selection, save/load/export, optional groupBy |
39+
40+ ### QueryBuilder
41+
42+ Create a Livewire component that extends ` QueryBuilder ` . Define ` query() ` , ` columns() ` , and ` conditions() ` :
3343
3444``` php
3545<?php
3646
37- namespace App\Http\ Livewire;
47+ namespace App\Livewire;
3848
39- use ACTTraining\QueryBuilder\QueryBuilder;use ACTTraining\QueryBuilder\Support\Columns\BooleanColumn;use ACTTraining\QueryBuilder\Support\Columns\Column;use ACTTraining\QueryBuilder\Support\Columns\DateColumn;use ACTTraining\QueryBuilder\Support\Conditions\BooleanCondition;use ACTTraining\QueryBuilder\Support\Conditions\DateCondition;use ACTTraining\QueryBuilder\Support\Conditions\TextCondition;use App\Models\Employee;use Illuminate\Database\Eloquent\Builder;
49+ use ACTTraining\QueryBuilder\QueryBuilder;
50+ use ACTTraining\QueryBuilder\Support\Columns\BooleanColumn;
51+ use ACTTraining\QueryBuilder\Support\Columns\Column;
52+ use ACTTraining\QueryBuilder\Support\Columns\DateColumn;
53+ use ACTTraining\QueryBuilder\Support\Conditions\BooleanCondition;
54+ use ACTTraining\QueryBuilder\Support\Conditions\DateCondition;
55+ use ACTTraining\QueryBuilder\Support\Conditions\TextCondition;
56+ use App\Models\Employee;
57+ use Illuminate\Database\Eloquent\Builder;
4058
41- class EmployeesReport extends QueryBuilder
59+ class EmployeesTable extends QueryBuilder
4260{
4361 public function config(): void
4462 {
@@ -47,6 +65,11 @@ class EmployeesReport extends QueryBuilder
4765 ->rowClickable(false);
4866 }
4967
68+ public function query(): Builder
69+ {
70+ return Employee::query()->with(['contract', 'contract.job', 'contract.location']);
71+ }
72+
5073 public function columns(): array
5174 {
5275 return [
@@ -74,20 +97,218 @@ class EmployeesReport extends QueryBuilder
7497 DateCondition::make('Start Date', 'contract.start_date'),
7598 ];
7699 }
100+ }
101+ ```
102+
103+ ### TableBuilder
104+
105+ A simpler alternative using filters instead of criteria. Define ` query() ` , ` columns() ` , and ` filters() ` :
106+
107+ ``` php
108+ <?php
109+
110+ namespace App\Livewire;
111+
112+ use ACTTraining\QueryBuilder\TableBuilder;
113+ use ACTTraining\QueryBuilder\Support\Columns\Column;
114+ use ACTTraining\QueryBuilder\Support\Filters\TextFilter;
115+ use ACTTraining\QueryBuilder\Support\Filters\SelectFilter;
116+ use App\Models\Employee;
117+ use Illuminate\Database\Eloquent\Builder;
118+
119+ class EmployeesList extends TableBuilder
120+ {
121+ public function query(): Builder
122+ {
123+ return Employee::query();
124+ }
125+
126+ public function columns(): array
127+ {
128+ return [
129+ Column::make('Name', 'full_name')->sortable()->searchable(),
130+ Column::make('Department', 'department')->sortable(),
131+ ];
132+ }
133+
134+ public function filters(): array
135+ {
136+ return [
137+ TextFilter::make('Name', 'full_name'),
138+ SelectFilter::make('Department', 'department')
139+ ->options(['HR' => 'HR', 'IT' => 'IT', 'Finance' => 'Finance']),
140+ ];
141+ }
142+ }
143+ ```
144+
145+ ### ReportBuilder
146+
147+ Extends ` QueryBuilder ` with dynamic column selection. Users pick columns at runtime from ` availableColumns() ` , and the component builds columns and conditions automatically:
148+
149+ ``` php
150+ <?php
77151
152+ namespace App\Livewire;
153+
154+ use ACTTraining\QueryBuilder\ReportBuilder;
155+ use App\Models\Employee;
156+ use Illuminate\Database\Eloquent\Builder;
157+
158+ class EmployeesReport extends ReportBuilder
159+ {
78160 public function query(): Builder
79161 {
80162 return Employee::query()->with(['contract', 'contract.job', 'contract.location']);
81163 }
82164
83- public function rowClick($row ): void
165+ public function availableColumns( ): array
84166 {
85- $this->dispatchBrowserEvent('notify', ['content' => 'The row was clicked', 'type' => 'success']);
167+ return [
168+ 'Employee' => [
169+ ['label' => 'Name', 'key' => 'full_name'],
170+ ['label' => 'Email', 'key' => 'email'],
171+ ],
172+ 'Contract' => [
173+ ['label' => 'Job Title', 'key' => 'contract.job.name'],
174+ ['label' => 'Location', 'key' => 'contract.location.name'],
175+ ['label' => 'Start Date', 'key' => 'contract.start_date', 'type' => 'date'],
176+ ['label' => 'Salary', 'key' => 'contract.salary', 'type' => 'number'],
177+ ['label' => 'Line Manager', 'key' => 'contract.line_manager', 'type' => 'boolean'],
178+ ],
179+ ];
180+ }
181+ }
182+ ```
183+
184+ Column definitions in ` availableColumns() ` support these keys:
185+
186+ | Key | Description |
187+ | ---| ---|
188+ | ` label ` | Display label (required) |
189+ | ` key ` | Column key, supports dot-notation for relationships (required) |
190+ | ` type ` | Column type: ` text ` (default), ` number ` , ` float ` , ` boolean ` , ` date ` , ` enum ` , ` null ` , ` view ` |
191+ | ` sortable ` | Enable sorting on this column |
192+ | ` justify ` | Alignment: ` left ` , ` center ` , ` right ` |
193+ | ` view ` | Custom Blade component for rendering |
194+ | ` options ` | Options array for ` enum ` type |
195+ | ` skipCondition ` | Exclude from query builder conditions |
196+
197+ ### ReportBuilder with GroupBy
198+
199+ Enable groupBy to allow users to group results by a column with aggregate functions (COUNT, SUM, AVG, MIN, MAX). Set ` $enableGroupBy = true ` :
200+
201+ ``` php
202+ <?php
203+
204+ namespace App\Livewire;
205+
206+ use ACTTraining\QueryBuilder\ReportBuilder;
207+ use App\Models\Employee;
208+ use Illuminate\Database\Eloquent\Builder;
209+
210+ class EmployeesGroupReport extends ReportBuilder
211+ {
212+ public bool $enableGroupBy = true;
213+
214+ public function query(): Builder
215+ {
216+ return Employee::query()->with(['contract', 'contract.job', 'contract.location']);
86217 }
87218
219+ public function availableColumns(): array
220+ {
221+ return [
222+ 'Employee' => [
223+ ['label' => 'Name', 'key' => 'full_name'],
224+ ['label' => 'Department', 'key' => 'department'],
225+ ],
226+ 'Contract' => [
227+ ['label' => 'Job Title', 'key' => 'contract.job.name'],
228+ ['label' => 'Location', 'key' => 'contract.location.name'],
229+ ['label' => 'Salary', 'key' => 'contract.salary', 'type' => 'number'],
230+ ],
231+ ];
232+ }
88233}
89234```
90235
236+ When ` enableGroupBy ` is true, the report editor UI shows additional controls:
237+
238+ - ** Group By Column** - select which column to group by
239+ - ** Function** - aggregate function (COUNT, SUM, AVG, MIN, MAX)
240+ - ** Aggregate Column** - which column to aggregate (defaults to ` id ` , numeric columns from ` availableColumns() ` are included automatically)
241+
242+ An "Aggregate" column is automatically appended to the table when grouping is active.
243+
244+ You can customise the available aggregate functions and groupable/aggregatable columns by overriding:
245+
246+ ``` php
247+ public function aggregateFunctions(): array
248+ {
249+ return ['COUNT', 'SUM', 'AVG'];
250+ }
251+
252+ public function groupableColumns(): array
253+ {
254+ // Return a flat array of ['label' => ..., 'key' => ...] items
255+ return [
256+ ['label' => 'Department', 'key' => 'department'],
257+ ['label' => 'Location', 'key' => 'contract.location.name'],
258+ ];
259+ }
260+
261+ public function aggregatableColumns(): array
262+ {
263+ return [
264+ ['label' => 'ID', 'key' => 'id'],
265+ ['label' => 'Salary', 'key' => 'contract.salary'],
266+ ];
267+ }
268+ ```
269+
270+ ## Columns
271+
272+ All column types use a fluent ` make($label, $key) ` constructor. If ` $key ` is omitted, it defaults to ` Str::snake($label) ` .
273+
274+ | Column Type | Description |
275+ | ---| ---|
276+ | ` Column ` | General text column |
277+ | ` BooleanColumn ` | Boolean/checkbox display |
278+ | ` DateColumn ` | Date formatting with ` ->format() ` and ` ->humanDiff() ` |
279+ | ` ViewColumn ` | Custom Blade view rendering |
280+
281+ Columns support: ` ->sortable() ` , ` ->searchable() ` , ` ->justify('right') ` , ` ->component('view.name') ` , ` ->reformatUsing(callable) ` , ` ->withSubTitle(callable) ` , ` ->hideIf(condition) ` , ` ->hideHeader() ` .
282+
283+ ## Conditions (QueryBuilder)
284+
285+ Used with ` QueryBuilder ` and ` ReportBuilder ` for criteria-based filtering:
286+
287+ | Condition Type | Operations |
288+ | ---| ---|
289+ | ` TextCondition ` | equals, not_equals, contains, starts_with, ends_with |
290+ | ` NumberCondition ` | equals, not_equals, greater_than, less_than, is_between |
291+ | ` FloatCondition ` | equals, not_equals, greater_than, less_than, is_between |
292+ | ` BooleanCondition ` | is_true, is_false |
293+ | ` DateCondition ` | equals, before, after, is_between |
294+ | ` EnumCondition ` | equals, not_equals (with defined options) |
295+ | ` NullCondition ` | is_set, is_not_set |
296+
297+ ## Filters (TableBuilder)
298+
299+ Used with ` TableBuilder ` for simpler key/operator/value filtering:
300+
301+ ` TextFilter ` , ` NumberFilter ` , ` DateFilter ` , ` BooleanFilter ` , ` SelectFilter ` , ` NullFilter `
302+
303+ ## Relationship Support
304+
305+ Dot-notation keys work throughout columns, conditions, and filters to traverse Eloquent relationships:
306+
307+ ``` php
308+ Column::make('Job Title', 'contract.job.name')
309+ TextCondition::make('Location', 'contract.location.name')
310+ ```
311+
91312## Testing
92313
93314``` bash
0 commit comments