Claude Media
Claude CodeでPHPStan(Larastan)の型エラーを減らす — baselineとlevelの上げ方

Claude CodeでPHPStan(Larastan)の型エラーを減らす — baselineとlevelの上げ方

Claude Codeに任せるLaravelの静的解析運用を、PHPStanのlevel段階、baseline、inline ignore、CLAUDE.mdとhookの設定例でまとめます。

Claude CodeにPHPStanのエラーを直させると、最短の逃げ道は2つあります。--generate-baseline で再生成するか、@phpstan-ignore-next-line を貼る方法です。どちらも件数は減りますが、型の問題は残ります。そうならないように、levelの上げ方、baselineの扱い、ignoreの書き方を先にCLAUDE.mdで決めておきます。

この記事は、LaravelプロジェクトでLarastanを使う前提で、その取り決めと設定を具体例で示します。Laravel全般の環境づくりはClaude CodeでLaravelアプリを開発する手順にあります。

運用の骨格は3つの取り決め

PHPStanは「levelを1段ずつ上げる」「古いエラーはbaselineに隔離する」「新しいエラーは直すか、理由付きでignoreする」の3つで回ります。Claude Codeに渡す指示も、この3つに対応させます。

取り決め守らせる内容破られたときの症状
levelは1段ずつ守らせる内容上げる前に件数を測る破られたときの症状数千件のエラーを一度に抱える
baselineは縮める一方守らせる内容再生成で件数を増やさない破られたときの症状新しい型エラーが負債に混ざる
ignoreは識別子と理由つき守らせる内容行単位の一括無視を使わない破られたときの症状何を無視したか後から追えない

LarastanをComposerで入れて設定を置く

LarastanはLaravel向けのPHPStan拡張です。READMEによると、静的解析と言いつつアプリのコンテナを起動して、実行時にしか決まらない型を解決します。Larastan 3.xの要件は、PHP 8.2以上とLaravel 11.15以上です。

composer require --dev "larastan/larastan:^3.0"

設定ファイルはプロジェクト直下の phpstan.neon か phpstan.neon.dist に置きます。READMEの例は次の形です。

includes:
    - vendor/larastan/larastan/extension.neon
    - vendor/nesbot/carbon/extension.neon
parameters:
    paths:
        - app/
    level: 5

PHPStan本体の設定ガイドでは、phpstan.neon.dist をバージョン管理に入れ、個人やCIの上書きは .gitignore 済みの phpstan.neon に書く運用が通例です。Claudeには、共有設定の .dist を編集対象とし、個人用の phpstan.neon は触らせない線引きを渡します。

解析対象は自分で書いたコードだけにします。公式は vendor を解析に含める必要はないとしています。

levelは11段階 — 段ごとの中身を知って1段ずつ上げる

levelは0から10までの11段階です。既定は0で、設定ファイルを指定した場合はlevelも必ず指定します。levelは累積なので、5で動かせば0〜4のチェックも含まれます。

level追加されるチェック
0〜2追加されるチェック未定義のクラス・関数・メソッド、引数の個数、未定義の可能性がある変数、全式でのメソッド存在確認、PHPDocの検証
3〜5追加されるチェック戻り値の型、プロパティへの代入型、到達しないコードなどのデッドコード、引数の型
6〜8追加されるチェック型宣言の欠落、union型の一部にしかないメソッド呼び出し、nullable型へのアクセス
9〜10追加されるチェック明示的な mixed の厳格化(9)、暗黙の mixed まで報告(10)

引数の型エラーは5で初めて出ます。levelが2のままだと、間違った型の引数を渡すバグは報告されません。

上げる手順をClaudeに渡す

1段上げるたびに同じ流れを踏むと、判断が一定します。

  1. 現在のlevelで、エラーが0件であることを確認させる
  2. --level に1つ大きい値を渡して実行し、件数だけを報告させる
  3. 件数が少なければ直す。多ければbaselineに回す
  4. 設定ファイルのlevelを上げ、必要ならbaselineを生成する
  5. baselineの件数を、ファイル単位で減らしていく

