ほげほげ見聞録

技術メモ、備忘録、使い方はそのうち覚える

PHPStanを試してみた

PHPerKaigiで紹介されていたPHPStanが面白そうだったので試してみた。
昨日今日でざっくりお試しなのでまだ設定できる部分とかありそう。

PHPDocが書いてあるプロジェクトにcomposer requireしてみて、躓いた点等ざっくりメモ。

必要なもの

PHP7.1。環境パスは通した。
あとは実行したいプロジェクトで「composer require --dev phpstan/phpstan」するだけ。
PHPStanのREADME:phpstan/README.md at master · phpstan/phpstan · GitHub
※追記
試しで使う場合は、COMPOSER_HOMEにグローバルインストール方がやりやすそうだ。
実行コマンドにbootstrapファイルを指定する必要はあるが、複数のプロジェクトに使ってみたいときは既にあるファイルをいじらなくて良い。

バージョン確認。

vendor/bin/phpstan -v

phpstan.neon

設定ファイルはvendor/phpstan/phpstan/confフォルダにある。
phpstan/README.md at master · phpstan/phpstan · GitHub

この中で間違いがあると何のメッセージもなく終了するので少しやりづらい。
autoload_directoriesとautoload_filesで使う%rootDir%はvendor/phpstan/phpstanにある。vendor/binだと思ってたら違った…。

とりあえず解析実行

vendor/bin/phpstan test.php

test.phpが以下のようなクラスのない場合はグリーンバー。

<?php
echo 'hello world!';


test.phpがクラスのある場合は「Class not found autoloadがどうのこうの」エラーが出た。

<?php
class test {
    function hoge() {
    }
}


autoloadで読み込まないといけないらしい。
Class not found (in same class file) · Issue #661 · phpstan/phpstan · GitHub

それぞれコードが長い8ファイルを対象にしたら、途中で以下エラー発生。

 ! [NOTE] PHPStan crashed in the previous run probably because of excessive memory consumption.
It consumed around 20 MB
 !        of memory.

To avoid this issue, allow to use more memory with the --memory-limit option.

エラーが多すぎてダメだったのかもしれない(GitHubのissueでとりあえずメモリエラーを吐くとか書いてあった気がする)。
一ファイルだけ対象にすると最後まで解析実行され、エラーも出た。

とりあえずbootstrapを用意

設定ファイルにbootstrapを設定できるので、PHPStan用に作成。autoload_filesに指定でも問題なかった。

bootstrap: %rootDir%/../../../phpstan-bootstrap.php


ちゃんとパス設定しておかないと以下のようなエラーが出る。
「Error - Package 'parser' could not be found at '/parser/' in core/classes/package.php on line 89」

プロジェクト全体の解析実行

coreとかその他を省いた部分に関して解析してみた。
行数の多いファイルが300個くらいだと、途中まででも結構時間が掛かった。

途中でFatal Error発生

当然ながら止まる。

時間かかりすぎる

止まる。
「Fatal error: Maximum execution time of 300 seconds exceeded in vendor\phpstan\phpstan\src\Analyser\Scope.php on line 1654」

bootstrapに「ini_set('MAX_EXECUTION_TIME', -1);」を設定してみた。
やっぱり止まる。
「Fatal Error - Maximum execution time of 300 seconds exceeded in vendor/nikic/php-parser/lib/PhpParser/PrettyPrinter/Standard.php on line 940」
出ている場所が違うが、エラーの出力が多いと面倒なので対象フォルダを分けた方が良さそうだ。

50ファイルを対象にしたら全部解析できた。

関数名のチェックで怒られる

「Call to method Imagick::readimage() with incorrect case: readImage」
マニュアル読む限りreadImage()が正しそうだが、readimage()にしろというエラーが出た。
PHP: Imagick::readImage - Manual
不明点。

※追記
コメントいただいて判明。
なんと、pecl-imagickのメソッド名はすべて小文字で定義されているそうだ。
imagick/imagick_class.c at 3acc635fe321bde7e2154f0cd01e6aa7d970bc3f · mkoppanen/imagick · GitHub
何のためのマニュアルだよぅ!
そういえばクラス名をImagickではなくimagickにしてるとエラーが出ていた。こういう、PHPだと実行・IDEでスルーされるエラーも見てくれるのは良いなぁ。

使い心地

bootstrapはPHPUnitのを使いまわしですぐに作れるし、導入は比較的容易、コマンド一つで解析できるのはとても良いと思った。

また、パッケージ導入忘れのエラーに気が付く事ができた。
例えばパッケージAはパッケージBをextendしているが、必要なのはAだけでBを導入しなくても何故かエラー出なかった場合など。
※追記
どうやらclass_exists()でどちらかのパッケージがあるか判断している部分で、AとB両方チェックしてしまっているらしい。
これに関してはエラーを除外するとか、そもそもパッケージのファイルまで解析しにいかないとか対応が必要そうだ。

困った事としては、stringのパラメータの関数でNULLチェックをする場合に「Call to function is_null() will always evaluate to false.」が出るという点。
@param string $valの場合、stringだからNULLはあり得ないという事だと思われる…。
明示的or予期せずNULLを入れる場合がありえるのでNULLチェックは入れておきたい。
ignoreErrorsとかで弾いたり、エラーレベルを下げたりするのが良いのかもしれない。

composerのパッケージが増えるのでプロジェクトに組み込んでもらえるかは怪しいが、様子を見て提案してみようかなと思った。既にあるプロジェクトでCIに組み込むのはハードルが高いかも。
あとはレベル5くらいで結構な数のエラーが出たので、どこのタイミングで直せるかなという問題。