Larastan 3.12 のキュー用ルールを有効にしたら、トランザクション内の dispatch に afterCommit を付け忘れていた話
🙌 結論から
Laravel でキューを使っているなら、Larastan 3.12 で増えたジョブ用のルールは有効にしておいたほうがいいです。
2026年9月10日リリースの Larastan 3.12.0 で、キュー済みジョブ向けのルールが7つ追加されました。
どれもデフォルトではオフのオプトインなので、phpstan.neon に1行ずつ足して有効にします。
私のプロジェクトでは、DB::transaction() の中で dispatch しているのに afterCommit() を付けていない箇所がいくつか見つかりました(・_・;)
まさに「たまにだけ落ちるジョブ」の原因だった所です。
👀 たまにだけ出るジョブのエラーに悩んでいた
ある Laravel の案件で、注文を確定したら確認メールのジョブを投げる処理がありました。
ほとんどは問題なく動くのに、たまにだけジョブが No query results for model [App\Models\Order] で失敗するんですよね。。
ローカルでは再現しないし、リトライすると成功するので、しばらく放置していました。
原因は、トランザクションがコミットされる前にワーカーがジョブを拾っていたことでした。
ジョブの中で注文を取りに行った時点では、まだ DB に注文が無い、というわけです。
こういう「タイミング次第で起きる」バグは、テストより静的解析で見つけるほうが確実だと、今回あらためて思いました。
💡 追加された7つのルール
Larastan のドキュメントに載っているルールと、有効にするためのパラメータ名をまとめるとこうです。
| ルール | パラメータ | 検出すること |
|---|---|---|
| UniqueJobDeclaresUniqueForRule | checkUniqueJobUniqueFor | ShouldBeUnique のジョブが uniqueFor を宣言していない |
| UniqueJobDeclaresUniqueIdRule | checkUniqueJobUniqueId | 引数を持つ ShouldBeUnique のジョブが uniqueId を宣言していない |
| NoBatchedUniqueJobRule | noBatchedUniqueJob | ShouldBeUnique のジョブを Bus::batch() や Bus::bulk() で投げている |
| JobWithModelPropertyDeclaresSerializesModelsRule | checkJobSerializesModels | public プロパティにモデルを持つジョブが SerializesModels を使っていない |
| BatchedJobIsBatchableRule | checkBatchedJobIsBatchable | Bus::batch() で投げるジョブが Batchable を使っていない |
| BatchableJobChecksCancellationRule | checkBatchableJobChecksCancellation | Batchable のジョブがキャンセルを確認していない |
| JobDispatchedInTransactionUsesAfterCommitRule | checkDispatchInTransactionAfterCommit | DB::transaction() の中の dispatch に afterCommit の指定が無い |
私が一番ありがたかったのは、最後の checkDispatchInTransactionAfterCommit です。
->afterCommit() をチェーンするか、ジョブ側のプロパティやインターフェースで指定していればエラーになりません。
バッチのキャンセル確認も、$this->batch()?->cancelled() を書くか SkipIfBatchCancelled ミドルウェアを付ければ通ります。
ちなみに 3.12.0 では、モデルの toArray() の戻り値を配列の形まで推論してくれるようになっていて、API レスポンスを組み立てる所の型もかなり正確になりました👍
🔧 私のphpstan.neonはこれ
そのまま真似できるように、今の設定を貼っておきます。
includes:
- vendor/larastan/larastan/extension.neon
- phpstan-baseline.neon
parameters:
level: 6
paths:
- app/
# Larastan 3.12.0 で追加されたキュー用ルール(すべてオプトイン)
checkDispatchInTransactionAfterCommit: true
checkJobSerializesModels: true
checkUniqueJobUniqueFor: true
checkUniqueJobUniqueId: true
noBatchedUniqueJob: true
checkBatchedJobIsBatchable: true
checkBatchableJobChecksCancellation: true
先に出た afterCommit の指摘は、こう直しました。
DB::transaction(function () use ($request) {
$order = Order::create($request->validated());
// コミットされてからキューに入るようにする
SendOrderConfirmation::dispatch($order)->afterCommit();
});
同じジョブを何カ所からも投げているなら、ジョブクラスに ShouldQueueAfterCommit を実装しておくほうが漏れないです。
私は「毎回 DB を読みに行くジョブ」はインターフェース側、それ以外は呼び出し側で ->afterCommit()、と分けています(^^)
なお、3.12.1 から PHPStan 2.2.14 以上が必須になっているので、Larastan だけ上げて PHPStan が古いままだとインストールで止まります。
composer update larastan/larastan phpstan/phpstan -W のように一緒に上げるのが楽でした。
🤔 既存プロジェクトはbaselineから入れる
ルールを7つ全部オンにすると、古いプロジェクトだと指摘がそれなりの数出ます。
全部その場で直そうとすると PR がふくらむので、私は baseline で一度「今ある指摘」を棚上げしました。
vendor/bin/phpstan analyse --generate-baseline
これで phpstan-baseline.neon ができて、今ある指摘は無視され、新しく書いたコードだけがチェックされます。
上の includes に phpstan-baseline.neon が入っているのはこのためです。
ただ、afterCommit の指摘だけは baseline に入れずに先に直しました。
これは実際に本番で落ちていたので、棚上げする理由がなかったです( `・ω・´)
残りは、ジョブを触るついでに baseline から1件ずつ消していく運用にしています。
静的解析の導入は husky と lint-staged の記事 で書いたコミット前のチェックとも組み合わせられるので、CI まで待たずに気づけるようになりました。
パパ系Webエンジニア〜愛用ガジェット一覧 🛍️
この記事を書いている作業環境のガジェットを楽天ROOMにまとめています ✨ 在宅エンジニアのデスク作りの参考にどうぞ 🙌
🙌 まとめ
Larastan 3.12 のキュー用ルールは、タイミング依存で再現しにくいジョブのバグを、書いた瞬間に教えてくれるルールでした。
phpstan.neon に7行足すだけなので、キューを使っている Laravel プロジェクトならまず checkDispatchInTransactionAfterCommit だけでも入れてみてください。
正直、もっと早く欲しかったです・・!
※ バージョン番号・料金・機能は今後も変わる可能性があります。最新情報は公式サイトをご確認ください👀