Model::orWhere

Joins a condition to the query with OR instead of AND, accepting the same arguments as where().

Signature
public function orWhere(\Closure|string|array|\Illuminate\Contracts\Database\Query\Expression $column, mixed $operator = null, mixed $value = null): $this

Parameters

ParameterTypeDescription
$column\Closure|string|array|\Illuminate\Contracts\Database\Query\ExpressionA column name, an array of conditions, or a Closure that opens a parenthesised group joined with OR.
$operatoroptionalmixed= nullA comparison operator. With two arguments this is treated as the value and the operator becomes '='.
$valueoptionalmixed= nullThe value to compare the column against.

Return value

$thisThe same builder instance, so calls can be chained. The clause is added with the 'or' boolean.

Errors

Throws InvalidArgumentException with the message "Illegal operator and value combination." when three arguments are passed, the value is null, and the operator is a recognised one other than '=', '<=>', '<>' or '!='. An operator the grammar does not recognise does not throw: it is re-read as the value, with '=' substituted as the operator.

orWhere() takes exactly the arguments where() takes and joins the resulting clause with or rather than and. Passing a Closure as the first argument wraps the whole group in parentheses and joins that with or.

The rule that matters

SQL binds AND more tightly than OR, so a bare orWhere() in a chain splits the query at the top level. Laravel's own documentation is unambiguous: you should always group orWhere calls in parentheses to avoid unexpected query behaviour, particularly once global scopes are applied.

The failure is quiet and it is a security bug more often than a display bug. Post::where('user_id', $id)->where('published', 1)->orWhere('featured', 1)->get() compiles to user_id = ? and published = ? or featured = ? — every featured post in the table leaks past the ownership check. Wrap the alternatives in a Closure and the ownership filter holds.

Global scopes make this worse because their clauses are appended to the same list. A soft-delete scope contributes deleted_at is null with and, and an ungrouped or earlier in the chain can float trashed rows straight back into the result.

When to reach for it

Multi-column search, alternate identifiers, and status sets that are not a simple IN list. When you are comparing one column against several values, whereIn() is clearer and compiles better.

Example

Example · php
<?php

use App\Models\User;
use Illuminate\Database\Eloquent\Builder;

// Ungrouped - the OR splits the whole query.
$users = User::where('votes', '>', 100)
    ->orWhere('name', 'John')
    ->get();
// select * from users where votes > ? or name = ?

// Grouped - the OR is contained in parentheses.
$users = User::where('votes', '>', 100)
    ->orWhere(function (Builder $query) {
        $query->where('name', 'Abigail')
            ->where('votes', '>', 50);
    })
    ->get();
// select * from users where votes > ? or (name = ? and votes > ?)

echo $users->count(), "\n";

When to use it

  • Search a name or an email column with a single user-supplied term.
  • Match a record by either its numeric id or its public slug.
  • Treat rows as visible when they are published or when the viewer owns them.

Frequently asked questions

Why is my orWhere() returning rows that fail my other conditions?

Because AND binds tighter than OR in SQL, so an ungrouped orWhere() splits the query at the top level and your earlier where() clauses only constrain the left-hand branch. Wrap the alternatives in a Closure — where(function ($q) { $q->where(...)->orWhere(...); }) — so they compile inside parentheses. Laravel's documentation recommends always grouping orWhere calls for exactly this reason.

How do I write parentheses around an OR in Eloquent?

Pass a Closure instead of a column name, to either where() or orWhere(). The closure receives a builder, and every clause you add to it is wrapped in parentheses in the compiled SQL. where(fn ($q) => $q->where('a', 1)->orWhere('b', 2)) produces and (a = ? or b = ?), while orWhere(fn ($q) => ...) produces or (...).

Should I use orWhere() or whereIn() for multiple values?

Use whereIn() when you are testing one column against a list of values — it compiles to a single IN clause, cannot leak past a surrounding AND, and generally optimises better. Reserve orWhere() for alternatives that span different columns or need different operators, such as matching a name or an email with LIKE.

More examples

The leak an ungrouped OR causes

Read the two compiled strings side by side. In the broken query the user_id filter binds only to the published branch, so any featured post owned by anyone satisfies the second half — the query returns other people's rows and nothing errors.

Example · php
<?php

use App\Models\Post;
use Illuminate\Database\Eloquent\Builder;

$userId = 7;

// BROKEN: every featured post in the table matches.
Post::where('user_id', $userId)
    ->where('published', 1)
    ->orWhere('featured', 1)
    ->get();
// where user_id = ? and published = ? or featured = ?

// CORRECT: ownership always applies.
$posts = Post::where('user_id', $userId)
    ->where(function (Builder $query) {
        $query->where('published', 1)
            ->orWhere('featured', 1);
    })
    ->get();
// where user_id = ? and (published = ? or featured = ?)

echo $posts->count(), "\n";

Multi-column search done safely

The nested closure keeps the three LIKE alternatives inside one parenthesised group, so tenant_id survives no matter how many search columns are added later. when() skips the group entirely for an empty term rather than emitting an always-true condition.

Example · php
<?php

use App\Models\User;
use Illuminate\Database\Eloquent\Builder;

$term = 'acme';

$users = User::where('tenant_id', 3)
    ->when($term, function (Builder $query, string $term) {
        $query->where(function (Builder $q) use ($term) {
            $q->where('name', 'like', "%{$term}%")
                ->orWhere('email', 'like', "%{$term}%")
                ->orWhere('company', 'like', "%{$term}%");
        });
    })
    ->orderBy('name')
    ->get();

// where tenant_id = ?
//   and (name like ? or email like ? or company like ?)
// order by name asc

echo $users->count(), "\n";

When whereIn is the better tool

Both counts are identical. The whereIn form is a single clause that cannot leak past a surrounding AND, so the grouping question never arises — reach for orWhere only when the alternatives involve different columns or different operators.

Example · php
<?php

use App\Models\Order;

// Works, but grows a clause per value and needs grouping.
$orders = Order::where(function ($query) {
    $query->where('status', 'pending')
        ->orWhere('status', 'processing')
        ->orWhere('status', 'shipped');
})->get();

echo $orders->count(), "\n";

// Same result, one clause, no grouping trap.
$orders = Order::whereIn('status', ['pending', 'processing', 'shipped'])->get();
// select * from orders where status in (?, ?, ?)

echo $orders->count(), "\n";

Discussion

  • Be the first to comment on this lesson.