2の確認は、設定ファイルを書き換えずにできます。

vendor/bin/phpstan analyse --level 6 --no-progress

--level max は最上位levelの別名です。公式は、PHPStan更新のたびに最上位が追加される可能性があり、更新時に大量のエラーを直す負担が出うる、と注記しています。Claudeに任せる運用では、数字を明示した方が差分を追いやすくなります。

baselineは「古い負債の隔離」に使う

baselineは、現在のエラー一覧を「基準」として記録し、以降の実行で報告させない仕組みです。新しく書くコードと変えたコードのエラーだけが残ります。公式は、数十から数百件のエラーを今は直せない場合に向くとしています。1万5千件のような規模では、設定の見直しか低いlevelでの運用を勧めています。

vendor/bin/phpstan analyse --level 6 --generate-baseline

出力先は何も指定しなければ phpstan-baseline.neon です。ファイルにはエラーメッセージの正規表現、件数、対象ファイルが並びます。

parameters:
    ignoreErrors:
        -
            message: "#^Anonymous function has an unused use \\$container\\.$#"
            count: 2
            path: src/Command/CommandHelper.php

生成したファイルは設定ファイルから読み込みます。

includes:
    - phpstan-baseline.neon

ここで押さえたいのは件数の持ち方です。baselineは「このファイルにこのメッセージが2件」としか覚えていません。行番号も理由も残りません。

Claudeが直したあとに起きること

baselineに載っているエラーをClaudeが直すと、そのエントリは「該当なし」になります。PHPStanはこの状態を報告し、パターンの削除を求めます。報告を止める reportUnmatchedIgnoredErrors: false もありますが、止めると直し漏れの目印が消えます。

直したあとの更新は、再生成ではなく差分の確認で判断します。再生成した結果、baselineの行が減っているだけなら正しい縮小です。増えている場合は、新しいエラーを隠したことになります。

新しいエラーは直すか、理由付きでignoreする

公式は、新しいエラーをbaselineの再生成で逃がす運用に3つの難点を挙げています。解決不能なエラーが増えてbaselineが消えなくなること。行番号が残らないこと。理由を書けないことです。

ignoreが必要なときは、行内の @phpstan-ignore を使います。エラー識別子が必須で、カッコ内に理由を書けます。

$this->mailer->send($message); // @phpstan-ignore argument.type (外部SDKのPHPDocが不正確)

識別子は、既定のtable形式の出力で🪪の横に出ます。Claudeに「識別子を確認してから書く」と指示すれば、argument.type のような識別子を推測で書く事態を避けられます。

理由なしのignoreを機械で止める

reportIgnoresWithoutComments を true にすると、理由のないignoreを報告します。@phpstan-ignore-line と @phpstan-ignore-next-line も使えなくなります。公式の記載では、この設定はPHPStan 2.1.41から利用できます。

parameters:
    reportIgnoresWithoutComments: true

LarastanのREADMEには @phpstan-ignore-next-line の例が載っています。上の設定を有効にすると、その書き方はそのままでは通りません。Claudeが参考にしたREADMEの例と設定が食い違わないよう、CLAUDE.mdで書き方を明示します。

Laravelの動的な機能で出るエラーには、Larastan側に「無視してよい代表例」のページがあります。魔法的な挙動をPHPStanが読めない箇所は、まずそこに載っているか確認させます。

baselineを縮める依頼は「1ファイル・1識別子」で出す

baselineの件数を減らす作業は、Claudeに丸ごと任せるより範囲を切った方が確認しやすくなります。エントリは「メッセージ・件数・ファイル」の組なので、そのまま依頼の単位になります。

phpstan-baseline.neon の先頭から3エントリを選び、対象ファイルの
エラーを型宣言かPHPDocで直してください。直したエントリは
baselineから削除し、phpstan analyse が0件になることを確認します。
baselineに新しい行を足す変更は行わないでください。

終わったら git diff phpstan-baseline.neon で削除行だけが並んでいるかを見ます。1回の依頼で3エントリ前後なら、レビューも数分で済みます。直せないエントリが出たら、その理由を @phpstan-ignore のカッコ内に書き、baselineからは外す選択もあります。

