Fusic Tech Blog

Fusion of Society, IT and Culture

PHPStanで静的解析をしてみる
2018/10/16

PHPStanで静的解析をしてみる

どうも、Fusic で PHPer をしている早﨑です。

早速ですが、業務で PHP-CS-Fixer や PHPMD を使用していますが、別で PHPStan というものを紹介したいと思います。

ゴール

git commit のフックで自動的に PHPStan を利用した静的解析が実行出来るようになること。

環境

  • PHP7.2
  • composer が使える

PHPStan とは?

Qiita [PHP] 静的解析ツール PHPStan の機能概要

PHPStan で始める PHP のための静的解析 #phperkaigi

メルカリさんも使ってるようです。

基本的な使い方は URL 先やググったら沢山情報がでるのでそちらにお任せしますが、要は静的解析が出来ます。
(PHPMD や PHPlint に近いかな)

ということで導入してみます。
私は毎回コマンドを叩くのが面倒なので git commit にフックさせます。

composer install

composer.json に以下を記述

"require-dev": {
 "phpstan/phpstan": "\*"
 },
 "scripts": {
 "post-install-cmd": [
 "mkdir -p .git/hooks/",
 "cp git-pre-commit .git/hooks/pre-commit",
 "chmod a+x .git/hooks/pre-commit"
],
 }
$ composer install

commit 時にフックさせる

git-pre-commit に以下を記述

PHP\_CODE=$(cat \<\<'EOS'
 $output = array();
 $return = 0;
 exec('git rev-parse --verify HEAD 2\> /dev/null', $output, $return);
 $against = $return == 0 ? 'HEAD' : 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'; // github repoのハッシュ値
 exec("git diff-index --cached --name-only {$against}", $files);

 // 対象ファイルの設定
 $filenamePattern = '/\.(php)$/';

 // 除外ファイルの設定
 $ignorePatterns = [
 '/^bootstrap\/cache\/.\*(php)$/',
];

 $exit\_status = 0;

 foreach ($files as $file) {
 if (!file\_exists($file)) {
 // delete file
 continue;
 }
 if (!preg\_match($filenamePattern, $file)) {
 // don't check files that aren't PHP
 continue;
 }
 foreach ($ignorePatterns as $ignoreFilePattern) {
 // ignore
 if (preg\_match($ignoreFilePattern, $file)) {
 continue 2;
 }
 }

 // PHPSTANを実行する
 ob\_start();
 $stan\_return = [];
 exec(sprintf('./vendor/bin/phpstan analyse --no-progress -l max %s', escapeshellarg($file)), $stan\_return, $stan\_error);
 $output = ob\_get\_clean();
 if ($stan\_error) {
 echo PHP\_EOL, "PHPSTAN error", PHP\_EOL;
 foreach ($stan\_return as $returnK =\> $returnV) {
 echo $returnV, PHP\_EOL;
 }
 $exit\_status = 1;
 continue;
 }

 }
 exit($exit\_status);
 EOS
 )

 php -r "$PHP\_CODE"
 php\_status=$?
 if test $php\_status -ne 0
 then
 echo "git pre-commit hook error"
 echo "please fix the error and retry commit"
 cat \<\

コード変更

エラーが出るように変更

\ $members]); // 型がphpdocsの定義と異なる
 }

 以下省略

git commit

$ git commit -a
------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Line MembersController.php
 ------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 17 Return typehint of method App\Http\Controllers\MembersController::index() has invalid type Illuminate\View\Viewaaaaaa.
 21 Undefined variable: $hoge
 23 Method App\Http\Controllers\MembersController::index() should return Illuminate\Contracts\View\Factory|Illuminate\View\Viewaaaaaa but returns Illuminate\Contracts\View\Factory|Illuminate\View
 ------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 ↑こんな感じで指摘してくれる

 [ERROR] Found 3 errors



 PHPSTAN error
 git pre-commit hook error
 please fix the error and retry commit
 if remove from the index:
 $ git reset
 if edit the file to fix errors and retry commit:
 $ edit FILE\_NAME ...
 $ git add FILE\_NAME
 $ git commit

良い感じに指摘してくれます。
PHPMD や PHPlint と被るところは多いと思いますが、色々指摘してくれるので便利です。
ただ、これは指摘だけになりますので、PHP-CS-Fixer と絡めて調整してくれるようにすると良いでしょう。
私は、同じ様に git-pre-commit に全部突っ込んでやってます。

指摘は結構でますが、徐々に意識したコードを書くようになります。
新人・経験者に限らず、入れておくと最初は開発のスピードは落ちるでしょうが、そのうちしっかりとしたコードが書けるようになると思います。

おまけ

PHPStan は Laravel で導入すると、やたらめったらエラーを吐きます。
具体的には、routes/web.php などのファイルで Route クラスってどこにあるの?とかです。

何かいい方法が無いかと調べてみたら、PHPStan の Laravel 用があるようです。

GitHub nunomaduro/larastan

こいつを使えばエラーが出なくなるので便利です。
Laravel ではこっちを使ってみると良いです。

それでは

hayasaki

hayasaki

主にPHPで業務系のアプリケーションの開発を行っています。