Passer au contenu principal

Qu’est-ce que le trait Conditionable

Le trait Illuminate\Support\Traits\Conditionable ajoute les méthodes when() et unless() à un objet. Sa particularité est de permettre de continuer une chaîne de méthodes tout en dérivant le traitement en fonction d’une condition.
Les sources réelles se trouvent dans src/Illuminate/Conditionable/Traits/Conditionable.php. Elles sont référencées via l’alias Illuminate\Support\Traits\Conditionable.
De nombreuses classes Laravel utilisent ce trait : QueryBuilder, EloquentBuilder, Mail, Notification, etc.

Utilisation de base

when() — s’exécute lorsque la condition est vraie

use Illuminate\Support\Collection;

$results = collect([1, 2, 3, 4, 5])
    ->when(true, function (Collection $collection) {
        return $collection->filter(fn ($n) => $n > 2);
    });
// [3, 4, 5]
Quand la valeur du premier argument est vraie, le callback du second argument est exécuté. Sinon, le callback (par défaut) du troisième argument est exécuté.
$results = collect([1, 2, 3, 4, 5])
    ->when(false, function (Collection $collection) {
        return $collection->filter(fn ($n) => $n > 2);
    }, function (Collection $collection) {
        return $collection->filter(fn ($n) => $n < 3);
    });
// [1, 2]

unless() — s’exécute lorsque la condition est fausse

unless() est l’inverse de when(). Le callback est exécuté lorsque la condition est fausse.
$results = collect([1, 2, 3, 4, 5])
    ->unless(false, function (Collection $collection) {
        return $collection->take(3);
    });
// [1, 2, 3]

Pourquoi le chaînage continue

Si la valeur de retour du callback est null, c’est $this (l’objet qui utilise le trait) qui est retourné. Si le callback ne retourne pas null, c’est sa valeur de retour qui est retournée.
// $this est retourné donc la chaîne continue
$query = User::query()
    ->when($request->has('active'), function ($query) {
        $query->where('active', true); // retourne void / null
    })
    ->when($request->filled('name'), function ($query) use ($request) {
        $query->where('name', 'like', "%{$request->name}%");
    })
    ->orderBy('created_at', 'desc');
Dans le code source, cela est implémenté ainsi :
if ($value) {
    return $callback($this, $value) ?? $this;
} elseif ($default) {
    return $default($this, $value) ?? $this;
}

return $this;
Si le callback retourne explicitement une valeur, cette valeur est passée au maillon suivant de la chaîne. S’il ne retourne rien (null), $this est retourné.

Appel sans argument — HigherOrderWhenProxy

Appeler when() sans argument retourne un HigherOrderWhenProxy. Cela permet de définir la condition ultérieurement.
$query = User::query()
    ->when()->isActive()  // évalue isActive() comme condition
    ->where('role', 'admin');
Avec un seul argument, un proxy contenant cette valeur comme condition est retourné.
// Un argument : passer uniquement la condition pour obtenir un proxy
$proxy = collect([1, 2, 3])->when($request->has('filter'));
// $proxy->methodName() n'appelle methodName() que si la condition est vraie

Passer une closure comme valeur

Passer une closure en premier argument utilise la valeur de retour de l’exécution de cette closure comme condition.
$results = User::query()
    ->when(
        fn ($query) => $request->filled('role'),
        fn ($query) => $query->where('role', $request->role)
    )
    ->get();
Cela permet d’extraire la logique d’évaluation de la condition dans un callback.

Pattern typique avec QueryBuilder

La construction dynamique de requêtes avec when() est le cas d’usage le plus courant.
public function index(Request $request)
{
    $users = User::query()
        ->when($request->filled('search'), function ($query) use ($request) {
            $query->where('name', 'like', "%{$request->search}%")
                  ->orWhere('email', 'like', "%{$request->search}%");
        })
        ->when($request->filled('role'), fn ($q) => $q->where('role', $request->role))
        ->when($request->boolean('verified'), fn ($q) => $q->whereNotNull('email_verified_at'))
        ->when(
            $request->filled('sort'),
            fn ($q) => $q->orderBy($request->sort, $request->get('direction', 'asc')),
            fn ($q) => $q->latest()
        )
        ->paginate();

    return UserResource::collection($users);
}

Appliquer Conditionable à votre propre classe

Il suffit d’un use du trait pour disposer de when() / unless().
namespace App\Services;

use Illuminate\Support\Traits\Conditionable;

class ReportBuilder
{
    use Conditionable;

    protected array $filters = [];
    protected bool $includeArchived = false;
    protected ?string $groupBy = null;

    public function withArchived(): static
    {
        $this->includeArchived = true;

        return $this;
    }

    public function groupBy(string $column): static
    {
        $this->groupBy = $column;

        return $this;
    }

    public function addFilter(string $column, mixed $value): static
    {
        $this->filters[$column] = $value;

        return $this;
    }

    public function build(): \Illuminate\Database\Eloquent\Builder
    {
        return Report::query()
            ->when($this->includeArchived, fn ($q) => $q->withTrashed())
            ->when($this->groupBy, fn ($q) => $q->groupBy($this->groupBy))
            ->when($this->filters, function ($q) {
                foreach ($this->filters as $column => $value) {
                    $q->where($column, $value);
                }
            });
    }
}
// Exemple d'utilisation
$query = (new ReportBuilder)
    ->when($request->boolean('archived'), fn ($b) => $b->withArchived())
    ->when($request->filled('group'), fn ($b) => $b->groupBy($request->group))
    ->addFilter('status', 'published')
    ->build();

Utilisation avec Mail, Notification, Response

when() est également utilisable pour la construction d’e-mails, de notifications et de réponses.
use Illuminate\Mail\Mailable;
use Illuminate\Support\Traits\Conditionable;

class OrderConfirmation extends Mailable
{
    public function build(): static
    {
        return $this
            ->subject('Votre commande a bien été reçue')
            ->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]');
            });
    }
}

Différence avec tap()

tap() et when() se ressemblent mais ont des objectifs différents.
tap()when()
ObjectifEffet de bord (log, debug)Branchement conditionnel
Valeur de retourToujours $this$this ou la valeur de retour du callback selon la condition
ConditionAucuneOui
// tap : à utiliser pour un effet de bord. Retourne toujours $this
$user = User::find($id)
    ->tap(fn ($user) => Log::info("User {$user->id} loaded"));

// when : à utiliser pour un branchement conditionnel
$user = User::query()
    ->when($isAdmin, fn ($q) => $q->where('role', 'admin'))
    ->first();
Si vous voulez juste faire quelque chose au milieu d’une chaîne pour du debug ou un effet de bord, utilisez tap(). Pour changer le traitement selon une condition, utilisez when() / unless().

Étapes suivantes

Messages d'ordre supérieur de Collection

Apprenez le mécanisme de la syntaxe $collection->map->method() et son utilisation pratique.
Dernière modification le 13 juillet 2026