Share
Embed
PHPerKaigi 2022をきっかけにPHPStanにコントリビュートした話 / PHPerKaigi makes me PHPStan's Contributor
Transcript
36 slides
-
こんなルールを作りました <?php $dt = new DateTimeImmutable(); $dt->__construct('2022/1/23'); ↓ > ./vendor/bin/phpstan
analyse -c phpstan.neon ------ ------------------------------------------------------------- Line sample.php ------ ------------------------------------------------------------- 4 Call to __construct() on an existing object is not allowed. ------ ------------------------------------------------------------- ※ デフォルトでは有効になっていないため、次ページ記載の設定が必要 3 -
良い感じのルールクラス <?php declare(strict_types = 1); class MyRule implements Rule {
public function getNodeType(): string { // 一致するコードがprocessNode でチェックされる return Node\Expr\ メソッド呼び出しを行っているパーツ::class; } public function processNode(Node $node, Scope $scope): array { // __construct 実行をチェック if ( エラーでない場合) { return []; } return [' エラーです']; } } 14 -
PHPerKaigi (day3?) ハッカソン爆誕 加速するDiscord 名前どうする? 「1. ソースコード内でメソッド呼び出しを行っている箇所を解析し」 どのノードタイプで引っ掛ける? 「3. 正常な使い方をしていない場合」
弾いてはいけない正常なパターンはどれ? どこに実装する? PHPStan本体 phpstan-strict-rules こんな感じのコードでいけそう (from mpywさん、yu-ichiroさん) 16 -
これはOK class MyDateTimeImmutable extends DateTimeImmutable { public function __construct(string $datetime
= "now", ?\DateTimeZone $timezone = null)) { parent::__construct($datetime, $timezone); } } 17 -
これはNG class MyDateTimeImmutable extends DateTimeImmutable { public function mutate(string $datetime
= "now", ?\DateTimeZone $timezone = null)) { parent::__construct($datetime, $timezone); } } $dt = new DateTimeImmutable(); $dt->__construct('2022/1/23'); 18 -
こういったパターンもある (OK?) from mpywさん class MyDateTimeImmutable extends DateTimeImmutable { public
function __construct(string $datetime = "now", ?\DateTimeZone $timezone = null)) { self::__construct($datetime, $timezone); } } ※ 許容する形で実装したが、最終的にマージされたコードではNGに 19 -
実装(PHPStan作者の方による調整が入った最終形) <?php declare(strict_types = 1); namespace PHPStan\Rules\Methods; // use は行数の都合で省略
class IllegalConstructorMethodCallRule implements Rule { public function getNodeType(): string { // 戻り値のNodeType (今回で言えばメソッド呼び出し)が解析対象のコードに含まれていた場合、processNode で解析する return Node\Expr\MethodCall::class; } public function processNode(Node $node, Scope $scope): array { if (!$node->name instanceof Node\Identifier || $node->name->toLowerString() !== '__construct') { // エラー無し return []; } return [ RuleErrorBuilder::message('Call to __construct() on an existing object is not allowed.') ->build(), ]; } } どうやってここに行き着く? 21 -
とりあえずvar_dump 3ページ目のコードを解析した場合のvar_dump($node)を抜粋 object(PhpParser\Node\Expr\MethodCall)#3146 (4) { ["attributes":protected]=> array(9) {} // 4
行目にあるdt という名前の変数がメソッドを呼び出している ["var"]=> object(PhpParser\Node\Expr\Variable)#3142 (2) { ["attributes":protected]=> array(9) { ["startLine"]=> int(4) } ["name"]=> string(2) "dt" } // 呼び出しているメソッドの名前は__construct ["name"]=> object(PhpParser\Node\Identifier)#3143 (2) { ["attributes":protected]=> array(6) {} ["name"]=> string(11) "__construct" } // 今回は引数の情報は要らない ["args"]=> array(1) {} } 22 -
焦ってCIに引っかかり、修正 この時点で0:32。 焦ってしまい、ローカルで静的解析やLintを走らせていなかった。 テスト Nullsafe Operator ?-> の使用がPHP7系でのテストで引っ掛かる ローカルではPHP8.1でテストを走らせていたが、CIではPHP7.2〜8.1でテスト テスト対象のコードに記載した再帰コンストラクタが原因で一部のテストでメモリ
エラーに テスト対象のコードで再帰を1回に抑える 静的解析 tadsanさんが助けて下さる Lint phpcbfで自動修正 26 -
その後 4/13 承認され、Ondreさんから後は引き取るとコメント頂く 4/17 調整が入った上でマージされた 4/26 PHPStan 1.6.0でBleeding Edgeとして利用可能に 5/5
PHPStan 1.6.6でphpstan-strict-rulesに移動 34