Laravel 12 イベントとリスナー完全ガイド:イベント駆動アーキテクチャの実装と面接対策

Laravel 12のイベント・リスナーシステムを徹底解説。自動検出機能、キュー処理、トランザクション対応、ユニークリスナーの実装方法から、技術面接でよく問われるイベント駆動アーキテクチャの質問まで網羅的に解説します。

Laravel イベントとリスナーのイベント駆動アーキテクチャ図解

Laravelのイベントとリスナーは、オブザーバーパターンを実装し、アプリケーションの各コンポーネントを疎結合に保つ仕組みです。ユーザーが注文を行うとOrderShippedイベントが発火し、通知送信、分析データ収集、在庫更新といった処理が、それぞれ独立したリスナーで実行されます。

Laravel 12 のイベント自動検出

Laravel 12では、app/Listenersディレクトリ内のリスナーが自動的に検出されます。イベントクラスをタイプヒントしたhandleまたは__invokeメソッドを持つクラスは、設定ファイルなしで自動登録されます。

Laravel 12 におけるイベント自動検出の仕組み

Laravel 11以降、EventServiceProviderは廃止されました。LaravelはListenersディレクトリをスキャンし、リフレクションを使用してhandleメソッドのタイプヒントから、各リスナーが応答するイベントを特定します。

app/Listeners/SendOrderConfirmation.phpphp
<?php

namespace App\Listeners;

use App\Events\OrderShipped;

class SendOrderConfirmation
{
    public function handle(OrderShipped $event): void
    {
        // $event->order->user に確認メールを送信
    }
}

上記のリスナーは、OrderShippedイベントに対して自動的に登録されます。サービスプロバイダでの手動バインディングは不要です。これによりボイラープレートコードが削減され、イベントとリスナーの関係が実装コードの近くに保たれます。

登録済みリスナーを確認するには、php artisan event:listを実行します。本番環境では、php artisan event:cacheでイベントマニフェストをキャッシュし、リクエストごとのディレクトリスキャンを省略できます。

Artisan によるイベントとリスナーの作成

Laravelは、イベントとリスナーの雛形を生成するArtisanコマンドを提供しています。

bash
# イベントクラスを生成
php artisan make:event OrderShipped

# イベントに紐づくリスナーを生成
php artisan make:listener SendShipmentNotification --event=OrderShipped

生成されるイベントクラスは3つのトレイトを使用します。Dispatchableは静的dispatch()メソッドを提供し、InteractsWithSocketsはブロードキャスト用、SerializesModelsはリスナーがキューに入る際のEloquentモデルのシリアライズを担当します。

app/Events/OrderShipped.phpphp
<?php

namespace App\Events;

use App\Models\Order;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Queue\SerializesModels;

class OrderShipped
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public function __construct(
        public Order $order,
    ) {}
}

イベントクラスはデータコンテナです。Orderモデルを保持するだけで、ビジネスロジックはリスナーに記述します。

コントローラとサービスからのイベント発火

イベントクラスの静的dispatch()メソッドを使用してイベントを発火します。

app/Http/Controllers/OrderController.phpphp
<?php

namespace App\Http\Controllers;

use App\Events\OrderShipped;
use App\Models\Order;
use Illuminate\Http\Request;

class OrderController extends Controller
{
    public function ship(Request $request, Order $order)
    {
        // 出荷処理...
        
        OrderShipped::dispatch($order);
        
        return redirect()->route('orders.index');
    }
}

条件付きでイベントを発火する場合は、dispatchIf()またはdispatchUnless()を使用します。

php
// 追跡番号がある場合のみ発火
OrderShipped::dispatchIf($order->tracking_number !== null, $order);

Laravelの面接対策はできていますか?

インタラクティブなシミュレーター、flashcards、技術テストで練習しましょう。

重い処理のためのキュー対応リスナー

メール送信、外部API呼び出し、重い計算処理を行うリスナーは、非同期で実行する必要があります。ShouldQueueインターフェースを実装することで、リスナーをキューにプッシュできます。

