> ## 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`、`HasUuids`、`HasUlids` 等大量 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`        | 实例方法              |
| **典型用途** | 注册事件监听器、添加全局作用域 | 设置默认属性值、合并 casts  |

***

## 命名规约

### 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 11 起，可以不依赖命名规约，通过 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>

***

## 实用示例：包开发中的应用

### 自动添加全局作用域

```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()` | 添加软删除用的全局作用域              |
| `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 解析顺序（即声明顺序）被调用。若 trait 之间存在依赖关系，请注意 `use` 的顺序。

***

## 总结

| 模式     | 定义方式                                | 适用场景         |
| ------ | ----------------------------------- | ------------ |
| 类启动时处理 | `static bootXxx()` 或 `#[Boot]`      | 事件注册、添加全局作用域 |
| 实例初始化  | `initializeXxx()` 或 `#[Initialize]` | 默认值、添加 cast  |

在 trait 中实现 `bootXxx()` / `initializeXxx()`，就能通过一行 `use` 向模型注入功能。这是包开发中向模型追加功能的经典模式。


## Related topics

- [Conditionable trait](/zh/advanced/conditionable.md)
- [Dumpable trait](/zh/advanced/dumpable.md)
- [Eloquent 工厂](/zh/eloquent-factories.md)
- [ForwardsCalls trait](/zh/advanced/forwards-calls.md)
- [Eloquent 作用域](/zh/advanced/eloquent-scopes.md)
