divineomega/laravel-natural-where
Laravel Natural Where adds expressive, human-readable where clauses to Laravel's query builder, letting you define filters in natural language instead of complex nested conditions. These simple helpers make Eloquent queries easier to write, read, and maintain in Laravel apps.
composer require as usual; the service provider is auto-discovered and registers the natural-sort macros on the query builder, so there is no manual app.php entry on modern Laravel. On the older end of the support matrix (5.6/8) check whether the package's docs still ask you to register the provider manually.Macros/QueryBuilder class). That file tells you the exact macro names, argument order, and whether column names are validated — which matters more than the README for day-to-day use.Item::query()->naturalOrWhere('name', 'like', "%{$term}%")->get() or ->naturalOrderBy('title'), giving you "Item 2" before "Item 10" instead of lexicographic ordering.composer why-not / check the constraint to confirm your Laravel version (9–12 are not named in the notes) is still inside the allowed range, and that the transitive illuminate/* constraints resolve.Group OR conditions. naturalOrWhere is an orWhere under the hood, so a bare call after an existing where will widen the result set. Wrap it:
$query->where('tenant_id', $tenant->id)
->where(function ($q) use ($term) {
$q->naturalOrWhere('name', 'like', "%{$term}%")
->naturalOrWhere('sku', 'like', "%{$term}%");
});
Chain directly now. The v3.1.0 fix makes naturalOrWhere return the builder result, so return $query->naturalOrWhere(...)->paginate(25); works. If you had a workaround ($query->naturalOrWhere(...); return $query;, or a temporary variable purely to dodge a null return), remove it — leaving it in makes the code misleading for the next reader.
Pair sorting with a tiebreaker. Natural ordering produces ties for human-identical strings ("item 2" vs "Item 2"). Add ->orderBy('id') after the natural order so pagination is deterministic and cursorPaginate doesn't skip/duplicate rows between pages.
Encapsulate in scopes. Put the multi-column search behind a local scope (scopeSearch) or a shared macro so the grouping discipline is enforced in one place rather than re-derived in every controller.
Compose with existing query builders. The macros operate on Illuminate\Database\Query\Builder, so they work inside Eloquent, DB::table(), and relations — useful for whereHas constraints where you want a natural match on a related model's column.
Test the generated SQL, not just results. Feature tests against SQLite will pass while production MySQL/Postgres differs; add ->toSql()/->getBindings() assertions for any non-trivial composition, and at least one integration test on the production driver.
LENGTH()/CAST()/REGEXP/collation tricks. ORDER BY on an expression forces a filesort; with offset pagination on large tables this degrades badly. Mitigate with a generated/stored sort column, a narrower filtered set, or cursor pagination.?sort=name), validate it against an allow-list. Binding protects values, not identifiers; the package may or may not quote identifiers for you — verify in the macro source before trusting it.LIKE is case-insensitive on default MySQL collations but case-sensitive on Postgres; SQLite comparisons differ again. Any behaviour you rely on (accent handling, case folding) needs a test on the real driver.naturalOrWhere return-value fix is a silent behavioural change. Call sites that depended on the previous null/void return — ternaries, truthiness checks, if ($result = $query->naturalOrWhere(...)) — will now behave differently. Grep for the macro before bumping and audit each hit; this is the main upgrade risk in v3.1.0, and it is not a BC break for correct code.->toSql() and ->getBindings() at the point of failure rather than the final SQL from the query log; it shows whether the macro expanded before or after your grouping closure was applied.searchNatural) over forking, and if the package exposes a comparator/collation config, publish it rather than editing vendor files — the 5.6→13 support range means internals are more likely to shift than the public macros.How can I help you explore Laravel packages today?