app/Listeners/SendShipmentNotification.phpphp
<?php

namespace App\Listeners;

use App\Events\OrderShipped;
use Illuminate\Contracts\Queue\ShouldQueue;

class SendShipmentNotification implements ShouldQueue
{
    public $queue = 'notifications';
    public $delay = 60; // 処理前に60秒待機

    public function handle(OrderShipped $event): void
    {
        // $event->order->user に通知を送信
    }
}

キュー接続と名前は$connection$queueプロパティでカスタマイズするか、ランタイムメソッドを使用します。

php
public function viaConnection(): string
{
    return 'redis';
}

public function viaQueue(): string
{
    return $this->event->order->priority === 'high' 
        ? 'high-priority' 
        : 'default';
}

shouldQueue による条件付きキューイング

イベントデータに基づいてキューイングを判断する場合があります。

php
public function shouldQueue(OrderShipped $event): bool
{
    // 100ドル以上の注文のみキューに入れる
    return $event->order->total >= 10000;
}

データベーストランザクションとイベントタイミング

トランザクション内で発火されたキュー対応リスナーは、トランザクションがコミットされる前に処理される可能性があり、データ不整合エラーが発生することがあります。2つの解決策があります。

1. グローバル設定 config/queue.phpで設定する方法:

php
'connections' => [
    'redis' => [
        'after_commit' => true,
    ],
],

2. リスナー単位 ShouldQueueAfterCommitを使用する方法:

php
use Illuminate\Contracts\Queue\ShouldQueueAfterCommit;

class UpdateInventory implements ShouldQueueAfterCommit
{
    public function handle(OrderShipped $event): void
    {
        // 注文のクエリは安全、トランザクションはコミット済み
    }
}

イベント自体に対しては、イベントクラスでShouldDispatchAfterCommitを実装します。

php
use Illuminate\Contracts\Events\ShouldDispatchAfterCommit;

class OrderShipped implements ShouldDispatchAfterCommit
{
    // トランザクションコミット後にのみイベントが発火
}

重複を防ぐユニークリスナー

ShouldBeUniqueインターフェースは、同一のリスナーが処理中に重複してキューに入ることを防ぎます。

php
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;

class ProcessLicenseKey implements ShouldQueue, ShouldBeUnique
{
    public $uniqueFor = 3600; // ロックは1時間後に期限切れ

    public function uniqueId(LicensePurchased $event): string
    {
        return 'license:' . $event->license->id;
    }

    public function handle(LicensePurchased $event): void
    {
        // ライセンスキーを生成して割り当て
    }
}

同じライセンスIDで複数のイベントが短時間に発火された場合、最初のリスナーのみが処理されます。後続の試行はロックが保持されていることを検出してキューイングをスキップします。

関連イベントのためのイベントサブスクライバ

複数の関連イベントが共通の処理ロジックを共有する場合、イベントサブスクライバは複数のハンドラを1つのクラスにまとめます。

app/Listeners/UserActivitySubscriber.phpphp
<?php

namespace App\Listeners;

use Illuminate\Auth\Events\Login;
use Illuminate\Auth\Events\Logout;
use Illuminate\Events\Dispatcher;

class UserActivitySubscriber
{
    public function handleLogin(Login $event): void
    {
        // ログイン成功をログに記録、last_loginタイムスタンプを更新
    }

    public function handleLogout(Logout $event): void
    {
        // ログアウトをログに記録、セッションデータをクリア
    }

    public function subscribe(Dispatcher $events): array
    {
        return [
            Login::class => 'handleLogin',
            Logout::class => 'handleLogout',
        ];
    }
}

サブスクライバも自動検出ルールに従います。ハンドラメソッドが適切にタイプヒントされたパラメータを持っていれば、Laravelが自動的に登録します。

Union Types による複数イベントの処理

PHP 8のUnion Typesを使用して、1つのリスナーで複数のイベントタイプに応答できます。

