11
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

EC-CUBEで「KernelEvents::TERMINATE」を利用して重たい処理を裏で処理したい

Posted at

なぜ KernelEvents::TERMINATE なのか

KernelEvents::TERMINATE を利用すれば、重い後処理(高解像度商品画像の一括リサイズ+WebP生成・100万行超CSVレポート生成→S3アップロード・外部WMS在庫同期API呼び出しなど)をユーザー待ち時間ゼロで実行できます(Lambdaでやれなどは一旦置いておきます!)

決済サービスのWebhook受信時のように数秒以内に 200 OK を返す必要があるケースでも、重い処理をレスポンス後に安全に遅延実行 できます

仕組みとメリット

  • Symfony Kernel はフレームワークの心臓部であり、Request を受け取って Response を返すまでの一連のフローを統括
  • EC-CUBE 4.3 は Symfony 6 をベースとしているため、Symfony Kernel をそのまま利用可能
  • KernelEvents::TERMINATE イベントは、レスポンス送信後に一度だけ発火し、ここでの処理はユーザーの待ち時間に影響を与えない

Symfony Kernel のイベント

イベント定数 発火タイミング & 主用途
KernelEvents::REQUEST ルーティング実行前の最初のフック Request への情報追加や、早期に Response を返して処理を打ち切るセキュリティ判定などに使用
KernelEvents::CONTROLLER コントローラ決定後・実行前 リクエスト属性に応じた初期化や、ControllerEvent::setController() によるコントローラ差し替えが可能
KernelEvents::CONTROLLER_ARGUMENTS コントローラ呼び出し直前 ArgumentResolver が用意した引数配列を調整・差し替える場として利用
KernelEvents::VIEW コントローラが Response 以外(配列・オブジェクトなど)を返した場合のみ発火 その戻り値を基に Response を生成してセットする。
KernelEvents::RESPONSE Response オブジェクトが確定した直後 ヘッダ追加・Cookie 付与・HTML 末尾へのスクリプト挿入など、送信前の最終加工ポイント
KernelEvents::FINISH_REQUEST kernel.response 直後に発火 翻訳ロケールやセキュリティコンテキストなど グローバル状態のリセット に利用
KernelEvents::TERMINATE レスポンス送信後 応答時間に影響させたくない重い処理を実行 実装時はワーカー/Messenger で非同期化すると堅牢
KernelEvents::EXCEPTION HttpKernel::handle() 内で例外が発生した瞬間に発火 例外種別に応じたエラーページ生成やロギング、再スロー前のカスタム処理に活用

TERMINATE イベントが後処理に最適な理由

  • TERMINATE イベントは レスポンス送信完了直後に 1 回だけ 発火 ユーザーはすでに HTML を受け取っているため、後処理で数秒かかっても UX は影響を受けない
  • リクエスト時と同一の DI コンテナDoctrine エンティティマネージャ が生存しているため、後処理でもトランザクション整合性が保たれる
  • Laravel でいうところの terminate ミドルウェアに相当するかと思いますが、Symfony/EC-CUBE では イベントリスナ/サブスクライバ 方式を採用するため、処理単位で疎結合に保てる

コード例

<?php
// app/Customize/EventSubscriber/AfterResponseSubscriber.php
namespace Customize\EventSubscriber;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\TerminateEvent;
use Symfony\Component\HttpKernel\KernelEvents;

final class AfterResponseSubscriber implements EventSubscriberInterface
{
    public static function getSubscribedEvents(): array
    {
        return [KernelEvents::TERMINATE => 'onTerminate'];
    }

    public function onTerminate(TerminateEvent $event): void
    {
        $request = $event->getRequest();
        $route   = $request->attributes->get('_route');

        /** 1) 決済システム Webhook:200 OK 即時返却後の裏処理 */

        /** 2) 受注確定後の在庫同期(WMS 連携) */

        /** 3) 会員登録完了時のCRM/Slack 通知 */
    }
}

注意点

  • TERMINATEイベントでの例外は通常のエラーページに表示されないため、適切なログ記録が必要
  • 完全な「待ち時間ゼロ」はPHP-FPM/FrankenPHP限定(他のSAPIでは部分的効果)
  • EntityManagerが例外で閉じている可能性があるためisOpen()チェックを推奨
  • 長時間処理はワーカープロセスを占有するため、重い処理はMessenger等に委譲
  • DB接続タイムアウトメモリリーク(Doctrineエンティティ蓄積)に注意
  • 並行実行時の競合(ファイル書き込み、在庫更新等)では排他制御が必要
  • ブラウザに表示されない処理のため、ログ出力とパフォーマンス監視が必須

まとめ

KernelEvents::TERMINATE を活用すれば、ユーザー体験に影響を与えずにビジネスロジックの後処理を安全に実行 できます。
実際に私も最近、決済システムの Webhook 通知で「数秒以内に 200 OK を返す」要件を満たすため、本イベントを用いて重い後処理をレスポンス後に回しました。
パフォーマンス改善や可観測性向上を図る際は、まずこの TERMINATE イベントへの切り出しを検討いただければと思います。

11
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?