aglipanci/laravel-eloquent-case
Add case-insensitive where clauses to Laravel Eloquent. This package provides simple, chainable whereCaseInsensitive and orWhereCaseInsensitive methods so you can query models without worrying about letter case, avoiding raw SQL and keeping your code clean and readable.
Install with composer require aglipanci/laravel-eloquent-case. The service provider is auto-discovered and there is no config file to publish. Start by reading the README and src/Query/CaseBuilder.php; the day-to-day surface is the fluent case()->when()->then()->else()->as() chain.
The first use case is replacing a PHP-side match/switch label map with a SQL CASE, so filtering, sorting, and grouping can happen in the database:
use AgliPanci\LaravelCase\Query\CaseBuilder;
$statusLabel = CaseBuilder::case()
->when('orders.status', '=', 'paid')->then('Paid')
->when('orders.status', '=', 'pending')->then('Pending')
->else('Unknown')
->as('status_label');
$orders = Order::query()
->select('orders.*')
->selectRaw((string) $statusLabel)
->get();
As of 5.1.0 the generated identifiers follow the active Laravel connection grammar, so the same expression is portable across MySQL/MariaDB (`backticks`), Postgres/SQLite ("double quotes"), and SQL Server ([brackets]).
Centralize label maps with a helper. Avoid repeating WHEN chains across controllers and scopes:
function statusLabelCase(string $column = 'status'): CaseBuilder
{
$case = CaseBuilder::case();
foreach (OrderStatus::labels() as $value => $label) {
$case->when($column, '=', $value)->then($label);
}
return $case->else('Unknown')->as('status_label');
}
Use it in a local scope so every consumer gets the same derived column:
public function scopeWithStatusLabel(Builder $query): Builder
{
return $query->selectRaw((string) statusLabelCase('orders.status'));
}
Custom sort order. CASE is useful for business-priority ordering that a normal ORDER BY cannot express:
$query->orderByRaw((string) CaseBuilder::case()
->when('status', '=', 'urgent')->then(0)
->when('status', '=', 'normal')->then(1)
->else(2));
Conditional aggregates for reporting. Embed the fragment inside an aggregate:
$query->selectRaw('SUM(' . (string) CaseBuilder::case()
->when('status', '=', 'paid')->then(1)
->else(0) . ') as paid_count');
Build per connection in multi-database apps. Construct the expression after the model/connection is selected (Model::on('pgsql')) and keep the builder close to the query it feeds. If you cache generated SQL strings, include the connection name in the cache key.
assertDatabaseHas, returned attributes) over exact SQL.CASE fragments or generated query strings, flush them on deploy; cached fragments may contain the old quoting.CaseBuilder instance built while one connection is active will quote for that grammar. Build the expression on the same connection as the query that uses it.then() and else() arguments as SQL literals, not bindings. Never pass raw request input; use whitelisted maps, enums, or parameter binding with DB::raw/selectRaw placeholders for dynamic values.=, >, LIKE, IS NULL, etc. explicitly; do not rely on a two-argument when() default if you have not verified it in the source.NULL does not match =. A branch like when('deleted_at', '=', null) will not behave as expected. Add explicit IS NULL / IS NOT NULL handling.else() produces NULL. If downstream DTOs or API resources expect a non-null label, always add an else() fallback.status_label selected via selectRaw is not automatically a model attribute in toArray(); add it to $appends or map it in a resource. It also bypasses casts, so it will be a string.WHERE/ORDER BY can defeat indexes. Filter on indexed columns with normal where() calls where possible, and use CASE for presentation or derived sort priority rather than primary filtering.then()/else() values as driver-neutral scalars or strings when you need cross-database portability.DB::raw for that specific branch or write the full expression as raw SQL rather than fighting the fluent API.How can I help you explore Laravel packages today?