php
public function handle(OrderShipped|OrderCancelled $event): void
{
    match (true) {
        $event instanceof OrderShipped => $this->notifyShipped($event),
        $event instanceof OrderCancelled => $this->notifyCancelled($event),
    };
}

このパターンは、異なるイベントタイプで類似のロジックをたどる通知に適しています。

Event::defer によるイベントの遅延発火

Laravel 12では、コードブロックの完了後までイベント発火を遅延させるEvent::defer()が導入されました。これにより、リスナーがすべての関連レコードにアクセスできることが保証されます。

php
use Illuminate\Support\Facades\Event;

Event::defer(function () {
    $user = User::create(['name' => 'John']);
    $user->posts()->create(['title' => 'Welcome']);
});
// UserとPostの作成イベントがここで発火

クロージャ内で例外が発生した場合、遅延されたイベントは発火されません。

今すぐ練習を始めましょう!

面接シミュレーターと技術テストで知識をテストしましょう。

Laravel イベント・リスナーの面接質問

技術面接では、イベント駆動アーキテクチャの理解度がよく問われます。以下は、経験豊富なLaravel開発者を見分ける質問です。

Q: Laravel 12はEventServiceProviderなしでどのようにイベントリスナーを登録しますか?

Laravelはリフレクションを使用してapp/Listenersをスキャンします。handle()または__invoke()メソッドのタイプヒントを読み取り、各リスナーが応答するイベントを特定します。本番環境ではphp artisan event:cacheでマニフェストをキャッシュします。

Q: リスナーがShouldQueueを実装すべきタイミングは?

処理に数ミリ秒以上かかる場合です。メール送信、外部API呼び出し、レポート生成、ファイル処理などが該当します。同期リスナーはHTTPレスポンスをブロックします。

Q: ShouldQueueAfterCommitが解決する問題は何ですか?

キュー対応リスナーは、データベーストランザクションがコミットされる前に処理される可能性があります。リスナーが作成されたばかりのレコードをクエリすると、データがまだコミットされていないため失敗します。ShouldQueueAfterCommitはトランザクション完了までキューへのディスパッチを遅延させます。

Q: 同一イベントの重複処理をどのように防ぎますか?

ShouldBeUniqueを実装します。uniqueId()を定義してイベントデータに基づくキャッシュキーを返します。リスナーはアトミックロックを取得し、ロックが保持されている場合は重複ディスパッチがスキップされます。

Q: イベントリスナーとイベントサブスクライバの違いは何ですか?

リスナーは1つのイベントタイプを処理します。サブスクライバは、複数の関連イベントのハンドラを1つの場所に登録するクラスで、認証イベントや監査ログのグループ化に有用です。

スケーラブルアプリケーションにおけるLaravelイベントの意義

  • イベントはコンポーネントを疎結合にし、通知や分析などの機能が独立して進化できるようにする
  • キュー対応リスナーは重い処理をオフロードし、HTTPレスポンスを高速に保つ
  • ShouldQueueAfterCommitはキューワーカーとデータベーストランザクション間の競合状態を防ぐ
  • Laravel 12の自動検出は設定のオーバーヘッドを排除し、リスナーロジックを実装に近く保つ
  • イベントサブスクライバは、ユーザーアクティビティトラッキングなどの一般的なパターンの関連イベント処理を統合し、ファイルの散在を減らす
  • SharpSkill/technologies/laravel/interview-questions/events-listenersモジュールでは、これらのパターンをインタラクティブな質問で学習できます
今日のチャレンジ

Laravel のバグを見つけられますか

実際のコード、隠れたバグ、1日1回。アカウントなしで試せます。

Anthony Fillion-Maillet

執筆

Anthony Fillion-Maillet

SharpSkill 創業者

10 年以上フルスタック開発に携わっています。SharpSkill を運営し、ここで公開される内容に責任を負っています。

2026年8月20日 更新

タグ

#laravel
#events
#listeners
#event-driven-architecture
#php
#interview-questions

共有

関連記事