跳转到主要内容

概述

Eloquent 模型在每次实例化时都会执行两个阶段:「启动处理(boot)」和「初始化处理(initialize)」。只要在 trait 中定义符合规约的方法,模型 use 该 trait 时就会自动调用它们。 利用这一机制,可以干净地实现向模型注入功能的 trait。Laravel 框架自身也在 SoftDeletesHasUuidsHasUlids 等大量 trait 中广泛运用此模式。

boot 与 initialize 的区别

bootXxx()initializeXxx()
调用时机模型类首次启动时(每个类一次)每次创建实例时
方法类型static实例方法
典型用途注册事件监听器、添加全局作用域设置默认属性值、合并 casts

命名规约

bootXxx()

在 trait 名称前加上 boot 前缀,定义 static 方法,即可在模型启动时被调用一次。
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();
            }
        });
    }
}
在模型中使用:
use App\Models\Concerns\HasSlug;

class Article extends Model
{
    use HasSlug;
    // bootHasSlug() 会被自动调用
}

initializeXxx()

在 trait 名称前加上 initialize 前缀,定义实例方法,即可在每次 new 模型时被调用。
namespace App\Models\Concerns;

trait HasDefaultStatus
{
    public function initializeHasDefaultStatus(): void
    {
        $this->attributes['status'] ??= 'draft';
    }
}
也可以用于动态添加 $casts
trait HasMetadata
{
    public function initializeHasMetadata(): void
    {
        $this->mergeCasts([
            'metadata' => 'array',
        ]);
    }
}

通过 PHP Attribute 显式指定

从 Laravel 11 起,可以不依赖命名规约,通过 PHP Attribute 显式标注方法。
Attribute用途
#[Boot]标记在类启动时仅执行一次的方法
#[Initialize]标记在每次创建实例时执行的方法
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;
    }
}
使用 PHP Attribute 后,方法名不再受 trait 名称的约束,可以取更有语义的名字。也可以在同一个 trait 中定义多个 #[Boot]#[Initialize] 方法。

实用示例:包开发中的应用

自动添加全局作用域

trait HasTenant
{
    public static function bootHasTenant(): void
    {
        static::addGlobalScope(new TenantScope);
    }
}

添加默认 cast

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

通过 Eloquent 事件自动处理

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

Laravel 框架内的示例

Laravel 框架本身在众多 trait 中都采用了这一模式。
Trait方法处理内容
SoftDeletesbootSoftDeletes()添加软删除用的全局作用域
HasFactorybootHasFactory()无实质处理(作用域外机制)
HasUlidsbootHasUlids()creating 事件中自动设置 ULID
HasUuidsbootHasUuids()creating 事件中自动设置 UUID
阅读这些参考实现,有助于加深对该模式的理解。

注意事项

清除模型缓存

由于 boot* 每个类只执行一次,在测试环境中需要特别注意。可以通过 Model::clearBootedModels() 重置已启动的缓存。
// 测试结束后重置模型的启动状态
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 向模型注入功能。这是包开发中向模型追加功能的经典模式。
最后修改于 2026年7月13日