はじめに
PHPの静的解析ツールであるPHPStanを導入してみましたのでご報告です。
なぜ静的解析なのか
コードのクオリティを保つためには、自動テストやコードレビューなど様々なアプローチがありますが、その中で今回は静的解析ツールの導入に焦点を当てました。
自動テストは基本的に開発に比例してコストがかかるため、受託案件だと要不要は場合によりけりですし、コードレビューについては、すべきことはツールの導入というより文化の醸成です。
その点、静的解析はツールの導入により手軽に実現できそうですし、一度導入してしまえば付きっきりで面倒を見てあげる必要もありません。社内での標準化を見据えたときに現実味がありそうでした。
なぜPHPStanなのか
PHPコードの解析をPhanからPHPStanに移行しようか検討しています – コネヒト開発者ブログ
こちらの記事を参考にしました。
後述しますが、作業者のローカル環境にはcomposerで直接環境に導入してもらうことを想定していたので、php-ast拡張が必要なPhanはちょっと取っつきづらいかなという判断です。
ローカルでの導入
ローカルにはcomposerで導入します。なるべく環境を汚したくないので、composer global require
がよいでしょう。
composer global require phpstan/phpstan
Dockerを利用して導入することもできますが、Docker化されていない環境もあることを踏まえ、今回はcomposerを利用します。
あと、PHPStanは静的解析と言いながらも一部コードを実行がされるため、なるべく実行環境と同等の環境が望ましく、Dockerであれこれするよりは直接ローカル環境にえいやしてしまった方がわかりやすいのでは、という経緯もありました。ただ、実行環境がPHP5系だったりするとPHPStanは動いてくれないので、Dockerを使って別コンテナで実行することになるかと思います。
設定と実行
composer global require
してあげるとホームディレクトリに~/.composer/vendor/bin/phpstan
ができているので、そこにパスを通したうえで
phpstan analyse src
などとすることで解析が実行されます。が、これだけでは実行時の細かい設定ができません。
PHPStanの設定はphpstan.neonという名称のファイルを置くことで行うことができます。
phpstan.neon
phpstan.neonでの設定方法については公式READMEやその他有用な記事があるのでそちらをあたってみてください。
本記事では気休め程度に、CakePHP3のプロジェクトで作成した設定ファイルを載せておきます。
parameters: level: 0 paths: - %currentWorkingDirectory%/src/ autoload_files: - %currentWorkingDirectory%/vendor/autoload.php - %currentWorkingDirectory%/config/bootstrap.php excludes_analyse: - %currentWorkingDirectory%/src/Console/Installer.php ignoreErrors: - '#Access to an undefined property [A-Za-z0-9\\_]+::\$(Articles|Categories|Tags)\.#'
%currentWorkingDirectory%/src/Console/Installer.php
についてはそのままだと、
------ ------------------------------------------------------------------------------------------------------------ Line Console/Installer.php ------ ------------------------------------------------------------------------------------------------------------ 69 Call to static method customizeCodeceptionBinary() on an unknown class Cake\Codeception\Console\Installer. ------ ------------------------------------------------------------------------------------------------------------
というエラーに引っかかります。直前の行でif (class_exists('\Cake\Codeception\Console\Installer')) {
としているのに…。
もともとCakePHP側で作られるファイルなので、ファイル自体を解析から除外してしまうとよいでしょう。
他にも、Controllerなどから使えるloadModel()
でプロパティに追加したTableインスタンスが上手く認識されないようなのでignoreErrorsとしています。できれば正しくチェックしたいところですし、設定ファイルも泥臭くなってしまうのであまりやりたくないのですが…。ちゃんとした解決策があれば教えてもらいたいです。
なお、phpstan.neonは実行時のカレントディレクトリに存在している場合に参照されます。Gitリポジトリのルートなどに置いてバージョン管理に含めるとよいでしょう。
GitLab CIとの連携
ここまでで、ローカル時点での静的解析はできるようになりました。
ですがやっぱりこういうのはCIに組み込んだ方が圧倒的にカッコいいので、弊社で運用しているGitLab CIに組み込み、コミット時に自動で静的解析が走るようにします。
.gitlab-ci.yml
GitLab CIはファイルひとつでCIに関する設定ができます。
Gitリポジトリのルートに.gitlab-ci.yml
を置き、今回は下記の様な設定を書きました。
image: php:7.2 before_script: - "apt-get update -y" - "apt-get -y install git zip unzip zlib1g-dev libicu-dev" - "docker-php-ext-install intl" - "php -r \"copy('https://getcomposer.org/installer', 'composer-setup.php');\"" - "php -r \"if (hash_file('sha384', 'composer-setup.php') === '48e3236262b34d30969dca3c37281b3b4bbe3221bda826ac6a9a62d6444cdb0dcd0615698a5cbe587c3f0fe57a54d8f5') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;\"" - "php composer-setup.php" - "php -r \"unlink('composer-setup.php');\"" - "php composer.phar global require phpstan/phpstan" stages: - lint phpstan: stage: lint script: - "php composer.phar install" - "/root/.composer/vendor/bin/phpstan analyse" only: - branches except: - master
やってることとしてはcomposer入れてcomposer install
してcomposer global require phpstan/phpstan
しています。
運用するにあたってそこまで問題にはならないですが、地味に時間がかかるのでできれば短縮したいですね。
おわりに
簡単にではありますが、PHPStanのローカル環境導入と、Gitlab CIへの組み込みを試してみました。
ある程度使ってみて思いましたが、静的解析自体はエラーでないものをエラーと認識してしまったり、エラーに気付かなかったりと憎めない雑さが多いので、うまい付き合い方を探っていくとよいと思います。