You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: README.md
+50-13Lines changed: 50 additions & 13 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,6 +1,6 @@
1
1
# Laravel Query Strategies
2
2
3
-
A package to help build queries with Eloquent Builder from URL parameters in a request.
3
+
Build safe, flexible API endpoints by turning URL query parameters into Eloquent queries. Define a strategy that controls exactly which filters, sorts, fields, and relationships your API consumers can use.
@@ -14,31 +14,68 @@ A package to help build queries with Eloquent Builder from URL parameters in a r
14
14
- PHP ^8.5
15
15
- Laravel ^13.0
16
16
17
-
## Why this package is helpful?
17
+
## Quick Example
18
18
19
-
If you want to apply query clauses to Eloquent Models using parameters passed by the user, then this package will allow you to create strategies that will enable them to be applied automatically.
19
+
Given a `ProductStrategy` that defines which parameters are allowed:
20
20
21
-
Using query strategies you can define what properties a user can have access to offering a safer way for them interact with your data schemas.
That single line handles filtering, sorting, field selection, eager loading, accessor appending, limiting, and pagination — all controlled by the strategy.
55
+
56
+
## Why Use This?
57
+
58
+
-**Safe by default** — only parameters defined in your strategy are applied. Unknown parameters are ignored (or rejected in strict mode).
59
+
-**Column obfuscation** — map public parameter names to real database columns so your schema stays private.
60
+
-**Flexible clauses** — 17 built-in filter clauses (equals, contains, between, greater than, etc.) with operator overrides via URL.
61
+
-**Relationship support** — filter through relationships with dot notation, sort by relationship columns, and control eager loading.
62
+
-**Composable** — chain individual methods (`filter()`, `order()`, `fields()`, etc.) or call `apply()` to run everything at once.
Copy file name to clipboardExpand all lines: docs/clauses.md
+57-42Lines changed: 57 additions & 42 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,6 +1,6 @@
1
1
# Clauses
2
2
3
-
Clauses define how a filter value is applied to the Eloquent query builder. Each clause maps to a SQL WHERE condition.
3
+
Clauses define how a filter value translates into a SQL condition. When a user sends `?name=John`, the clause determines whether that becomes `WHERE name = 'John'`, `WHERE name LIKE '%John%'`, or something else entirely.
4
4
5
5
## Built-in Clauses
6
6
@@ -20,86 +20,100 @@ Clauses define how a filter value is applied to the Eloquent query builder. Each
20
20
|`IsNotInClause`|`WHERE column NOT IN (...)`|`notIn`, `!in`|
21
21
|`IsNullClause`|`WHERE column IS NULL`|`isNull`, `null`|
22
22
|`IsNotNullClause`|`WHERE column IS NOT NULL`|`isNotNull`, `!null`, `notNull`|
|`OrEqualsClause`|`OR WHERE column = value`|`or`, `\|\|`|
25
24
|`ScopeClause`| Calls a model scope | — |
25
+
|`TrashedClause`| Soft delete filtering | — |
26
26
27
-
Aliases are used in operator overrides. For example, `?name--contains=John` uses the `ContainsClause`.
27
+
## How Clauses Are Resolved
28
+
29
+
When a request comes in, the package resolves which clause to use in this order:
30
+
31
+
1. If the parameter has a `callback` defined, that runs instead of any clause.
32
+
2. If the user specifies an operator override (e.g. `?name--contains=John`), the alias is looked up in the parameter's methods and the strategy's default methods.
33
+
3. If the parameter has a `filter` or `default` clause class set, that's used.
34
+
4. If multiple values are provided, the `multi` clause is used (defaults to `IsInClause`).
35
+
5. Otherwise, the global default `EqualsClause` is used.
36
+
37
+
## Operator Overrides
38
+
39
+
API consumers can override the default clause for any parameter by appending `--` and a clause alias:
40
+
41
+
```
42
+
?name--contains=lap → WHERE name LIKE '%lap%'
43
+
?price--gte=100 → WHERE price >= 100
44
+
?price--between=10,500 → WHERE price BETWEEN 10 AND 500
45
+
?email--beginsWith=admin → WHERE email LIKE 'admin%'
46
+
?status--isNull → WHERE status IS NULL
47
+
```
48
+
49
+
This gives consumers flexibility without you needing to define separate parameters for each operation.
28
50
29
51
## Scope Clause
30
52
31
-
The `ScopeClause` calls an Eloquent local scope on the model instead of applying a direct WHERE condition. The parameter name is converted to camelCase to match the scope method name.
53
+
The `ScopeClause` calls an Eloquent local scope instead of applying a direct WHERE condition. The parameter name is converted to camelCase to match the scope method.
54
+
55
+
Say your model has:
32
56
33
57
```php
34
-
// Model scope
35
58
public function scopeStartsBefore(Builder $query, string $date): Builder
This creates `config/query-strategies.php`. If you don't publish it, the package uses sensible defaults.
12
10
13
11
## Options
14
12
@@ -27,31 +25,39 @@ return [
27
25
];
28
26
```
29
27
30
-
###Strict Mode
28
+
## Strict Mode
31
29
32
-
| Key | Default | Description |
33
-
|---|---|---|
34
-
|`strict`|`false`| When `true`, throws `InvalidFilterException` if a query parameter is not a recognised filter, system key, or operator override |
30
+
When `strict` is `true`, any query parameter that isn't a recognised filter, system key, or operator override throws an `InvalidFilterException`. This is useful for APIs where you want to catch typos or prevent consumers from guessing at filter names.
31
+
32
+
```php
33
+
'strict' => true,
34
+
```
35
+
36
+
When `strict` is `false` (the default), unknown parameters are silently ignored.
37
+
38
+
You can also enable strict mode per-request by passing it in the config array:
35
39
36
-
When strict mode is enabled, any unknown query parameter will throw an exception with a message listing the allowed filters. This is useful for APIs where you want to prevent typos or unauthorised filter attempts from silently being ignored.
40
+
```php
41
+
$filter = new Filter($builder, $strategy, $request->query->all(), ['strict' => true]);
42
+
```
37
43
38
-
###Parameter Keys
44
+
## Parameter Names
39
45
40
-
Each key maps an internal concept to the query parameter name your API exposes:
46
+
Each key maps an internal concept to the query parameter name your API exposes. Change these if your API conventions differ from the defaults:
41
47
42
-
| Key | Default |Description|
48
+
| Key | Default |What it controls|
43
49
|---|---|---|
44
-
|`order`|`order`|Parameter for specifying which column to order results by |
45
-
|`sort`|`sort`|Parameter for specifying sort direction (`asc`or`desc`) |
46
-
|`limit`|`limit`|Parameter for limiting the number of results returned|
47
-
|`page`|`page`|Parameter for pagination page number |
48
-
|`with`|`with`|Parameter for specifying relationships to eager load |
49
-
|`fields`|`fields`|Parameter for selecting specific columns|
50
-
|`append`|`append`|Reserved for future use|
50
+
|`order`|`order`|Column to sort by |
51
+
|`sort`|`sort`|Sort direction (`asc`/`desc`) |
52
+
|`limit`|`limit`|Results per page|
53
+
|`page`|`page`|Page number |
54
+
|`with`|`with`|Relationships to eager load |
55
+
|`fields`|`fields`|Columns to select|
56
+
|`append`|`append`|Model accessors to append|
51
57
52
-
## Example
58
+
## Example: Custom Parameter Names
53
59
54
-
If your API uses `sort_by` instead of `order` and `per_page` instead of `limit`:
60
+
If your API uses different conventions:
55
61
56
62
```php
57
63
return [
@@ -61,10 +67,16 @@ return [
61
67
'limit' => 'per_page',
62
68
'page' => 'page',
63
69
'with' => 'include',
64
-
'fields' => 'fields',
70
+
'fields' => 'select',
65
71
'append' => 'append',
66
72
],
67
73
];
68
74
```
69
75
70
-
Users would then query with `?sort_by=name&direction=desc&per_page=25&include=owner`.
76
+
Consumers would then query with:
77
+
78
+
```
79
+
GET /products?sort_by=name&direction=desc&per_page=25&include=category&select=id,name
80
+
```
81
+
82
+
The strategy and filter logic stays the same — only the URL parameter names change.
0 commit comments