【Laravel】Observerパターンの使い方|Eloquentモデルのライフサイクルイベントを活用する

はじめに

「ユーザー作成時にプロフィール行も作る」「記事更新のたびにキャッシュを無効化する」といった処理を、コントローラや Service に毎回書くと、同じモデル操作のたびに条件分岐が増えていきます。モデルの save()delete() の前後で共通処理を走らせたいが、モデル本体の booted() にコールバックを書き続けると見通しが悪くなっていませんか。

Observer は、Eloquent モデルのライフサイクルイベント(作成・更新・削除など)に反応する専用クラスです。モデル操作のたびに手動で event() を呼ぶ必要がなく、副作用をモデル本体から切り離せます。

この記事では次の範囲を整理します。

  • Eloquent モデルのライフサイクルイベントと Observer の対応関係
  • Observer の作成・登録・動作確認
  • Event / Listener やモデル内コールバックとの使い分け
  • 実装時の注意点と、よくある失敗パターン

細かな API はメジャーバージョンで変わることがあります。手元の composer.jsonlaravel/framework に合わせ、Eloquent の Observers 章の該当バージョンを照合してください。

背景:ライフサイクルイベントとは

Eloquent モデルは、永続化の前後で次のようなイベントを発火します(公式ドキュメントの Events 節に一覧があります)。

イベントタイミング
creating / created新規レコードの INSERT 前 / 後
updating / updated既存レコードの UPDATE 前 / 後
saving / saved作成・更新のいずれでも save 前 / 後
deleting / deleted削除前 / 後
restoring / restoredソフトデリート(論理削除)の復元前 / 後

通常の delete だけを扱う場合は deleting / deleted を見れば十分です。restoring / restored はソフトデリートを有効にしたモデル向けです。

creatingupdating など「〜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.phpboot メソッドで、従来どおり 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.logPost 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 との違いを表にまとめます。

観点ObserverEvent / 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() しない

updatedsaved の中で同じモデルを 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 を検討する。

次に試せること

  1. make:observer でクラスを生成し、ObservedBy(Laravel 11+)または AppServiceProvider::boot で登録する。
  2. createdLog::info を入れ、tinker で Post::create(...) を実行する。
  3. storage/logs/laravel.log にログが出ることを確認する。
  4. 同じ要件をモデル内 booted と Observer で書き分け、どちらが読みやすいか比較する。
  5. Laravel 公式ドキュメントの Muting Events 節 を読み、saveQuietly()withoutEvents() の使いどころを把握する。

Source