Page 1
PHPプロジェクトに
静的解析を新規導入する
Introduce static analyzers to PHP project
公開日:
by USAMI Kenta @tadsan
に東京都港区六本木の株式会社メルカリで開催された『大改修!PHPレガシーコードビフォーアフター』でPHPの匠(30分)として発表しました。
PHPプロジェクトに
静的解析を新規導入する
Introduce static analyzers to PHP project
お前誰よ
うさみけんた (@tadsan)
/ Zonu.EXE
本日のお題
さて
PHPカンファレンス
まだユニットテストと同 様に「あって当然」のもの とまでは普及できてない
既存のプロジェクトに
導入する話は
あまりできてなかった
なぜ静的解析を
するのか
関数がどのような引数
を期待しどんな値を 返すのか明示したい
プログラムが
間違ってることを
実行前に検証したい
それ以上に
オブジェクトのメソッド
やプロパティが適切に
補完されると気持ち良い
補完がきちんと機能 するようにPHPに 型をつけていこう
ただし
静的解析は、現に
「動いている」事実を
覆すものではない
エラーとして見えないが
「たまたま動いている」
現実を炙り出すことはある
変更によって動かなく
なってしまうことを
「動かさずに」発見できる
かもしれない
静的解析基盤を整えた 瞬間に致命的な問題を 検出できることはまれ
ということで静的 解析をしましょう
静的解析
ツールの種類
現行の高度な静的解析ツール
PhpStorm (IDE)
Phan
PHPStan
Psalm
PhpStorm
よいところ
導入が最も簡単
十分な精度のエラー検出
よくないところ
エラーの列挙ができないので CIのフローに組み込めない
Phan
よいところ
導入は比較的簡単
とても高い精度のエラー検出
よくないところ
大規模プロジェクトでは解析
に時間がかかる
PHPStan
よいところ
実行時情報を使用し高速 高い精度のエラー検出
よくないところ
Composer以外の依存関係 があると導入にコツが要る
Psalm
よいところ
独自の引数アサーション 高い精度のエラー検出
よくないところ
大規模プロジェクトでは解析
に時間がかかる
静的解析の選択軸
Phan PHPStan
定義のみ実行が必要
解析方法
実行しない(静的)
(静的と動的のmix)
php-ast (高速/C拡張)
依存関係 PHP-Parser
Tolerant PHP Parser(低速)
プロジェクトおよび 対象ファイルを
解析単位
依存ライブラリ全体 個別指定
つまりどれがいいのか (CI)
現時点ではそれぞれのツール で検出項目に一長一短がある
ため、使い分けるとよい
PHPStanは高速なのでGitで ブランチにpushするごとに検 査するようなCIと相性がよい
PHPStanの制約
クラス/関数/定数の定義と副作 用のある処理は明確に分かれ
ていないといけない
つまり、PSR-1
Phanなら定義と処理が混在
したファイルも解析できる
こういうコードはPHPStanで解析できない
<?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も推奨
Dockerfileにphp-astを足す
RUN pecl install ast \
&& docker-php-ext-enable ast
astをデフォルト有効化したくなければ
RUN pecl install ast
別のコンテナイメー ジに分割したい場合
docker/phan/Dockerfile
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
docker-compose.yml
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 という ラッパースクリプト を使うことにします
./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が未定義
deprecatedコードの検出
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
初めてでも
できちゃった