Illuminate\Support\Traits\Conditionable adds when() and unless() to any class that uses it. These methods let you branch logic conditionally while keeping a fluent method chain intact.
The source lives in src/Illuminate/Conditionable/Traits/Conditionable.php. The Illuminate\Support\Traits\Conditionable namespace is an alias that points there.
Many Laravel classes use this trait: QueryBuilder, Eloquent Builder, Mail, Notification, and others.
When the callback returns null, when() returns $this so the chain continues. When the callback returns a value, that value becomes the next item in the chain.
// The chain continues because the callbacks return void/null$query = User::query() ->when($request->has('active'), function ($query) { $query->where('active', true); }) ->when($request->filled('name'), function ($query) use ($request) { $query->where('name', 'like', "%{$request->name}%"); }) ->orderBy('created_at', 'desc');
When the callback returns an explicit value, that value flows to the next call in the chain. When the callback returns nothing (null), $this is returned instead.
Calling when() with one argument returns a proxy that holds that value as the condition.
// One argument: get a proxy with the condition already set$proxy = collect([1, 2, 3])->when($request->has('filter'));// $proxy->methodName() calls methodName() only when the condition is truthy
when() is available in Mailable and Notification classes too.
use Illuminate\Mail\Mailable;class OrderConfirmation extends Mailable{ public function build(): static { return $this ->subject('Your order has been received') ->view('emails.order.confirmation') ->when($this->order->hasDiscount(), function (Mailable $mail) { $mail->attach(storage_path('discounts/coupon.pdf')); }) ->when(app()->environment('production'), function (Mailable $mail) { $mail->bcc('[email protected]'); }); }}
// tap: use for side effects; always returns $this$user = User::find($id) ->tap(fn ($user) => Log::info("User {$user->id} loaded"));// when: use for conditional branching$user = User::query() ->when($isAdmin, fn ($q) => $q->where('role', 'admin')) ->first();
If you only want to trigger a side effect (logging, debugging) mid-chain, use tap(). Use when() or unless() when you need to change behavior based on a condition.