なぜ 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
イベントへの切り出しを検討いただければと思います。