kakakakakku blog

Weekly Tech Blog: Keep on Learning!

PHPCompatibility で非互換になった項目を静的解析する

最近 PHP のバージョンアップの検証をしていて,非互換 (incompatible) になった項目を静的解析できる PHP_CodeSniffer + PHPCompatibility を導入した.非互換を完全に網羅してチェックできるわけではなく,最終的には地道にコードを読んで書き換えていく必要はあるけど,ある程度のレベルで自動化できるのは助かる.

github.com

github.com

PHP_CodeSniffer をインストールする

Composer 経由でグルーバルにインストールする.

インストール方法は複数あって,詳しくは GitHub 参照で.

$ composer global require "squizlabs/php_codesniffer=*"

PSR1 などデフォルトでセットされているルールを確認できる.

$ phpcs -i
The installed coding standards are MySource, PEAR, PHPCS, PSR1, PSR2, Squiz and Zend

PHPCompatibility をインストールする

PHP_CodeSniffer の Standards ディレクトリ直下で PHPCompatibility を git clone する.

インストール方法は複数あって,詳しくは GitHub 参照で.

$ cd ~/.composer/vendor/squizlabs/php_codesniffer/CodeSniffer/Standards
$ git clone git@github.com:wimg/PHPCompatibility.git

新しく PHPCompatibility ルールが追加された.

$ phpcs -i
The installed coding standards are MySource, PEAR, PHPCompatibility, PHPCS, PSR1, PSR2, Squiz and Zend

ruleset.xml

PHP_CodeSniffer の実行ルールを XML で定義することができる.詳しくは Wiki にまとまっている.

以下はサンプルとして3種類のアノテーションを定義している.

  • exclude-pattern : 除外するディレクトリを指定できる
  • exclude : 除外するルールを指定できる
  • arg name="testVersion" 非互換をチェックする PHP バージョンを指定できる(これは PHPCompatibility の機能)
<?xml version="1.0"?>
<ruleset name="my_rules">
  <description>my_rules</description>

  <exclude-pattern>fuel/app/logs/*</exclude-pattern>
  <exclude-pattern>fuel/app/tmp/*</exclude-pattern>
  <exclude-pattern>fuel/app/vendor/*</exclude-pattern>

  <rule ref="PHPCompatibility">
    <!-- 特定の PHP バージョンの非互換をチェックする -->
    <arg name="testVersion" value="5.6" />
    <!-- 任意のルールを除外することもできる -->
    <exclude name="PHPCompatibility.PHP.DefaultTimezoneRequired" />
  </rule>
</ruleset>

実行する

--standardruleset.xml を指定して,--extensions でチェックするファイルの拡張子を .php に限定して実行する.例えば PHP 5.4 で非互換になった「break の引数に変数を使う記法」を検知することができた.実行オプションなども Wiki にまとまっている.

$ phpcs --standard=ruleset.xml --extensions=php ~/xxx/fuel/app

FILE: ~/xxx/fuel/app/xxx.php
----------------------------------------------------------------------
FOUND 1 ERROR AFFECTING 1 LINE
----------------------------------------------------------------------
 40 | ERROR | Using a variable argument on break or continue is
    |       | forbidden since PHP 5.4
----------------------------------------------------------------------

試しに PHPCompatibility 以外で PSR1 と FuelPHP のルールも適用して流してみたけど,驚くほど大量に指摘項目が出てそっ閉じした.静的解析は開発プロセスの一貫として強制力を持たせないとダメだと思ってるけど,保守フェーズから導入する場合は既存コードをどうする問題があって結構大変…!

最終的には地道にチェックしていく必要がある

冒頭にも書いた通り,非互換を完全に網羅してチェックできるわけではなく,最終的には地道にコードを読んでチェックしていく必要がある.例えば PHP 5.4 で非互換になった break の件と類似する「break の引数に 0 を使う記法」は検知できなかった.PHPCompatibility にプルリクして検知できるようにしたら良さそう.

break と continue への引数として、変数は使えなくなりました。つまり、 break 1 + foo() * $bar; などとは書けなくなったということです。 静的な引数を使うのはもちろん可能で、break 2; などは使えます。 この変更の副作用として、 break 0; や continue 0; が使えなくなりました。

また FuelPHP などのフレームワークレベルの非互換もあるし,Packagist から取得してくるライブラリの非互換もあるし,実際には考慮するべきポイントがたくさんある.

まとめ

PHP_CodeSniffer と言うと PSR1 のような一般的な静的解析のイメージだったけど,PHPCompatibility を使うと非互換チェックができるというのを最近知って,導入してみた.銀の弾丸ではないけど,自動化して繰り返し実行できるメリットは大きく,活用していこうと考えている.