面倒いの抜きでPhanを使ってみる
あけましておめでとうございます!
サーバーサイドやってます金城(@o0h)です。
年末年始は毎年恒例の「ヘルシング全巻読み直し」をしていました。カッコいいですね・・
- 作者: 平野耕太
- メディア: Kindle版
- この商品を含むブログを見る
弊社ではPhanを用いた静的解析を行っております。
今回は、「多分、これが最も1番の最速さでPhanを試す方法になるんじゃないかな?」というお話をいたします。
cf) リファクタリング対象を選ぶ戦略を決めよう - コネヒト開発者ブログ
tl;dr
- cloudflare/phan というイメージがあります
- これを使うと、「php7環境の用意」だとか「php-astの用意」とかいった手間を全部すっ飛ばしてPhanの静的解析が実行できます
- すぐ使えるようにコマンドがあるので、↑のページの "Getting docker-phan" のセクションを参照してください*1
おしながき
概要
これは「静的解析したいけど環境作るの面倒くさい(そもそもphp7じゃない!)」という時のお供(になると思います)。
「Phanの実行に必要なもの全部入り」になっているイメージを使って、使い捨て感覚で静的解析を実行できるようになりました。
Cloudflare社が公開してくれているDockerイメージがそれを可能たらしめてくれているわけですが、こちらは本家のwikiからも言及されているものとなります。*2
そういう点では十分に信頼できるのかな?と思っています。
ただし、既存の「実際にアプリケーションコードが動作するPHP環境」とは違う「クリーンな状態(に近い)のPHP環境」になるため、少しハマった点もありました。
この記事では、「利用方法」にあわせて「実際にCakePHPアプリケーションを解析するときに用意したもの・ハマった点」を取り上げたいと思います。
解決したい問題
before(現状)
- 本番環境とローカル環境で、あまり設定等を変更したくない
- でも本番でphp-ast使うわけでもないし・・・いらないものを入れている状況
- ローカル環境でxdebugを利用していて、「いつものコンテナの中でPhanをそのまま叩くと爆重」
- でもいちいち「別設定ファイルを用意して、Phanのときにはそっちを使う」の面倒くさい・・
- php71にしたら早くなるんだっけ?でも残念、全てのPJがphp71に出来ているわけではありません!
- でもわざわざPhanのためにphp71環境を用意するのは(ry
after(嬉しい)
- ローカル環境/本番環境で「php-ast/phanがいらない」状態に持っていける
- 「すべてPhanのために用意しました」環境をつくれる
- あくまでやりたいのは静的解析なので、そもそも「アプリケーションが正常に動くか」というのは別問題なわけで
- php71!
- xdebugも当然いない!
- Alpine Linux、軽量、嬉しい
利用方法
例えば、こんな二通りのやり方があるかな?という気がしています。
A. ターミナルから即実行できるようにしておく
Getting docker-phan
のセクションに説明がある内容です。
.bashrcなどに、以下のように記述しておきましょう
phan() { docker run -v $PWD:/mnt/src --rm -u "$(id -u):$(id -g)" cloudflare/phan:latest $@; return $? }
そして、おもむろにターミナルから phan -p -o analyze.txt
などと叩きます。
すると「カレントディレクトリを対象に(./.phan/config.php
があれば、それを読みつつ)、 -p(--progress-bar)
オプション付きで、 analyze.txt
にアウトプットする」という形で実行されます。
すなわち、「コマンドにくっつけられたオプションは、そのまま透過的に渡される」という形です。
B. 簡単に利用できるようにして配布する
チームメンバーのローカル環境を問わず「すぐに叩けるよう、共通化しておきたい」といったときはdocker-compose.ymlなどを用いて、コードと一緒にgit管理してしまうのも良いかと思います。
docker-compose.yml
に以下のように記述します
version: '3' services: phan: image: cloudflare/phan volumes: - .:/mnt/src:cached entrypoint: [] command: sh -c "cd /mnt/src && /opt/phan/phan -p --color"
そうすると、 docker-compose run --rm phan
とすれば呼び出し可能です。
この場合はPhanに渡すオプションが固定になるので、必要に応じてphan
コンテナに任意のコマンドを渡してrunする形で利用すると良いと思います。
なにが行われているの?(ざっくり)
イメージのビルドについて
- PHP/composerのインストール
- https://github.com/cloudflare/docker-phan/blob/a256af03d21e767db84d3b0134b47dac7e3ce7e1/builder/scripts/mkimage-phan.bash#L26
- php系の入ってくるもの/入ってこないものはコチラでチェックしてください
- Phanのインストール
- https://github.com/cloudflare/docker-phan/blob/a256af03d21e767db84d3b0134b47dac7e3ce7e1/builder/scripts/mkimage-phan.bash#L46
- このときに、
/opt/phan/phan
で実行できるように設置されている
- php-astのインストール
ENTRYPOINTについて
cloudflare/phan
は何をしてくれるんだい、という部分ですね。
先に触れた docker-compose.yml
で「commandをどう書けばいいのかな?」については、こちらを参考にしておりました。
/mnt/src
にcdしてexec php7 /opt/phan/phan "$@"
を実行している
というシンプルなものですね。。 そのため、先に紹介しているコマンドをみても
docker run
-v $PWD:/mnt/src
: 現在位置を「解析対象」としてコンテナにマウントする--rm
: 実行完了後に破棄-u "$(id -u):$(id -g)"
: 権限周りで問題になりにくいようにしつつcloudflare/phan:latest
: 最新版のイメージを利用して$@
: 透過的にオプションを渡す
という、ごくごく単純な処理しか要らなくなるわけです。
利用法については、これで終わりです。
CakePHPを対象に実行してみる
Phanの設定ファイルを書いてみる
.phan/config.php
に諸々の設定を投げ込んでおくと、読みに行ってくれます。*3
こんな感じで動くのではないでしょうか。
<?php return [ // ロード対象 'directory_list' => [ 'src', 'vendor', '.phan/stubs' ], // ロード対象のうち、解析対象から除外するもの 'exclude_analysis_directory_list' => [ 'vendor', 'src/Console', '.phan/stubs', ], // 以下は好み 'allow_missing_properties' => true, // @propertyの省略を許容 'null_casts_as_any_type' => true, // nullのキャストを許容 'backward_compatibility_checks' => false, // 後方互換性はチェックしない 'quick_mode' => true, // 対象の関数の中の関数を再帰的に検査しない 'minimum_severity' => 10, // SEVERITY_CRITICALが対象 ];
Phan\Exception\IssueException
について(ハマった点)
さて、以上の設定を行った上で試しに走らせてみたら、動きませんでした!😫 どうも例外がスローされてしまいます・・・
Phan\Exception\IssueException in /opt/phan/src/Phan/AST/ContextNode.php:1261
あまり詳細な情報が出力されていなかったので、直接その付近のコードをいじりつつ眺めてみたら、どうもCake本体のコードが引っかかっているようです。
「\PDO::PARAM_BOOLへの参照があるけど、そんなクラスないよ!」みたいな内容でした。 「PDOクラス未定義ってなんだろうな〜」と暫くハマっていたのですが、 もしや?とおもってphp7-pdoをインストールしてみました。
apk add --upgrade php7-pdo
すると挙動が変わります。
ここで、「そういえば普段使っているのはDockerfileでゴニョゴニョしていたっけ〜」と思い出したわけです。
stubについて
docker-phanイメージをベースにコンテナ作るか?という考えも頭をよぎったのですが、 あくまで静的解析ができればよい(コードを動かすわけではない) ということで、Phanには「スタブ」という仕組みがあります。
How To Use Stubs · phan/phan Wiki
要するに、「クラスやプロパティやメソッドが定義されていれば良い」というゴールを設定できるのです。
以下の手順で使います。
- stubを定義する
- そのstubを配置したディレクトリを、config.phpから
directory_list
に設定する - 同時に
exclude_analysis_directory_list
にも設定する
というステップで利用できます。
stubは、単純に「PHPのクラスを定義してphpdocコメントをつける」と言うものになり、新たにDSLなどを覚える必要はありません。
<?php class AppDomainException extends \Exception { /** @var string */ public $hoge; }
みたいなものです。
が!PHPのスタンダードなものについては、JetBrains社が配布しているものをそのまま利用することができます(Apache2ライセンス)
これはPHPStormを利用している方にはおなじみの、「標準クラス上で⌘+bを押したら開くアレ」になります。
今回は、こちらからPDO.phpを始めとしたいくつかのスタブを喰わせることで、無事に完走することができました。
まとめ
静的解析は「くらだないミスをなくし」たり、IDEを利用している身においては「普段のコーディングのストレスを軽減する」という効果もあると感じています。
ただし、PHPだと「やり始めるまでの腰が重い」のも事実でしょう。
これに対して、「使い捨てDockerイメージ」みたいなものは非常にインパクトが大きいと思います!正に巨人の肩に乗る感じですね。
未体験の方も、これを気に「とりあえず1回やってみようかな」くらいの感覚で遊んでみてはいかがでしょうか〜*4
お役立ちリンク
Phan導入当初も含めて、とても助けを得たURLたちです
*1:この記事を書き始めてから見つけたのですが、まさに「コマンドを使う」感覚でPhanを丸ごと実行できる!!というユーザー体験だと思います。すごい。。 → コマンド実行環境としてのDocker // Speaker Deck
*2:Getting started / From a Docker Image
*3:別箇所に置きたい場合は実行時に -k /path/to/config.php として渡せる
*4:実際の導入においては、 https://github.com/phan/phan/wiki/Incrementally-Strengthening-Analysis がとても参考になります