最近、Claude Code の検証も兼ねて、大量のPHPStanエラーを抑えている baseline の解消を試みています。
対処方針
PHPStan には Error Identifiers という概念があります。
https://phpstan.org/error-identifiers
これは種々のルールをグルーピングするための概念で、エラーをレポートしたルールが相異なるルールであっても同じ Error Identifier であれば、同様の原因に起因するエラーであることが判別できます。
baseline にはエラー情報やエラーが発生しているファイルのパス、同種のエラーのカウントと合わせて、この Error Identifiers が記載されます。
このうち、PHPDocによる型情報の補足や、既存PHPDocの修正そのものなどによって解決するものを中心に、現在 Claude Code を使用して徐々に解消を進めています。
基本的にはエラーの種類ごとに対処するよりは機能ごとに対処し動作確認を行うのが良いと思いますが、なにぶん抑制されているエラーの数が当初5万以上(PHPStanのLevelは5)と膨大な数であった事、テストが十分に用意されているとは全く言い難い事などが重なり、とにかくPHPDocによる対処でまずは大きく数を減らしたいと考えました。
具体的に対処したものとしては、
- phpDoc.parseError
- varTag.misPlaced
- parameter.notFound
などがあります。これらは全て誤ったPHPDocに起因するものであり、一律にプログラムで対処することは難しいこともありますが、Claude Code にエラー情報を渡して修正を依頼することですんなり解消できました。
しかし、これらは数が少なかったので人力でも対処は難しくなかったものです。問題なのは次のエラーでした。
大量の variable.undefined への対処
弊プロダクトは今でも CakePHP2 を使用しており(悲しい)、フロントエンドの React化なども一部進んではいますが、大半はctpファイルという旧来の独自Viewファイルを使用します。
これを使用すると、静的解析にとって大きな障壁が発生してしまいます。
すなわち、extractを使用して配列からシンボルテーブルに変数をインポートするために、ctpファイルを見てもスコープ内に変数の定義が存在せず、PHPStanには未定義(= variable.undefined)としか判断できない問題です。
この variable.undefined だけで1万個を優に超えるエラーが発生しています。
これを解消する方法としては、ctpファイル内に @var タグで変数の存在を型情報と共に補足する方法があります。
対応するControllerのアクションを参照しset()で格納した変数を確認すれば概ね型はわかるので、PHPStanの型推論を借用して@varタグを付与するツールを書こうかとも思いましたが、mixedやarrayとしか推論できない状態がほとんどなため、あまり意味がないとして断念していました。
そこで、Claude Code に型を推論させながら @var タグの付与をさせてみました。
課したルールとしては以下のようになります(実際にはslash commandとしてmarkdownに書いています)。
- PHPDocを追加・変更・削除する以外の修正を加えてはならない
- array,object,mixed,stdClassを使わず、よく考えて具体的な型を推論しなければならない
- 変数が連想配列だった場合、構造を調べてarray-shapes記法で型を書かなければならない
- スクリプトを書いてまとめて処理してはならない
1つ目は言わずもがな、本番環境に影響を与えることを防ぐためです。もちろんコードレビューもしますが、指示を与えておかないとたまに暴走します。
2つ目と3つ目は、曖昧な型のまま終わらせないための指示です。間違ったものがないとはまだ言い切れないですが、「よく考えて」(think hard)と指示することで概ね正しく推論できているように見えました。(ただ、繰り返しているうちにcompactが走ってこの指示を忘れるせいか、目を離すとarrayやmixedで済ませようとすることがよくあります)
4つ目は、同種のエラーを大量に発見すると効率化のためにpythonでスクリプトを書き始めるということがあったためです。事故の元なので、一旦禁止しています。
加えて、単に推論するよう指示するだけだと、存在しない名前空間をでっち上げるということがありました(psr-4に従っていないクラスであったことが根本原因ではあるのですが)。これは型として有効でないばかりか、class.notFound に分類されるPHPStanエラーを新たに発生させてしまいます。
これは修正したファイルに対して自ら PHPStan で検証させながら解消させることで、概ね回避できるようになりました。
結果
当初5万を超えていたエラー数が、現在は4万を下回るまでになっています。
まだ variable.undefined はかなりの数残っているため、その対処が終わる頃には3万も下回る見込みです。
雑感
前の冬ぐらいまでは VibeCoding アンチ気味だったのですが、Claude Code が強力で完全に手のひらを返してしまいました。まさにゲームチェンジャーと言わざるを得ません。参りました。
あと、最初は動作に割り込んでスクリプトを禁止したのですが、「わかりました。手動で修正します」と言われ、君に手はあるんか?と思いました。
