Skip to content

Commit fe98bc5

Browse files
docs(readme): document or and negated like variants
1 parent 6bd8b78 commit fe98bc5

1 file changed

Lines changed: 45 additions & 1 deletion

File tree

README.md

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ There is nothing else to do. No service provider to register, no config to publi
3434

3535
`LikeOperator::applyContains()` adds a wildcard safe `LIKE` (or `ILIKE` on PostgreSQL) clause to a query.
3636

37-
`applyContains()` and `applyContainsOnDate()` type hint `Illuminate\Database\Eloquent\Builder`. They can be called on an Eloquent builder, and inside a closure that Laravel hands one, such as the closure passed to `Eloquent\Builder::where()`. They cannot be called on a plain `Illuminate\Database\Query\Builder`, or inside a closure that receives one (for example the closure passed to `orWhereIn()`, `whereExists()` or `Query\Builder::from()`). Accepting both builder types is a known limitation, deferred to a later release.
37+
`applyContains()`, its OR and negated variants, and `applyContainsOnDate()` type hint `Illuminate\Database\Eloquent\Builder`. They can be called on an Eloquent builder, and inside a closure that Laravel hands one, such as the closure passed to `Eloquent\Builder::where()`. They cannot be called on a plain `Illuminate\Database\Query\Builder`, or inside a closure that receives one (for example the closure passed to `orWhereIn()`, `whereExists()` or `Query\Builder::from()`). Accepting both builder types is a known limitation, deferred to a later release.
3838

3939
```php
4040
use Illuminate\Database\Eloquent\Builder;
@@ -49,6 +49,50 @@ Movie::query()
4949

5050
`applyContains()` wraps the column through the query's grammar, picks the operator with `LikeOperator::for()`, builds the pattern with `LikeOperator::containsPattern()` and issues one `whereRaw()` call with the correct `ESCAPE` clause for the driver. `applyContainsOnDate()` does the same thing but first casts the date column to text in the right dialect (`::text` on PostgreSQL, `CAST(... AS CHAR)` on MySQL and MariaDB, `CAST(... AS TEXT)` elsewhere), for matching a partial date, month or year that is displayed rather than compared.
5151

52+
### OR and negated variants
53+
54+
`applyContains()` accepts two optional arguments that mirror Laravel's own `whereLike()`: `$boolean` (`'and'` by default, or `'or'`) and `$not` (`false` by default). `applyContainsOnDate()` accepts the same two. For readability `applyContains()` also has three named shortcuts:
55+
56+
| Method | Clause added |
57+
| --- | --- |
58+
| `applyContains($query, $column, $term)` | `and <column> LIKE ?` |
59+
| `orApplyContains($query, $column, $term)` | `or <column> LIKE ?` |
60+
| `applyNotContains($query, $column, $term)` | `and <column> NOT LIKE ?` |
61+
| `orApplyNotContains($query, $column, $term)` | `or <column> NOT LIKE ?` |
62+
63+
On PostgreSQL the operator is `ILIKE` or `NOT ILIKE`. Escaping and the `ESCAPE` clause are identical in all four. Any `$boolean` other than `'and'` or `'or'` (case insensitive) throws an `InvalidArgumentException`.
64+
65+
An OR clause joins whatever came before it in the same `where` group, so wrap OR clauses in a closure to keep them from leaking into the rest of the query:
66+
67+
```php
68+
use Illuminate\Database\Eloquent\Builder;
69+
use PlinCode\SqlDialect\LikeOperator;
70+
71+
Job::query()
72+
->where(function (Builder $query) use ($keywords) {
73+
foreach ($keywords as $keyword) {
74+
LikeOperator::orApplyContains($query, 'title', $keyword);
75+
}
76+
})
77+
->where(function (Builder $query) use ($excluded) {
78+
foreach ($excluded as $keyword) {
79+
LikeOperator::applyNotContains($query, 'title', $keyword);
80+
}
81+
})
82+
->get();
83+
```
84+
85+
`NULL` follows SQL's three valued logic and the package does not change it. `NULL LIKE '%x%'` and `NULL NOT LIKE '%x%'` both evaluate to `NULL`, so a row whose column is `NULL` is left out by the negated variants as well as by the positive ones. When those rows should be kept, add the null check yourself:
86+
87+
```php
88+
$query->where(function (Builder $query) {
89+
$query->whereNull('location');
90+
LikeOperator::orApplyNotContains($query, 'location', 'onsite');
91+
});
92+
```
93+
94+
### Escaping
95+
5296
`containsPattern()` (and the `escapeWildcards()` it calls) neutralise `%`, `_` and `\` in the search term with `addcslashes()`, so a term containing those characters is matched literally instead of being interpreted as a wildcard. That is why `applyContains()` always appends an `ESCAPE` clause: it tells the driver which character in the pattern is the escape character it just used.
5397

5498
`$column` is interpolated straight into the raw SQL through the connection's grammar and must be a column name your own code supplies, never request input; `$term`, the search value, is always passed as a bound parameter. `Grammar::wrap()` quotes identifiers, it does not validate or escape arbitrary strings, so it is not a safeguard against passing user input as `$column`.

0 commit comments

Comments
 (0)