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

PHPStanでCustomRuleを作る / Make PHPStan CustomRule

PHPStanでCustomRuleを作る / Make PHPStan CustomRule

PHPカンファレンス福岡2019の発表資料です。
PHPStanのCustomRuleを使えば独自の静的検査をかけることが出来ます。具体例を通しながら、CustomRuleで何が実現できるのか、どうすれば自分たちで作れるようになるのかを解説しました。

Satoshi Kawashima

June 29, 2019
Tweet

More Decks by Satoshi Kawashima

Other Decks in Technology

Transcript

  1. © - BASE, Inc. • PHPStanとは • 何故CustomRuleを作るのか • CustomRuleの基本

    • 望むCustomRuleを作るには • PHPStanのクラスを使いこなす • CustomRuleの具体例 ⽬次
  2. © - BASE, Inc. • 未定義変数の使⽤ • タイプヒントとの不⼀致 • 存在しないファンクション、メソッド、プロ

    パティの呼び出し • 無意味なコードの検出(未使⽤変数や絶対⼊ らない条件分岐) • etc 問題点の検出例
  3. © - BASE, Inc. • 全員がPhpStormが使っているとは限らな い • PHPStanの⽅がCIで実⾏させやすい •

    検査(というか結果の判定)が厳密 • 検査項⽬が豊富 • 独⾃の検査(CustomRule)が作れる それ、PhpStormで良くね?
  4. © - BASE, Inc. • PHPStanとは • 何故CustomRuleを作るのか • CustomRuleの基本

    • 望むCustomRuleを作るには • PHPStanのクラスを使いこなす • CustomRuleの具体例 ⽬次
  5. © - BASE, Inc. • PHPStanとは • 何故CustomRuleを作るのか • CustomRuleの基本

    • 望むCustomRuleを作るには • PHPStanのクラスを使いこなす • CustomRuleの具体例 ⽬次
  6. © - BASE, Inc. よくあるPHPStanの実⾏例 $ vendor/bin/phpstan analyse -l -c

    phpstan.neon ./src -l オプションで検査レベルを指定する (レベルが上がるほど厳密になる)
  7. © - BASE, Inc. • CustomRuleクラスを作って、configに 追加するだけ CustomRuleの追加の仕⽅ $ vendor/bin/phpstan

    analyse -l -c phpstan.config.neon ./src PHPStanのconfigファイルを作る (拡張⼦は.neon)
  8. © - BASE, Inc. • CustomRuleクラスを作って、configに 追加するだけ CustomRuleの追加の仕⽅ $ vendor/bin/phpstan

    analyse -l -c phpstan.config.neon ./src PHPStanのconfigファイルを作る (拡張⼦は.neon) -c オプションに⾃分で作った configファイルを渡す
  9. © - BASE, Inc. • ソースコードをPHPにとっての最⼩の意 味単位まで分解する 1. 字句解析 T_OPEN_TAG

    T_NAMESPACE T_STRING T_CLASS T_STRING T_PRIVATE T_VARIABLE T_PUBLIC T_FUNCTION T_STRING T_STRING T_VARIABLE T_STRIN T_RETURN T
  10. © - BASE, Inc. • nikic/PHP-Parserで構⽂解析し、 AST(abstract syntax tree)を⽣成する 2.

    構⽂解析 T_OPEN_TAG T_NAMESPACE T_STRING T_CLASS T_STRING T_PRIVATE T_VARIABLE T_PUBLIC T_FUNCTION T_STRING T_STRING T_VARIABLE T_STRIN T_RETURN T Namespace statement Class statement Property statement ClassMethod statement
  11. © - BASE, Inc. 2. 構⽂解析 \PhpParser\Node\Stmt\Namespace_ \PhpParser\Node\Stmt\Return_ \PhpParser\Node\Stmt\Expression \PhpParser\Node\Stmt\ClassMethod

    \PhpParser\Node\Stmt\Property \PhpParser\Node\Stmt\Class_ \PhpParser\Node\Stmt\PropertyProperty \PhpParser\Node\VarLinkIdentifier \PhpParser\Node\Scalar\Identifier \PhpParser\Node\Expr\Assign \PhpParser\Node\Expr\Variable \PhpParser\Node\Scalar\String \PhpParser\Node\Expr\BinaryOp\ConCat \PhpParser\Node\Expr\Variable \PhpParser\Node\Expr\Variable
  12. © - BASE, Inc. ノード=ASTの各頂点 \PhpParser\Node\Stmt\Namespace_ \PhpParser\Node\Stmt\Return_ \PhpParser\Node\Stmt\Expression \PhpParser\Node\Stmt\ClassMethod \PhpParser\Node\Stmt\Property

    \PhpParser\Node\Stmt\Class_ \PhpParser\Node\Stmt\PropertyProperty \PhpParser\Node\VarLinkIdentifier \PhpParser\Node\Scalar\Identifier \PhpParser\Node\Expr\Assign \PhpParser\Node\Expr\Variable \PhpParser\Node\Scalar\String \PhpParser\Node\Expr\BinaryOp\ConCat \PhpParser\Node\Expr\Variable \PhpParser\Node\Expr\Variable
  13. © - BASE, Inc. . ASTの解析 NodeScopeResolver AST 解析 各Ruleにはどのノードを発⾒したときに

    検査メソッドを呼び出してほしいかが書いてある Rule Rule Rule Rule Rule Rule
  14. © - BASE, Inc. • PHPStanとは • 何故CustomRuleを作るのか • CustomRuleの基本

    • 望むCustomRuleを作るには • PHPStanのクラスを使いこなす • CustomRuleの具体例 ⽬次
  15. No

  16. Scope Node Broker Type RunLevelHelper TokenIterator ParameterAcceptorSelector FunctionParameterCheck PhpDocParser Lexer

    Container ClassCaseSensitivityCheck 望む解析を⾏うには PHPStanの機能群を 使いこなす必要がある
  17. © - BASE, Inc. • 主要3クラスの解説 • 具体例1: 新しく追加したメソッドには戻り 値型宣⾔を必須化する

    • DependencyInjectionの使い⽅ • 具体例2: 独⾃phpdocの作成 • PhpDocParserの使い⽅ この後のスライド
  18. © - BASE, Inc. ASTの頂点を表すクラス コレ \PhpParser\Node\Stmt\Namespace_ \PhpParser\Node\Stmt\Return_ \PhpParser\Node\Stmt\Expression \PhpParser\Node\Stmt\ClassMethod

    \PhpParser\Node\Stmt\Property \PhpParser\Node\Stmt\Class_ \PhpParser\Node\Stmt\PropertyProperty \PhpParser\Node\VarLinkIdentifier \PhpParser\Node\Scalar\Identifier \PhpParser\Node\Expr\Assign \PhpParser\Node\Expr\Variable \PhpParser\Node\Scalar\String \PhpParser\Node\Expr\BinaryOp\ConCat \PhpParser\Node\Expr\Variable \PhpParser\Node\Expr\Variable
  19. © - BASE, Inc. • PhpParser\Nodeを基底とした複雑な継 承関係がある • 合計183個のクラス •

    解析したいコード近辺の情報はこいつを 辿りながら集めることになる PhpParser\Node
  20. © - BASE, Inc. Scopeの作られ⽅ NodeScopeResolver AST 解析 スコープの切れ⽬となるノードを発⾒するたび、 Scopeオブジェクトを新しく作る

    Rule Rule Rule Rule Rule Rule Ruleクラスの検査メソッドを呼び出すときには、 そのノードが属するScopeオブジェクトもセットで渡す 検査メソッドの呼び出し Scope Scope Scope Scope
  21. © - BASE, Inc. • 解析対象のファイル内部に関わることな らだいたいScopeクラスに聞くことにな る • ファイル名、クラス情報を取得する

    • 現在のNodeのスコープ上の位置を聞く • 変数スコープの型解決(重要) Scopeクラスを使いこなす
  22. © - BASE, Inc. リフレクションを提供するクラス Broker autoloader \PHPStan\Reflection\ClassReflection \ReflectionClass config.neon

    Rule クラス定義 動的プロパティ‧メソッドの 定義情報 組み込みリフレクションをラップした、 動的プロパティ‧メソッド定義も含めた リフレクションの提供
  23. © - BASE, Inc. • Broker: 仲買⼈、周旋屋販売⼈ • 解析対象のファイル以外からの(主にクラスや関数 の)リフレクションを供給する

    • PHPStanは動的メソッド‧プロパティも解析できる ようにサポートするための機能があり、それらを踏ま えた上での独⾃のリフレクションクラスを⽣成する • ⾃分でReflectionを取ろうとせずBrokerに委託する のがおそらく正しいPHPStanスタイル PHPStan\Broker\Broker
  24. © - BASE, Inc. 1. 修正前後のファイルコンテンツを⼊⼿ 1. マージベースのコミットハッシュの取得 $ git

    show-branch --merge-base master HEAD b faefdc b d c b b d f ab a fd da ec e fc f b faefdc a f b master feature_branch
  25. © - BASE, Inc. 1. 修正前後のファイルコンテンツを⼊⼿ a fd da ec

    e fc f b faefdc a f b master feature_branch 2. 修正後のファイルコンテンツを取得 $ git show HEAD:src/Hoge.php <?php declare(strict_types= );
  26. © - BASE, Inc. 1. 修正前後のファイルコンテンツを⼊⼿ a fd da ec

    e fc f b faefdc a f b master feature_branch 3. 修正前(マージベース)のファイルコンテンツを取得 $ git show fc f :src/Hoge.php <?php declare(strict_types= );
  27. © - BASE, Inc. . ASTの⼊⼿ ClassMethod ClassMethod ClassMethod ClassMethod

    ClassMethod PHP-Parserを使ってASTの取得 マージベース時点のファイルコンテンツ 現時点のファイルコンテンツ
  28. © - BASE, Inc. 3. 実⾏時にneonファイルを渡す $ vendor/bin/phpstan analyze -c

    ./conf/phpstan.config.neon src/ -cオプションに渡す
  29. © - BASE, Inc. • traitをuseするのに必要なプロパティを 独⾃タグで⽰す • traitが持つ暗黙的依存を表現し、検査 する

    • class側でうっかり暗黙的依存を破って しまう事故を防⽌する 具体例2:traitの暗黙的依存を⽰す
  30. © - BASE, Inc. • PSR- は個別のアプリケーション向けの独⾃タグ を考慮していて、以下の規約が定められている • タグの先頭にPHPスタイルの名前空間をつける

    • 例:@\Doctrine\Orm\Mapping\Entity() • タグの先頭にハイフンを付けたベンダ名をつける • 例:@phan-closure-scope 余談:PSR- における独⾃タグの規約