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

# Eloquent Bootable Traits

> 解說 Eloquent 模型自動啟動 trait 的機制。學習 `bootXxx()`、`initializeXxx()` 方法的命名慣例，以及以 PHP Attribute 明確指定的方式。這是套件開發中常用的模式。

## 概觀

Eloquent 模型每次實例化時都會執行「啟動處理（boot）」與「初始化處理（initialize）」。若在 trait 中定義了這些方法，只要模型 `use` 該 trait，便會自動被呼叫。

透過此機制，可以乾淨地實作向模型注入功能的 trait。Laravel 框架本身也在 `SoftDeletes` 等許多 trait 中活用此模式。

```mermaid theme={null}
sequenceDiagram
    participant App as 應用程式
    participant Model as Eloquent Model
    participant Trait as trait

    App->>Model: new Post() 或 Post::find()
    Model->>Model: bootIfNotBooted()
    Model->>Trait: bootXxx()（僅類別首次）
    Model->>Model: initializeTraits()
    Model->>Trait: initializeXxx()（每個實例）
    Model-->>App: 模型實例
```

***

## boot 與 initialize 的差異

|          | `bootXxx()`               | `initializeXxx()` |
| -------- | ------------------------- | ----------------- |
| **呼叫時機** | 模型類別最初啟動時（每個類別一次）         | 每次實例產生時           |
| **方法種類** | `static`                  | 實例方法              |
| **用途**   | 事件監聽器註冊、新增 Global Scope 等 | 預設值設定、屬性初始化等      |

***

## 命名慣例

### bootXxx()

在 trait 名稱前加上 `boot` 前綴的 **`static`** 方法，會於模型啟動時被呼叫一次。

```php theme={null}
namespace App\Models\Concerns;

trait HasSlug
{
    public static function bootHasSlug(): void
    {
        static::creating(function ($model) {
            if (empty($model->slug)) {
                $model->slug = str($model->name)->slug()->toString();
            }
        });
    }
}
```

於模型端使用：

```php theme={null}
use App\Models\Concerns\HasSlug;

class Article extends Model
{
    use HasSlug;
    // bootHasSlug() 會被自動呼叫
}
```

### initializeXxx()

在 trait 名稱前加上 `initialize` 前綴的實例方法，會於模型每次 `new` 時被呼叫。

```php theme={null}
namespace App\Models\Concerns;

trait HasDefaultStatus
{
    public function initializeHasDefaultStatus(): void
    {
        $this->attributes['status'] ??= 'draft';
    }
}
```

亦可用於動態新增 `$casts`：

```php theme={null}
trait HasMetadata
{
    public function initializeHasMetadata(): void
    {
        $this->mergeCasts([
            'metadata' => 'array',
        ]);
    }
}
```

***

## 以 PHP Attribute 明確指定

Laravel 12 以後，可不依賴命名慣例，透過 PHP Attribute 明確指定方法。

| Attribute       | 對象            |
| --------------- | ------------- |
| `#[Boot]`       | 於類別啟動時執行一次的方法 |
| `#[Initialize]` | 於每次實例產生時執行的方法 |

```php theme={null}
use Illuminate\Database\Eloquent\Attributes\Boot;
use Illuminate\Database\Eloquent\Attributes\Initialize;

trait HasAuditLog
{
    #[Boot]
    public static function registerAuditListeners(): void
    {
        static::created(function ($model) {
            AuditLog::record('created', $model);
        });

        static::updated(function ($model) {
            AuditLog::record('updated', $model);
        });
    }

    #[Initialize]
    public function setAuditDefaults(): void
    {
        $this->attributes['audit_enabled'] ??= true;
    }
}
```

<Info>
  使用 PHP Attribute 時，方法名稱不再受限於 trait 名稱，可取具意義的名稱。也可於同一 trait 中定義多個 `#[Boot]` 或 `#[Initialize]` 方法。
</Info>

***

## 實務範例：套件開發的活用

### 自動新增 Global Scope

```php theme={null}
trait HasTenant
{
    public static function bootHasTenant(): void
    {
        static::addGlobalScope(new TenantScope);
    }
}
```

### 加入預設 cast

```php theme={null}
trait HasJsonSettings
{
    public function initializeHasJsonSettings(): void
    {
        $this->mergeCasts([
            'settings' => 'array',
        ]);
    }
}
```

### 以 Eloquent 事件自動處理

```php theme={null}
trait Archivable
{
    #[Boot]
    public static function bootArchivable(): void
    {
        static::deleting(function ($model) {
            Archive::store($model->toArray());
        });
    }
}
```

***

## Laravel 框架內的範例

Laravel 框架本身也在許多 trait 中採用此模式。

| trait         | 方法                  | 處理內容                     |
| ------------- | ------------------- | ------------------------ |
| `SoftDeletes` | `bootSoftDeletes()` | 加入軟刪除用的 Global Scope     |
| `HasFactory`  | `bootHasFactory()`  | 無特別處理（機制外的內容）            |
| `HasUlids`    | `bootHasUlids()`    | 於 `creating` 事件自動設定 ULID |
| `HasUuids`    | `bootHasUuids()`    | 於 `creating` 事件自動設定 UUID |

以這些作為參考實作閱讀，能加深對此模式的理解。

***

## 注意事項

### 模型快取的清除

由於 `boot*` 每個類別只執行一次，在測試環境中需要留意。可透過 `Model::clearBootedModels()` 重置已啟動的快取。

```php theme={null}
// 測試後重置模型的啟動狀態
protected function tearDown(): void
{
    Model::clearBootedModels();
    parent::tearDown();
}
```

### 執行順序

若同一模型 `use` 了多個 trait，`bootXxx()` / `initializeXxx()` 會依 PHP 的 trait 解析順序（宣告順序）呼叫。若有依賴關係，請留意 `use` 的順序。

***

## 總結

| 模式      | 方式                                  | 適用場合                 |
| ------- | ----------------------------------- | -------------------- |
| 類別啟動時處理 | `static bootXxx()` 或 `#[Boot]`      | 事件註冊、Global Scope 追加 |
| 實例初始化   | `initializeXxx()` 或 `#[Initialize]` | 預設值、加入 cast          |

於 trait 中實作 `bootXxx()` / `initializeXxx()`，讓向模型注入功能只需 `use` 一行即可完成。這是套件為模型新增功能時的常見模式。


## Related topics

- [Conditionable trait](/zh-TW/advanced/conditionable.md)
- [Dumpable trait](/zh-TW/advanced/dumpable.md)
- [Eloquent Factories](/zh-TW/eloquent-factories.md)
- [ForwardsCalls trait](/zh-TW/advanced/forwards-calls.md)
- [Eloquent 的 Scope](/zh-TW/advanced/eloquent-scopes.md)
