Page 1
PHPプロジェクトに静的解析を新規導入するIntroduce static analyzers to PHP project
公開日:
by USAMI Kenta@tadsan
に東京都港区六本木の株式会社メルカリで開催された『大改修!PHPレガシーコードビフォーアフター』でPHPの匠(30分)として発表しました。
PHPプロジェクトに静的解析を新規導入するIntroduce static analyzers to PHP project
本日のお題
さて
PHPカンファレンス2016
まだユニットテストと同様に「あって当然」のものとまでは普及できてない
既存のプロジェクトに導入する話はあまりできてなかった
なぜ静的解析をするのか
関数がどのような引数を期待しどんな値を返すのか明示したい
プログラムが間違ってることを実行前に検証したい
それ以上に
オブジェクトのメソッドやプロパティが適切に補完されると気持ち良い
補完がきちんと機能するようにPHPに型をつけていこう
ただし
静的解析は、現に「動いている」事実を覆すものではない
エラーとして見えないが「たまたま動いている」現実を炙り出すことはある
変更によって動かなくなってしまうことを「動かさずに」発見できるかもしれない
静的解析基盤を整えた瞬間に致命的な問題を検出できることはまれ
ということで静的解析をしましょう
静的解析ツールの種類
現行の高度な静的解析ツール
PhpStorm (IDE)
Phan
PHPStan
Psalm
PhpStorm
よいところ
導入が最も簡単
十分な精度のエラー検出
よくないところ
エラーの列挙ができないのでCIのフローに組み込めない
Phan
よいところ
導入は比較的簡単
とても高い精度のエラー検出
よくないところ
大規模プロジェクトでは解析に時間がかかる
PHPStan
よいところ
実行時情報を使用し高速
高い精度のエラー検出
よくないところ
Composer以外の依存関係があると導入にコツが要る
Psalm
よいところ
独自の引数アサーション
高い精度のエラー検出
よくないところ
大規模プロジェクトでは解析に時間がかかる
Phan PHPStan
定義のみ実行が必要
解析方法
実行しない(静的)
(静的と動的のmix)
依存関係
php-ast (高速/C拡張)
Tolerant PHP Parser(低速)
PHP-Parser
対象ファイルを
解析単位プロジェクトおよび依存ライブラリ全体
個別指定
つまりどれがいいのか (CI)
現時点ではそれぞれのツールで検出項目に一長一短があるため、使い分けるとよい
PHPStanは高速なのでGitでブランチにpushするごとに検査するようなCIと相性がよい
PHPStanの制約
クラス/関数/定数の定義と副作用のある処理は明確に分かれていないといけない
つまり、PSR-1
Phanなら定義と処理が混在したファイルも解析できる
<?php include __DIR__ . '/config.php';
$connection = new PDO(DSN);drop_table($connection);exit;
function drop_table($connection){$connection->exec('DROP TABLE `users`;');}
静的解析を導入しよう
CakePHP 4.0.0ではPHPStanとPsalmで型定義が大幅に改善(現在RC1)
じゃあPhanを入れるか
基本はかcomposer require Pharファイルをダウンロードするだけ
forteeはレガシーを自称しているがDocker Compose
Docker Compose使ったことない僕の方がレガシーなんですけど…
Docker Phan
[検索]
だめです
古すぎる!
Phanはほぼ毎週アップデートされてさまざまな改善がある
最新のPhanだけが良いPhanだ
Phanの要求環境
基本的に普通のPHPイメージ
ext-ast (PECL ast)は必須
Xdebugは無効化したい
パフォーマンスに影響する
ext-pcntl, ext-iconv,ext-mbstringも推奨
RUN pecl install ast \&& docker-php-ext-enable ast
RUN pecl install ast
別のコンテナイメージに分割したい場合
FROM php:7.3-cli RUN apt-get update -y \&& apt-get install -y --no-install-recommends \libicu-dev \unzip RUN docker-php-ext-install -j$(nproc) intl pcntl
RUN pecl install ast && docker-php-ext-enable ast
COPY --from=composer /usr/bin/composer \/usr/bin/composer
RUN composer global require phan/phan ENTRYPOINT php /root/.composer/vendor/bin/phan WORKDIR /var/www/html
version: "3.7"services:phan:build: ./docker/phan volumes:- ./:/var/www/html:cached entrypoint: php /root/.composer/vendor/bin/phan working_dir: /var/www/html
独立したコンテナに分けるメリットも大きくない気がするので既存のPHP用コンテナに混ぜ込めばいいと思います
composer require --devを使うべき?
好みでどうぞ
アプリケーションと依存関係が混ざるのでメンテ難しいと思います
僕はサブディレクトリにcomposer.json作ります
composer-bin-pluginを使ってもいいかも
以後./phan というラッパースクリプトを使うことにします
#!/usr/bin docker-compose run --rm php-cli \.phan/vendor/bin/phan "$@"
Windowsならphan.batってファイルを作って %* あたりでパラメータ展開すれば動くと思うけど、詳しいことはWindowsに詳しいひとに任せた!
phan --initで初期化できる
.phan/config.phpが生成される
自動追加されなかったdirectory_listを追加していく
composer requireで直接依存するパッケージだけが自動追加される
基本的な使いかた
./phan
-o phan.log結果をphan.logに書き出す
-j 4並列実行数。CPUのコア数を指定するのが良い (諸説あり)
baseline
--save-baseline=.phan/baseline.php を付けて実行するとファイルごとに検出されたIssueを保存できる
--load-baseline で実行すると新たに増えたものだけ報告される
suppress_issue_types
報告しないIssueをリストアップ
フレームワーク側に問題があるなら握り潰すしかない…
ある程度の妥協も時には必要
CakePHP 4.0になればおそらくほぼ消せるでしょう
tadsanのよくやる手法
masterブランチの内容をphan-master.logに書き出す
リファクタリングして変更の都度解析結果をphan.logに書き出す
diff -u phan-master.log phan.log
実際に検出されたミス
$part変数が未定義
$videoEmbedHtmlと$slideEmbedHtmlが未定義
CakePHP 4.0.0でプロパティにアクセスできなくなる
use Exceptionがないので現在の名前空間を指してる
おまけ: 謎のException
3箇所くらいで登場
どれもAuraを直接使ってない
たぶんPhpStormの自動挿入
値を返せない(単にreturn;でよい)
そろそろまとめ
実際稼動しているアプリなので致命的な問題はなかなか見つからない
リファクタリングやフレームワークのバージョンアップでは大きな武器になる
地味なものも目立つが地道に潰していこう
おまけ
今回forteeのコードで初めてDocker Composeで構築されたPHPアプリケーションに触れた
なるほどわからん
手持ちのレガシーPHPをDocker Composeに載せてみるか
Vagrant →Docker Compose
phpdotenv
.env → conf/.env
docker-composeを噛ませてsetupスクリプトを実行できるように場所を変更
php -S →Apache HTTP
初めてでもできちゃった
