Upgrade to Pro — share decks privately, control downloads, hide ads and more …

ちいさくPHPStanをつくってみる

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest. →
Avatar for asumikam asumikam
October 02, 2026
39

 ちいさくPHPStanをつくってみる

Avatar for asumikam

asumikam

October 02, 2026

More Decks by asumikam

Transcript

  1. #1 ちいさくつくる どのくらいちいさくするのか PHPStan ministan ファイル数 約 2,000 8 行数(空行など除く)

    約 200,000 220 ministanもそこそこボリュームはあるので トークの中ではふんわり理解できるように解説していきます🌷
  2. #1 ちいさくつくる AST(抽象構文木) • Abstract Syntax Tree • コードを「意味のあるかたまり」=「ノード」に分けて、木の形 にしたもの

    • ノードには種類がある(代入・メソッド呼び出し・変数…) • 「;」や 空白 など、意味に関係ないものは捨てている • PHPStan は php-parser(nikic/php-parser)で AST にしている
  3. #1 ちいさくつくる • 文(Stmt)の配列から始まる • 1 行目 0: Stmt̲Expression •

    2 行目 1: Stmt̲Expression • AST を親から子へたどるの は、コードを上から1行ずつ、 外側から内側へ読むのと同じ
  4. #1 ちいさくつくる 「観点をもつ」をつくる • 導入で見たルールの実装をする • 有効にしたいルールを配列で並べておく • コンストラクタで $ast

    を渡しておき、ルールの中で定 義(関数やクラス)を探せるようにする • 具体的なRuleがどうなるかは後述
  5. #1 ちいさくつくる 「観点をあてはめる」をつくる • ノードを 1 つ受け取り、種類が合うルールでチェックする • 使うルール群は、コンストラクタで受け取っておく •

    ノードの数だけ何度もチェックするので、見つけたエラー は、その場ですぐに出さずに最後にまとめて「伝える」 • 実際に動くのは「読みすすめる」のタイミング
  6. #1 ちいさくつくる スライドで説明しきれていない部分 • Before • もともとのScope は「親 → 子」に渡すだけ

    • After • 処理し終わった Scope を返して、次の処理へ引き継ぐ • ただし、クラスの中で分かったことは外に持ち出さない
  7. #1 ちいさくつくる ふりかえり • PHPStan の中身をちいさくつくった(超特急) • 約20万行 → 220行

    • ちいさくしても、流れと登場人物(骨組み)は同じでつくれた • とはいえ、端折ったところはたくさんある • ministan を足がかりに、「本家との差分はどこ?」をみてほしい • 普段使っているルールの実装を見るだけでも、技巧を感じて奥深い
  8. #1 ちいさくつくる ministan PHPStan 早見表 PHPStan ParserFactory Parser\ RichParser Rule

    周辺 Rules\ Rule, LazyRegistry FileAnalyserCallback Analyser\ FileAnalyserCallback NodeScopeResolver Analyser\ NodeScopeResolver Scope Analyser\ MutatingScope NodeFinder Re ection\ Re ectionProvider echo Command\ErrorFormatter\ ErrorFormatter ↔︎ fl fl ministan
  9. #2 PHPStanと比較する PHPStanと比較する • ministan は動くが「PHPStan 未満」 • つくってみて気になった「本家はどうしてる?」をみる •

    なぜ AST を使う? • どうしてあんなに速く処理できる? • カスタムルールはどう組み込まれてる?
  10. #2 PHPStanと比較する ① なぜ「AST」を使う? • php-parser は Rector・Psalm など、多くの PHP

    ツー ルで使われている • ministan でも php-parser をそのまま使った • 「クラスにメソッドがあるか」だけなら、PHP 標準の fl Re ection でも分かるのでは?
  11. #2 PHPStanと比較する ①「読み込むと、実行されてしまう」から • Re ection で調べるには、ファイルを require(=実行)する必要がある • 定義と処理が同じファイルにあると、調べる前に処理が動いてしまう

    • 実は ministan も最初は Re ection でつくろうとして、ここでつまずき、 AST から定義を探す形にした • 本家も 0.12.26 で、AST から定義を読む「静的リフレクション」に移行 (BetterRe ection) fl fi fl fl fl • 参照: https://phpstan.org/blog/zero-con g-analysis-with-static-re ection
  12. #2 PHPStanと比較する ② 速さのひみつ • そのノードに関係するルールだけを取り出す(結果はキャッシュ) • ファイルを 20 個ずつの「ジョブ」に分け、複数のワーカープロセ

    スで同時に解析 • 変わっていないファイルは解析しない(結果キャッシュ) • さらに最近は:C++ 製の拡張(PHPStan Turbo)や、 pcntl̲fork() でワーカーの起動をはぶく工夫も
  13. #2 PHPStanと比較する ③-1 レベルの登録 • 組み込みルールには、「何レベルから有効か」が属性で書いてある • #[RegisteredRule(level: 0)] •

    レベル N を選ぶと、level ≤ N のルールだけが登録される • src/DependencyInjection/AutowiredAttributeServicesExtension.php • 属性の付いたルールを集めて、レベルで絞って登録しているところ
  14. #2 PHPStanと比較する ③-2 カスタムルールの登録 • phpstan.neon の rules: にクラス名を書くと、そのルールが登録される •

    登録先は、組み込みルールと同じ「ルール一覧」 • だから本家の中では、組み込みもカスタムも同じように扱われる • src/DependencyInjection/RulesExtension.php • rules: に書かれたクラスを、ルール一覧に追加しているところ