> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Conditionable trait

> 解說 when() / unless() 方法的內部實作與活用模式。

## Conditionable trait 是什麼

`Illuminate\Support\Traits\Conditionable` trait 為物件加入 `when()` 與 `unless()` 方法。特色是可依條件分支處理，同時延續方法鏈。

<Info>
  實際原始碼位於 `src/Illuminate/Conditionable/Traits/Conditionable.php`，並透過 `Illuminate\Support\Traits\Conditionable` 別名被參照。
</Info>

QueryBuilder、EloquentBuilder、Mail、Notification 等 Laravel 中許多類別皆使用此 trait。

## 基本用法

### when() — 條件為真時執行

```php theme={null}
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]
```

當第 1 個引數的值為真時，執行第 2 個引數的 callback。為偽時，會執行第 3 個引數的 callback（預設）。

```php theme={null}
$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()` 的相反。條件為偽時執行 callback。

```php theme={null}
$results = collect([1, 2, 3, 4, 5])
    ->unless(false, function (Collection $collection) {
        return $collection->take(3);
    });
// [1, 2, 3]
```

## 方法鏈能持續的理由

若 callback 的回傳值為 `null`，會回傳 `$this`（使用 trait 的物件）。若 callback 不回傳 `null`，則回傳其回傳值。

```php theme={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');
```

原始碼中如下實作：

```php theme={null}
if ($value) {
    return $callback($this, $value) ?? $this;
} elseif ($default) {
    return $default($this, $value) ?? $this;
}

return $this;
```

若 callback 明確回傳值，該值即傳遞至鏈的下一環節。若未回傳任何值（`null`），則回傳 `$this`。

## 不帶引數呼叫 — HigherOrderWhenProxy

以零引數呼叫 `when()` 會回傳 `HigherOrderWhenProxy`，可利用此後再設定條件。

```php theme={null}
$query = User::query()
    ->when()->isActive()  // 以 isActive() 作為條件評估
    ->where('role', 'admin');
```

以 1 個引數呼叫時，會回傳持有該值作為條件的 proxy。

```php theme={null}
// 1 個引數：僅傳入條件並取得 proxy
$proxy = collect([1, 2, 3])->when($request->has('filter'));
// 透過 $proxy->methodName() 僅在條件為真時呼叫 methodName()
```

## 將 closure 作為值傳入

若在第 1 個引數傳入 closure，會以該 closure 的執行結果作為條件。

```php theme={null}
$results = User::query()
    ->when(
        fn ($query) => $request->filled('role'),
        fn ($query) => $query->where('role', $request->role)
    )
    ->get();
```

如此可將條件評估邏輯抽出至 callback。

## QueryBuilder 的典型模式

使用 `when()` 動態構建查詢是最常見的使用情境。

```php theme={null}
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` 該 trait 就能使用 `when()` / `unless()`。

```php theme={null}
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);
                }
            });
    }
}
```

```php theme={null}
// 使用範例
$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()` 亦可用於郵件、通知及回應的建立。

```php theme={null}
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('archive@example.com');
            });
    }
}
```

## 與 tap() 的差異

`tap()` 與 `when()` 相似，但用途不同。

|     | `tap()`    | `when()`                     |
| --- | ---------- | ---------------------------- |
| 目的  | 副作用（日誌、除錯） | 條件分支                         |
| 回傳值 | 恆為 `$this` | 依條件為 `$this` 或 callback 的回傳值 |
| 條件  | 無          | 有                            |

```php theme={null}
// 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();
```

<Tip>
  若僅為除錯或副作用而希望在鏈中執行某事，使用 `tap()`。若希望依條件切換處理，則使用 `when()` / `unless()`。
</Tip>

## 下一步

<Card title="Collection 的 Higher Order Messages" icon="layers" href="/zh-TW/advanced/higher-order-messages">
  學習 `$collection->map->method()` 這類語法的機制及實務用法。
</Card>


## Related topics

- [Fluent 類別](/zh-TW/advanced/fluent.md)
- [Dumpable trait](/zh-TW/advanced/dumpable.md)
- [tap() helper 與 Tappable trait](/zh-TW/advanced/tap.md)
- [InteractsWithData trait](/zh-TW/advanced/interacts-with-data.md)
- [InteractsWithTime trait](/zh-TW/advanced/interacts-with-time.md)
