PHPStanは静的解析ツールでコードに潜むバグや潜在的な問題を検出することができます。例えば未定義変数、使用されていないコード、不整合な型の流れを検出できます。PHPStanを使用することでコードの品質を向上させ不具合を起こしにくくできます。しかしながらPHPStanの検出できる項目は余りに多く、既存のプロジェクトに導入すると過去のコードに非常に多くのエラーが報告され修正する気が萎えるほどになる場合があります(そのプロジェクトが問題なく動いている、改修し続けられているとなおさらです)。PHPStanの”ベースライン”機能でこの問題を解決できます。この記事ではPHPStanのベースライン機能の紹介をします。
PHPStanのベースライン機能は既存のプロジェクトにおいて過去に報告されたエラーを一時的に無視するための機能です。これを使うとPHPStanは新しいコードや変更されたコードについてのみ警告を出すようになります。ベースラインは次のコマンドで作れます。
# このコマンドでベースラインファイルを生成し vendor/bin/phpstan analyse --generate-baseline
# phpstan.neonのような設定ファイルで生成したファイルを指定します。 includes: - phpstan-baseline.neon
こうするとベースラインに含まれる警告は無視され以降の解析では新たに発生した警告のみが報告されます。
ベースラインを用いることで快適にPHPStanを使えますが、正常に動作するコードであろうと問題点が指摘されるのであれば直したくもあります。そういったケースではベースラインを用いずに常に一部ファイルのみを扱う方法とスクリプトを作って一時的にベースラインを無視する方法が取れます。
ベースラインを用いずに常に一部ファイルのみを扱う方法は次のようにできます。
vendor/bin/phpstan analyse app/Models/Eloquents/User.php app/Models/Eloquents/Post.php
こんな感じで常に検査対象のファイルを指定するのみの運用にすると膨大な指摘を読まずに少しずつ改修を進められます。これはコミットフックなどに組み込みやすいです。
また次のようにスクリプトを作って一時的にベースラインを無視する方法も取れます。例えばこのコードはphp phpstan_wrapper.php --ignore-baseline phpstan-baseline.neon
で phpstan-baseline.neon を無視でき、php phpstan_wrapper.php --ignore-baseline phpstan-baseline.neon -- app/Models/Eloquents/User.php
で phpstan-baseline.neon を無視して User.php だけを検査できます。
<?php /** * PHPStanに--ignore-baselineオプションを追加するためのラッパー * 必要なライブラリは以下: * - symfony/process: 外部コマンドの実行とその管理を容易にするライブラリ * - symfony/yaml: YAMLファイルの解析と書き出しをサポートするライブラリ * 使用例: * php phpstan_wrapper.php --ignore-baseline phpstan-baseline.neon * # 検査対象ファイル指定をする場合は検査対象のファイルの前に -- を付けて区切る * php phpstan_wrapper.php --ignore-baseline phpstan-baseline.neon -- app/Models/Eloquents/User.php * # 複数のbaselineファイルを無視する場合はファイル名をスペース区切りで指定する * php phpstan_wrapper.php --ignore-baseline phpstan-baseline.model.neon phpstan-baseline.controller.neon */ require_once __DIR__ . '/vendor/autoload.php'; $baseConf = 'phpstan.neon'; // デフォルトのPHPStan設定ファイル $tmpConf = 'phpstan.tmp.neon'; // 一時的な設定ファイル。baselineを無視した設定を一時的に保存する // スクリプト終了時に一時ファイルを削除するための関数を登録 // 一時ファイルが存在する場合のみ削除を行う register_shutdown_function(static fn() => file_exists($tmpConf) && unlink($tmpConf)); // コマンドライン引数を元に、PHPStanの実行コマンドを構築 $cmd = ['vendor/bin/phpstan', 'analyse', ...array_slice($argv, 1)]; $cmdLen = count($cmd); foreach ($cmd as $i => $arg) { // --ignore-baseline オプションが指定されている場合、このPHPスクリプト上で処理する if ($arg === '--ignore-baseline') { // --ignore-baseline オプションの位置を記録 // 引数は後で phpstan に渡すために削除する必要があるので、削除対象のインデックスを記録する $toUnsetIdxList = [$i]; $ignoreBaseLineFileList = []; // --ignore-baseline オプションに続くファイル名を収集 for ($j = $i + 1; $j < $cmdLen; $j++) { if (str_starts_with($cmd[$j], '--')) { break; // 新しいオプションの開始を検出したら収集を終了 } $ignoreBaseLineFileList[] = $cmd[$j]; // baselineファイルをリストに追加 $toUnsetIdxList[] = $j; // 後で削除するためにインデックスを記録 } // オリジナルの設定ファイルを読み込み、無視するbaselineファイルについての設定を消す $baseConfContent = Symfony\Component\Yaml\Yaml::parseFile($baseConf); $baseConfContent['includes'] = array_filter( $baseConfContent['includes'], static function ($include) use ($ignoreBaseLineFileList) { return !in_array($include, $ignoreBaseLineFileList, true); } ); // 修正された設定を一時ファイルに書き出し file_put_contents($tmpConf, Symfony\Component\Yaml\Yaml::dump($baseConfContent)); // コマンドから無視したオプションとファイルを削除 foreach ($toUnsetIdxList as $idx) { unset($cmd[$idx]); } // 一時的な設定ファイルを使用するようにコマンドを修正 // 末尾の検査対象ファイルの指定を壊さないように --ignore-baseline オプションがあったところに挿入する array_splice($cmd, $i, 0, ['--configuration', $tmpConf]); } } // vendor/bin/phpstan を実行 echo "cmd: " . implode(' ', $cmd) . PHP_EOL; (new Symfony\Component\Process\Process($cmd))->run(static function ($type, $buffer) { echo $buffer; // 実行中の出力をそのまま表示 });
こんな感じでコマンドをラップすると本来ない機能を持たせることができます。
余談ですが Laravel を使っているプロジェクトに PHPStan を導入する場合は larastan を使って導入した方がいいです。Laravel を使っているコードに素の PHPStan を適用すると Laravel 的には想定通りの正しい使い方のコードについても大量に警告を出してきます。