Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Conditionable Laravel Package

illuminate/conditionable

Illuminate/Conditionable provides the Conditionable trait, adding when() and unless() methods to Laravel classes. Run callbacks conditionally without verbose if statements, keeping fluent method chains clean and readable. Lightweight and dependency-free, it ships as part of the Laravel framework.

View on GitHub
Deep Wiki
Context7

Getting Started

You probably already have it. illuminate/conditionable is a read-only split of laravel/framework and is pulled in transitively by illuminate/support, so a normal Laravel app needs no composer require. Confirm with:

composer show illuminate/conditionable        # standalone/component installs
composer show laravel/framework                # full app: framework `replace`s it

In a full app the source lives at vendor/laravel/framework/src/Illuminate/Support/Traits/Conditionable.php; in a component-based install it is vendor/illuminate/conditionable/Traits/Conditionable.php. Read that file first — it is ~70 lines and defines the entire contract (when, unless, the HigherOrderWhenProxy).

Only add the direct dependency when you need the trait without the rest of Laravel (a standalone package, Lumen, a non-Laravel codebase using illuminate components):

composer require illuminate/conditionable

First use case — optional request filters. The canonical day-to-day use is keeping a query readable when filters arrive from a request:

$users = User::query()
    ->when($request->filled('search'), fn ($q, $search) => $q->where('name', 'like', "%{$search}%"))
    ->when($request->boolean('active_only'), fn ($q) => $q->where('active', true))
    ->when($request->user()->isAdmin(), fn ($q) => $q->withTrashed())
    ->latest()
    ->paginate();

Import the trait as Illuminate\Support\Traits\Conditionable (note the Traits segment) and use it in any of your own classes to get the same chainability.

Implementation Patterns

Conditional object/DTO assembly. Because the callback receives the instance and the chain falls back to $this when the condition is false, you can build value objects without a single if:

return (new Invoice())
    ->when($order->isPaid(), fn ($i) => $i->markPaid())
    ->when($order->hasDiscount(), fn ($i, $on) => $i->applyDiscount($order->discount))
    ->when($order->isGift(), fn ($i) => $i->asGift(), fn ($i) => $i->asStandard());

The third argument is the else-branch. Prefer it over when(!$cond, ...) — it reads better and evaluates the condition once.

Reuse the condition value inside the closure. The callback signature is ($this, $value), so you never need to capture the condition again:

->when($request->input('role'), fn ($q, $role) => $q->where('role', $role))

Higher-order shorthand. With a single argument, when() returns a HigherOrderWhenProxy, letting you drop the closure for simple calls:

$query->when($includeTrashed)->withTrashed();
$mail->when($isDebug)->onQueue('debug');

Reach for this only when the conditional call takes no arguments of its own and needs no branching — otherwise the callback form is clearer.

Lazy conditions. Pass a closure as the condition to defer evaluation (and to receive $this as its argument):

$query->when(fn () => auth()->user()?->isAdmin(), fn ($q) => $q->withTrashed());

Use this for anything that hits the DB, the container, or config, so it isn't evaluated when the chain is assembled for an unrelated path.

Pipeline/service chaining. The trait is most valuable in service classes, jobs, and HTTP client calls — anywhere Laravel already chains fluently:

Http::withToken($token)
    ->when($trace, fn ($http) => $http->withHeader('X-Trace', $traceId))
    ->when($retryable, fn ($http) => $http->retry(3, 200))
    ->post('/orders', $payload);

Encapsulate recurring conditionals. If the same when() expression appears in several places, wrap it in your own trait (which itself uses Conditionable) or a local scope:

trait HasVisibilityFilters
{
    use Conditionable;

    public function whenPublished(bool $published): static
    {
        return $this->when($published, fn ($q) => $q->whereNotNull('published_at'));
    }
}

Test both branches explicitly. Assert the callback does not run when the condition is false, rather than only asserting the happy path:

$called = false;
$subject->when(false, function () use (&$called) { $called = true; });
$this->assertFalse($called);
$this->assertSame($subject, $subject->when(false, fn ($s) => $s));

Note Collection has the same when/unless semantics plus whenEmpty()/whenNotEmpty() — the mental model transfers directly between collections, query builders, and your own classes.

Gotchas and Tips

  • Namespace: it is Illuminate\Support\Traits\Conditionable. Older tutorials show Illuminate\Support\Conditionable, which no longer resolves — a wasted half hour if you copy from a stale blog post.
  • This is the official framework package, not an unvetted third-party. It's a read-only split published for component installs; in a full app it is replaced by laravel/framework. Never fork or pin the split repo — send fixes upstream to the monorepo.
  • Single-argument when() returns a proxy, not $this. $obj->when(true) yields a HigherOrderWhenProxy, which breaks assertSame, typed return declarations (: static), and instanceof checks. Fix: pass a callback, or immediately force resolution with ->when($cond, fn ($x) => $x).
  • ?? $this swallow. The callback's return value replaces the chain subject unless it is null, in which case $this is returned. So a callback that returns null on purpose silently keeps chaining, and one that returns a Collection/int/string silently changes the chain's type. End the chain outside the last when() if the terminal call returns something else.
  • Truthiness, not filled(). 0, '', [], and '0' are all falsy. Pass real booleans ($request->boolean(...), $model->exists) rather than raw input, or you will ship a filter that silently never runs.
  • Don't over-apply it. Three consecutive when() calls sharing one condition is an if block in disguise; hoist the condition into a variable or a named scope. The trait earns its keep on 1–3 conditionals inside an otherwise linear chain.
  • Debugging: drop dump()/ray() inside the closure. If it never fires, the condition was falsy (see truthiness above) or the method was reached via the proxy path. Keep a breakpoint in Conditionable::when() when a chain "skips" a step.
  • Static analysis: PHPStan/Larastan can't express "returns $this or the callback's type," so generics on Collection/Builder can degrade after a when(). Add @return $this on wrapper methods and type-hint the closure param (fn (Builder $q) => ...) for inference inside the callback.
  • Version alignment: in component installs keep illuminate/conditionable on the same major as your other illuminate packages (^9.0|^10.0|^11.0|^12.0). In a full app, adding it directly invites composer conflicts for zero benefit.
  • No config, no provider, no facade. There is nothing to publish or tune; all behavior is in the trait. Extension points are your own traits wrapping it, or macros on Collection/Builder for domain-specific helpers (whenFilled(), whenOwnedBy()).
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
vento/laravel-airbrake
wfeller/laravel-batch
maco-studios/mage-os-installer
busarm/armie
siberfx/laravel-logger
kfteam1991/dcat-admin
dashed/dashed-laposta
dashed/dashed-ecommerce-montaportal
dashed/dashed-ecommerce-etsy
neohq/string-blade-compiler
log1x/acf-composer
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core
nodisrupt/laravel-agent
sierratecnologia/media-manager