메인 콘텐츠로 건너뛰기

Conditionable 트레이트란

Illuminate\Support\Traits\Conditionable 트레이트는 오브젝트에 when()unless() 메서드를 추가합니다. 조건에 따라 처리를 분기하면서 메서드 체인을 이어갈 수 있는 것이 특징입니다.
실제 소스는 src/Illuminate/Conditionable/Traits/Conditionable.php에 있습니다. Illuminate\Support\Traits\Conditionable이라는 별칭에서 참조되고 있습니다.
QueryBuilder·EloquentBuilder·Mail·Notification 등, Laravel의 많은 클래스가 이 트레이트를 사용하고 있습니다.

기본적인 사용법

when() — 조건이 참일 때 실행하기

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]
첫 번째 인수의 값이 참일 때, 두 번째 인수의 콜백이 실행됩니다. 거짓일 때는 세 번째 인수의 콜백(기본값)이 실행됩니다.
$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() — 조건이 거짓일 때 실행하기

unless()when()의 반대입니다. 조건이 거짓일 때 콜백이 실행됩니다.
$results = collect([1, 2, 3, 4, 5])
    ->unless(false, function (Collection $collection) {
        return $collection->take(3);
    });
// [1, 2, 3]

메서드 체인이 이어지는 이유

콜백의 반환값이 null인 경우, $this(트레이트를 사용하는 오브젝트)가 반환됩니다. 콜백이 null을 반환하지 않는 경우에는 그 반환값이 그대로 반환됩니다.
// $this が返るのでチェーンが続く
$query = User::query()
    ->when($request->has('active'), function ($query) {
        $query->where('active', true); // void / null を返す
    })
    ->when($request->filled('name'), function ($query) use ($request) {
        $query->where('name', 'like', "%{$request->name}%");
    })
    ->orderBy('created_at', 'desc');
소스 코드에서는 다음과 같이 구현되어 있습니다.
if ($value) {
    return $callback($this, $value) ?? $this;
} elseif ($default) {
    return $default($this, $value) ?? $this;
}

return $this;
콜백이 명시적인 값을 반환한 경우에는 그 값이 체인의 다음으로 전달됩니다. 아무 것도 반환하지 않는(null) 경우에는 $this가 반환됩니다.

인수 없이 호출하기 — HigherOrderWhenProxy

인수 없이 when()을 호출하면 HigherOrderWhenProxy가 반환됩니다. 이를 사용하면 조건을 나중에 설정할 수 있습니다.
$query = User::query()
    ->when()->isActive()  // isActive() を条件として評価
    ->where('role', 'admin');
인수 하나로 호출하면, 그 값을 조건으로 갖는 프록시가 반환됩니다.
// 引数1つ: 条件だけを渡してプロキシを得る
$proxy = collect([1, 2, 3])->when($request->has('filter'));
// $proxy->methodName() で条件が真のときだけ methodName() を呼び出せる

클로저를 값으로 전달하기

첫 번째 인수에 클로저를 전달하면, 해당 클로저가 실행된 반환값이 조건으로 사용됩니다.
$results = User::query()
    ->when(
        fn ($query) => $request->filled('role'),
        fn ($query) => $query->where('role', $request->role)
    )
    ->get();
이를 통해 조건의 평가 로직을 콜백으로 분리할 수 있습니다.

QueryBuilder에서의 전형적인 패턴

when()을 사용한 동적 쿼리 구축은 가장 일반적인 유스케이스입니다.
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);
}

Conditionable을 자체 클래스에 적용하기

트레이트를 use하는 것만으로 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);
                }
            });
    }
}
// 使用例
$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();

Mail·Notification·Response에서의 활용

when()은 메일·알림·응답 구축에서도 사용할 수 있습니다.
use Illuminate\Mail\Mailable;
use Illuminate\Support\Traits\Conditionable;

class OrderConfirmation extends Mailable
{
    public function build(): static
    {
        return $this
            ->subject('ご注文を承りました')
            ->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()과의 사용 구분

tap()when()은 비슷하지만 목적이 다릅니다.
tap()when()
목적부수 효과(로그·디버그)조건 분기
반환값항상 $this조건에 따라 $this 또는 콜백의 반환값
조건없음있음
// tap: 副作用のために使う。戻り値は常に $this
$user = User::find($id)
    ->tap(fn ($user) => Log::info("User {$user->id} loaded"));

// when: 条件分岐に使う
$user = User::query()
    ->when($isAdmin, fn ($q) => $q->where('role', 'admin'))
    ->first();
디버그나 부수 효과를 위해 체인 중간에 뭔가를 하고 싶은 것뿐이라면 tap()을 사용합니다. 조건에 따라 처리를 전환하고 싶다면 when() / unless()를 사용합니다.

다음 단계

컬렉션의 고차 메시지

$collection->map->method()와 같은 구문의 구조와 실전적인 사용법을 배웁니다.
마지막 수정일 2026년 7월 13일