【Laravel】Observerパターンの使い方|Eloquentモデルのライフサイクルイベントを活用する
はじめに
「ユーザー作成時にプロフィール行も作る」「記事更新のたびにキャッシュを無効化する」といった処理を、コントローラや Service に毎回書くと、同じモデル操作のたびに条件分岐が増えていきます。モデルの save() や delete() の前後で共通処理を走らせたいが、モデル本体の booted() にコールバックを書き続けると見通しが悪くなっていませんか。
Observer は、Eloquent モデルのライフサイクルイベント(作成・更新・削除など)に反応する専用クラスです。モデル操作のたびに手動で event() を呼ぶ必要がなく、副作用をモデル本体から切り離せます。
この記事では次の範囲を整理します。
- Eloquent モデルのライフサイクルイベントと Observer の対応関係
- Observer の作成・登録・動作確認
- Event / Listener やモデル内コールバックとの使い分け
- 実装時の注意点と、よくある失敗パターン
細かな API はメジャーバージョンで変わることがあります。手元の composer.json の laravel/framework に合わせ、Eloquent の Observers 章の該当バージョンを照合してください。
背景:ライフサイクルイベントとは
Eloquent モデルは、永続化の前後で次のようなイベントを発火します(公式ドキュメントの Events 節に一覧があります)。
| イベント | タイミング |
|---|---|
creating / created | 新規レコードの INSERT 前 / 後 |
updating / updated | 既存レコードの UPDATE 前 / 後 |
saving / saved | 作成・更新のいずれでも save 前 / 後 |
deleting / deleted | 削除前 / 後 |
restoring / restored | ソフトデリート(論理削除)の復元前 / 後 |
通常の delete だけを扱う場合は deleting / deleted を見れば十分です。restoring / restored はソフトデリートを有効にしたモデル向けです。
creating や updating など「〜ing」系のメソッドで false を返すと、その操作はキャンセルされます。HTTP 入力の検証は Form Request に任せ、永続化直前のモデル固有ルールだけ Observer やコールバックに置く、という分担が一般的です。
Observer は、これらのイベント名と同名のメソッドをクラスに定義し、モデルに登録すると自動で呼ばれます。
実装と検証
Observer の生成
Artisan で Observer クラスを生成します。--model オプションを付けると、対象モデルに合わせたメソッドの雛形が入ります。
php artisan make:observer PostObserver --model=Post
生成されたクラスは、イベント名に対応するメソッドを持ちます。次は created にログ出力を入れる実行可能な例です。
<?php
namespace App\\Observers;
use App\\Models\\Post;
use Illuminate\\Support\\Facades\\Log;
class PostObserver
{
public function created(Post $post): void
{
Log::info('Post created', ['id' => $post->id]);
}
public function updated(Post $post): void
{
// 更新直後の処理(例: キャッシュの無効化)
}
}
Observer の登録
登録方法は Laravel のバージョンとプロジェクト構成によって異なります。代表的な方法は次の 2 つです。
方法 1: モデルに ObservedBy 属性を付ける(Laravel 11 以降)
Laravel 11 以降では、モデルクラスに ObservedBy 属性を付けて Observer を宣言できます。
<?php
namespace App\\Models;
use App\\Observers\\PostObserver;
use Illuminate\\Database\\Eloquent\\Attributes\\ObservedBy;
use Illuminate\\Database\\Eloquent\\Model;
#[ObservedBy([PostObserver::class])]
class Post extends Model
{
//
}
方法 2: observe メソッドで登録する
app/Providers/AppServiceProvider.php の boot メソッドで、従来どおり observe を呼び出す方法も使えます。Laravel 10 以前のプロジェクトや、モデルファイルを触りたくない場合に向いています。
use App\\Models\\Post;
use App\\Observers\\PostObserver;
public function boot(): void
{
Post::observe(PostObserver::class);
}
いずれの方法でも、登録を忘れると Observer は一切動きません。後述の失敗例も参照してください。
動作確認
登録後は、実際にモデルを保存して Observer が呼ばれるか確認します。php artisan tinker で次を実行する方法が手軽です。
php artisan tinker
>>> Post::create(['title' => 'Test', 'body' => 'Hello']);
storage/logs/laravel.log に Post created が出力されていれば、created が動いています。本番相当の確認には Feature テストで Post::factory()->create() 後にログや副作用を assert する方法も使えます。
モデル内コールバックとの比較
モデルの booted メソッド内で static::creating(...) のように書く方法もあります。
protected static function booted(): void
{
static::creating(function (Post $post): void {
// 作成前の処理
});
}
| 方式 | 向いている場面 |
|---|---|
| モデル内コールバック | そのモデルにだけ関係する 1〜2 行の処理 |
| Observer | 複数イベントを扱う、テストやレビューで分離したい、クラスが肥大化しそうなとき |
Observer は「モデル本体から副作用を追い出す」目的で使われることが多いです。
使い分け:Event / Listener との違い
Event / Listener は、アプリ内の任意の出来事を event() で明示的に通知する仕組みです。Observer との違いを表にまとめます。
| 観点 | Observer | Event / Listener |
|---|---|---|
| トリガー | Eloquent の save / delete など | 任意のタイミングで event() を呼ぶ |
| 対象 | 特定モデルのライフサイクル | アプリケーション全体のドメインイベント |
| 発火元の意識 | モデル操作だけで自動 | 明示的なディスパッチが必要 |
同じ要件でも選び方が変わります。
- Post 保存のたびに監査ログ行を 1 件足す → Observer
- 「記事が公開された」を Slack とメールの両方に知らせたい → Event / Listener
両方を併用しても問題ありません。
実装時の注意点
同期実行であること
Observer のメソッドは、モデルの save / delete と同じリクエスト内で同期的に実行されます。外部 API 呼び出しや重い処理をそのまま書くと、レスポンスが遅くなります。時間のかかる処理は Laravel の Queue に載せる Job へ dispatch する設計を検討してください。
use App\\Jobs\\SyncSearchIndex;
public function updated(Post $post): void
{
SyncSearchIndex::dispatch($post);
}
updated 内で再度 save() しない
updated や saved の中で同じモデルを save() すると、再度イベントが発火し、意図しないループや二重処理の原因になります。属性を変更する必要がある場合は、updating / saving の段階で行うか、saveQuietly() でイベントを抑止する方法を 公式ドキュメント で確認してください。
トランザクションとの関係
DB トランザクション内でモデルを保存した場合、Observer もトランザクション内で動きます。
ロールバック時は DB 上の変更は取り消されます。
Observer 内で外部 API を呼ぶと、DB と外部サービス間の不整合を招きます。
コミット後にだけ外部 API を叩きたい場合は、次のように DB::afterCommit() で遅延させます。
use Illuminate\\Support\\Facades\\DB;
public function created(Post $post): void
{
DB::afterCommit(function () use ($post): void {
// トランザクション確定後にだけ実行される
});
}
失敗例:Observer の登録忘れで処理が動かない
make:observer でクラスを生成し、メソッドを実装しただけでは、Observer は登録されないため動きません。ObservedBy 属性の付け忘れ、または Post::observe(...) の呼び出し忘れがあると、save() しても何も起きません。
この状態は例外を投げず無反応のため、気づきにくいです。新しい Observer を追加したら、前述の tinker やテストで created が呼ばれることを確認すると安全です。
学び:モデル操作に副作用を載せすぎない
実装時の注意を踏まえ、Observer に何を載せるべきかを整理します。Observer は「モデルが永続化されたら必ず走る」という性質上、save() するだけで通知・外部 API・Job が連鎖し、呼び出し元が意識していない処理が増えやすくなります。
- Observer には、そのモデルの永続化に密接な処理だけを置く(例: スラッグ生成、監査用の更新日時の補完)
- 複数モデルにまたがるワークフローや、ユーザー操作起点の通知は Event / Listener や Application Service に委ねる
- 重い処理は Job へ切り出し、Observer からは dispatch だけ行う
まとめ
- Observer は Eloquent のライフサイクルイベント(
creating/createdなど)に反応するクラスである。 - Laravel 11 以降は
ObservedBy属性、またはModel::observe()でモデルに紐づける。 - 同期実行のため、重い処理は Job への dispatch を検討する。
次に試せること
make:observerでクラスを生成し、ObservedBy(Laravel 11+)またはAppServiceProvider::bootで登録する。createdにLog::infoを入れ、tinker でPost::create(...)を実行する。storage/logs/laravel.logにログが出ることを確認する。- 同じ要件をモデル内
bootedと Observer で書き分け、どちらが読みやすいか比較する。 - Laravel 公式ドキュメントの Muting Events 節 を読み、
saveQuietly()やwithoutEvents()の使いどころを把握する。






