Eloquentモデルはインスタンス化のたびに「起動処理(boot)」と「初期化処理(initialize)」を実行します。トレイトにこれらのメソッドを定義しておくと、モデルがそのトレイトを use するだけで自動的に呼び出されます。
この仕組みを使うことで、モデルに機能を注入するトレイトをクリーンに実装できます。Laravelフレームワーク自体も SoftDeletes などの多くのトレイトでこのパターンを活用しています。
boot と initialize の違い
| bootXxx() | initializeXxx() |
|---|
| 呼び出しタイミング | モデルクラスの最初の起動時(クラスごとに1回) | インスタンス生成のたび |
| メソッド種別 | static | インスタンスメソッド |
| 用途 | イベントリスナー登録、グローバルスコープ追加など | デフォルト値設定、プロパティ初期化など |
ネーミング規約
bootXxx()
トレイト名の末尾に 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()
トレイト名の末尾に 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 を使うと、メソッド名がトレイト名に縛られないため、意味のある名前をつけられます。複数の #[Boot] メソッドや #[Initialize] メソッドを同じトレイトに定義することも可能です。
実用例:パッケージ開発での活用
グローバルスコープの自動追加
trait HasTenant
{
public static function bootHasTenant(): void
{
static::addGlobalScope(new TenantScope);
}
}
デフォルトキャスト追加
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フレームワーク自身も多くのトレイトでこのパターンを採用しています。
| トレイト | メソッド | 処理内容 |
|---|
SoftDeletes | bootSoftDeletes() | ソフトデリート用グローバルスコープの追加 |
HasFactory | bootHasFactory() | 特になし(スコープ外の仕組み) |
HasUlids | bootHasUlids() | creating イベントで ULID を自動セット |
HasUuids | bootHasUuids() | creating イベントで UUID を自動セット |
これらを参考実装として読むと、パターンの理解が深まります。
注意点
モデルキャッシュのクリア
boot* はクラスごとに一度しか実行されないため、テスト環境では注意が必要です。Model::clearBootedModels() で起動済みキャッシュをリセットできます。
// テスト後にモデルのブート状態をリセット
protected function tearDown(): void
{
Model::clearBootedModels();
parent::tearDown();
}
実行順序
同一モデルに複数のトレイトが use されている場合、bootXxx() / initializeXxx() はPHPのトレイト解決順(宣言順)で呼ばれます。依存関係がある場合は use の順序に注意してください。
まとめ
| パターン | 方法 | 適用場面 |
|---|
| クラス起動時処理 | static bootXxx() または #[Boot] | イベント登録、グローバルスコープ追加 |
| インスタンス初期化 | initializeXxx() または #[Initialize] | デフォルト値、キャスト追加 |
トレイトに bootXxx() / initializeXxx() を実装しておくと、モデルへの機能注入が use 一行で済むようになります。パッケージでモデルに機能を追加する際の定番パターンです。