CLAUDE.mdに書く具体例

ここまでの取り決めを、CLAUDE.mdに検証できる形で書きます。公式のmemoryドキュメントは、曖昧な指示より検証可能な具体的指示を勧めています。

## 静的解析(PHPStan / Larastan)
- 実行: `vendor/bin/phpstan analyse --no-progress`
- 現在のlevelは `phpstan.neon.dist` が正。勝手に上げない
- levelを上げる提案は `--level N+1` の件数を測って報告するところまで
- `--generate-baseline` は実行しない。baselineの更新は私が指示したときだけ
- 直したら `phpstan-baseline.neon` の該当エントリを削除する(行を増やさない)
- ignoreは `// @phpstan-ignore <識別子> (理由)` の形のみ
- `@phpstan-ignore-line` / `-next-line` は使わない
- 型は先にPHPDocと型宣言で直す。ignoreは最後の手段

CLAUDE.mdは200行を目安に短く保ちます。静的解析の細部が増えたら、PHPファイルにだけ効くパス限定ルール(.claude/rules/ の paths: フロントマター)へ切り出せます。

「baselineを再生成させない」という禁止は、文章だけでは破られる余地があります。確実に止めたいコマンドは、権限設定のdenyに書く方法もあります。Artisanの権限設計はLaravel開発の記事の該当節にまとめています。

編集のたびにPHPStanを回すhook

Claudeが .php を編集した直後に解析を回すと、エラーを溜めずに済みます。PostToolUseフックの Edit|Write マッチャーで実現できます。公式のフック例は、jq で編集されたファイルのパスを取り出して外部ツールに渡す形です。

#!/bin/bash
# .claude/hooks/phpstan.sh
file=$(jq -r '.tool_input.file_path // empty')
case "$file" in *.php) ;; *) exit 0 ;; esac
out=$(vendor/bin/phpstan analyse "$file" --no-progress 2>&1) || {
  echo "$out" >&2
  exit 2
}

.claude/settings.json には次のように登録します。

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          { "type": "command", "command": ".claude/hooks/phpstan.sh" }
        ]
      }
    ]
  }
}

exit 2は、アクションをブロックしてstderrの内容を返す終了コードです。PostToolUseは編集が終わったあとのイベントなので、ブロックそのものよりも、失敗内容をClaudeに伝える使い方が中心になります。この制約を踏まえたLintフックの組み方はPostToolUse hookのLint自動修正に詳しく書いています。テストを回す場合はClaude Code hooksでテストを自動実行するも参考になります。

Larastanはアプリのコンテナを起動して型を解決するため、hookもアプリが起動できる環境で動かす前提になります。例の1ファイル解析は、自分のプロジェクトで所要時間と結果を確かめてから常用してください。

よくあるつまずき

  • Allowed memory size exhausted が出る。 --memory-limit=2G のように上限を渡します。Claudeにコマンドを組み立てさせるなら、CLAUDE.mdの実行行にも入れておきます。
  • vendor まで解析して終わらない。解析対象に vendor を含めません。必要なシンボルは「シンボルの探索」で拾われます。
  • levelを指定していないのに緩い。既定は0です。設定ファイルを置いた場合は、levelの記述が必要です。
  • Larastanが入らない。3.xはLaravel 11.15以上が前提です。それより古いLaravelは、対応表でLarastanの旧系列を確認します。
  • baselineを直してもエラーが残る。直した分のエントリが「該当なし」として報告されます。パターンの削除で解消します。

まとめ

Claude Codeで静的解析を回すと、「件数を減らす」ことが目的化しやすくなります。levelを1段ずつ上げ、古い負債だけをbaselineに隔離し、新しいエラーは直すか理由付きでignoreする。この3点をCLAUDE.mdに書き、reportIgnoresWithoutComments とhookで機械的に補強します。baselineの更新は人間が指示したときだけにすると、件数の増減が意図どおりかを差分で確認できます。

この記事を共有:XはてブLinkedIn