2児のパパ系Webエンジニア
の開発日記
A Dad-of-Two Engineer's Development Diary

Larastan 3.12 のキュー用ルールを有効にしたら、トランザクション内の dispatch に afterCommit を付け忘れていた話

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 のドキュメントに載っているルールと、有効にするためのパラメータ名をまとめるとこうです。

ルールパラメータ検出すること
UniqueJobDeclaresUniqueForRulecheckUniqueJobUniqueForShouldBeUnique のジョブが uniqueFor を宣言していない
UniqueJobDeclaresUniqueIdRulecheckUniqueJobUniqueId引数を持つ ShouldBeUnique のジョブが uniqueId を宣言していない
NoBatchedUniqueJobRulenoBatchedUniqueJobShouldBeUnique のジョブを Bus::batch() や Bus::bulk() で投げている
JobWithModelPropertyDeclaresSerializesModelsRulecheckJobSerializesModelspublic プロパティにモデルを持つジョブが SerializesModels を使っていない
BatchedJobIsBatchableRulecheckBatchedJobIsBatchableBus::batch() で投げるジョブが Batchable を使っていない
BatchableJobChecksCancellationRulecheckBatchableJobChecksCancellationBatchable のジョブがキャンセルを確認していない
JobDispatchedInTransactionUsesAfterCommitRulecheckDispatchInTransactionAfterCommitDB::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 だけでも入れてみてください。

正直、もっと早く欲しかったです・・!

※ バージョン番号・料金・機能は今後も変わる可能性があります。最新情報は公式サイトをご確認ください👀

参考になったらシェアしてもらえるとうれしいです🙌

Xでポスト Threads はてブ LINE