PHP CS Fixerで快適PHPライフ

2017/03/30 追記

新しいバージョン (v2.0) の記事を書きましたのでこちらもご覧ください

fivestar.hatenablog.com


この記事は PHP Advent Calendar 2014 の8日目の記事です。

コーディング規約が守れない方とお悩みの方も、チームメンバーがなかなか守ってくれないとお悩みの方も、 PHP CS Fixer があればもう安心。PHP CS Fixer が PHP コードをコーディング規約に沿って整えてくれるので、秩序ある PHP ライフが約束されるでしょう。

そんなこんなで PHP Advent Calendar 2014 の 8 日目ですね。みなさんこんにちは、 fivestar こと小川です。いつのまにかクロコスがなくなって Y の人になっちゃいましたね。

昨今は PSR (PHP Standard Recommendation) の普及も進み、PHP 界隈にも統一感が訪れてきていますね。会社でコーディング規約を定める際、 PSR を基準とするところも増えてきていることでしょう。

ただ、コーディング規約を定めても、うっかり規約に沿わないコードを書いてしまう場合もあったり、コーディング規約の細部まで覚えられない場合や、どうしてもクセで間違えてしまう、なんて方も少なくないと思います。

組織によってはコードレビューを積極的に取り入れているところもあるかと思いますが、いちいち規約違反を指摘するのはもううんざり、なんて方もいるのではないでしょうか。

そんなときは PHP CS Fixer の出番です。

PHP CS Fixer とは

PHP CS Fixer (PHP Coding Standards Fixer) とは、その名の通り PHP コードをコーディング規約に沿うよう修正してくれるツール です。開発は活発に行われており、11月には v1.0 がリリースされました。現時点の最新バージョンは v1.2 がリリースされています。

PHP CS Fixer には標準で PSR-0PSR-1PSR-2 、および Symfony のコーディング規約 用の修正プログラムが用意されており、これらに準拠する場合はそのまま利用可能です。

ちなみに Symfony のコーディング規約は PSR-0/½ に準拠した上でさらにいくつかの規約を追加したもので、それらの上位規約として扱われ、 PHP-CS-Fixer のデフォルト に設定されています。

元々 Symfony のコアデベロッパーの Fabien Potencier 氏が開発したもので、前まで GitHub の fabpot ユーザが管理していたのですが、 最近 FriendsOfPHP に移管されたようです。

インストール

インストール方法は ドキュメント にいくつか書いてありますが、僕は Composer でインストールしてます。

% composer global require fabpot/php-cs-fixer
Changed current directory to /home/fivestar/.composer
Using version ~1.2 for fabpot/php-cs-fixer
...

Composer でインストールすると、 php-cs-fixer コマンドが利用可能になります。

ちなみに僕は ~/bin を Composer の bin-dir に設定してますが、デフォルトでは ~/.composer/vendor/bin あたりに入ると思うので Composer でインストールする場合は適宜パスを通す必要があります。

% composer config --global bin-dir "~/bin"

使い方

サンプル用に次のコードを用意します。これは PSR-2 の「名前空間の宣言直後に空行を入れる」「ブレス({ })は改行する」という2点に違反している状態です。

<?php
namespace Sample;
class Dummy {
}

このファイルが配置されたディレクトリ上で php-cs-fixerfix コマンドを実行すると対象に修正が行われます。 --dry-run オプションをつけることでドライランも可能です。次の例では対象を . にしているため、ディレクトリに含まれるすべての PHP ファイルが対象となります。

% php-cs-fixer fix . --dry-run --verbose --diff
F
Legend: ?-unknown, I-invalid file syntax, file ignored, .-no changes, F-fixed, E-error
   1) Dummy.php (line_after_namespace, braces)
      ---------- begin diff ----------
      --- Original
      +++ New
      @@ @@
       <?php
       namespace Sample;
      -class Dummy {
      +
      +class Dummy
      +{
       }
       
      
      ---------- end diff ----------

Fixed all files in 0.103 seconds, 5.250 MB memory used

level / fixers オプションで修正項目を制御する

デフォルトで (PSR-0/½ を含む) Symfony のコーディング規約に沿うよう修正が行われますが、内部的には項目が細分化されているため、例えば Symfony を基準にするが一部の項目は除外したいとか、 PSR-2 を基準に一部 Symfony の規約を追加したいとか、細かな制御も可能です。

項目の名称とか内容とかはたくさんあって書ききれないので ドキュメント を参照してください。ここを見るとどの項目がどの規約に準拠した内容かも確認できます。

前述のサンプルで取り上げた「名前空間の宣言直後に空行を入れる」「ブレス({ })は改行する」はそれぞれ「line_after_namespace」「braces」という名前の項目になっています。 fix コマンドに --verbose オプションをつけるとどの項目が適応されたか一目でわかるので参考になるかと思います。

PHP CS Fixer では psr0 < psr1 < psr2 < symfony のようにレベルわけされており、上位は下位を含む扱いとなっています。そのため PSR-0 および PSR-1 の項目のみで修正を行いたい場合、次のように --level=psr1 を指定すると、 PSR-0 および PSR-1 に分類される項目のみが実行されます。

% php-cs-fixer fix . --level=psr1

さらに psr1 に加えて line_after_namespace 項目を指定したい場合は --fixers=line_after_namespace を指定します。

% php-cs-fixer fix . --level=psr1 --fixers=line_after_namespace

psr2 から bracesvisibility を除いた項目のみ実行したいといった場合、項目名の先頭に - をつけて指定します

% php-cs-fixer fix . --level=psr2 --fixers=-braces,-visibility

このように level / fixers オプションを組み合わせることで、組織やプロジェクトごとの規約に沿った修正が実現できます。

.php_cs による設定管理

これらの指定は毎回しなくても、 .php_cs ファイルを用意することでデフォルトの挙動を設定しておくことが可能です。たとえば前述の最後のコマンドを設定化すると次のようになります。

<?php

return Symfony\CS\Config\Config::create()
    ->level(Symfony\CS\FixerInterface::PSR2_LEVEL)
    ->fixers(['-braces', '-visibility'])
;

.php_cs ファイルが配置されたディレクトリ上でコマンドを実行するか、 --config-file オプションで .php_cs へのパスを指定すると .php_cs が読み込まれるようになります。設定ではこの他にも、検索対象のファイルの指定や自前の項目の追加なんかも可能です。 ドキュメント にもう少し詳しく使い方が書いてあるのでやりたいことが他にある方は見てみるとよいでしょう。

基本的な使い方はこの辺を覚えておけば十分かと思います。あとはどの項目を適応するかの選択かなと思います。

一応、項目のロジックは自分で追加/拡張できるんですけど、トークン化したコードを正規表現なんかも駆使してゴリゴリやってるので、まあその辺用意するよりは素直に PSR や Symfony の規約に合わせちゃうのが楽だと思います。せっかく標準化を進めてるわけですからね。

もし組み合わせた場合の挙動を詳しく知りたい方がいたら、 ConfigurationResolver のテスト を見てみるといいかと思います。

ちなみに以前に「追加の指定と除外の指定を同時にした場合にうまく適応されないバグ」があって、実はその辺を僕が Pull Request を送って修正した んですけど、ちょろっと修正するつもりがオプションとか設定関連なんかをついでにってことであちこち修正させられて、結局1ヶ月くらいかけてやりとりしながらなんとか取り込まれたという思い出がありまして、そんなこともあったので今回このネタにしたんですけど、せっかくなので記事の最後に思い出話も書いておきますので興味ある方は読んでみてください。

PHP CS Fixer を Git のコミットフックに設定する

PHP CS Fixer が使えるようになったからといって実行されないと意味ないので、エディタの保存時に実行するようにするとか、何かにつけて実行されるようにしておくのがよいかと思います。

クロコスのときは、 Git のコミットフックでコミット対象のファイルに対して PHP CS Fixer が実行されるように設定していました。一応 Gist に pre-commit ファイルを置いておきます。ちなみにオプションの指定は適当です。使われる場合は好きなように変えたり、 .php_cs ファイルを用意するのがよいかと思います。

https://gist.github.com/fivestar/7b2491c633d7476fa33f

元々 sotarok が GREE 時代に書いていた、コミット対象に php -l を実行するフックを拡張したもので、対象にコーディング規約違反があると、修正した上で diff を表示してコミットを中止する、ということを行っています。中止しているのは、以前想定外の修正が行われてプログラムがバグったことがあったので内容を確認してから add するなり修正するなりしてね、という意図があるためです。

% git add Dummy.php
% git commit
CS fixed:
F
Legend: ?-unknown, I-invalid file syntax, file ignored, .-no changes, F-fixed, E-error
   1) /home/fivestar/sample/Dummy.php (line_after_namespace, braces)
Fixed all files in 0.121 seconds, 5.250 MB memory used
diff --git a/Dummy.php b/Dummy.php
index 62721b7..5e5873a 100644
--- a/Dummy.php
+++ b/Dummy.php
@@ -1,4 +1,6 @@
 <?php
 namespace Sample;
-class Dummy {
+
+class Dummy
+{
 }
  
----
git pre-commit hook error
...

エディタで設定するかどうかは基本各自に任せていたので、最低限これを入れて防げるようにだけしていました。一応クロコスのときはリリース前に必ずコードレビューが入るようにしていたので、そこで規約違反見つけたら設定漏れてるかどうか確認したりはしていました。

このようにどこかのタイミングで必ず実行されるようにしておくと、コードレビュー時に最低限規約に沿った状態から始められるので余計なチェックをしなくて済むのでおすすめです。

一応ドキュメントに エディタ用プラグインリスト があるのでエディタに設定しておきたい人はチェックしてみてください。

Pull Request 送った時の話

蛇足かもしれませんが、前にちょこっと触れた通り、 Pull Request を送った時の話を少し書いてみます。

repo owner の話

このプロジェクト、 keradus ってポーランド人が活発に動いていて、彼とずっとやりとりしていたんですけど、彼についてすばらしいなと思ったことがありまして。彼は基本的にこちらの活動を褒めた上で、指摘や要望をあげるんですよね。あと1週間くらい修正を放置すると催促の連絡がくるんですけど、その内容も前向きで「いい感じにできてきてるから続報待ってます!」のような感じなんです。

特に OSS に関わる人の多くはボランティアとして活動しているだけに、モチベーションがエネルギーに繋がると思うんですけど、彼はそこのコントロールがうまいなあと思いました。さらに相手のモチベーションをなるべく下げないようにしつつ、別の要望を言ってくるんですよね。おかげで僕は既存のいけてない部分をあちこち直す羽目になったんですけど、まあそうやってうまくリソースを使って開発を継続的に進めていくということは大切だなと改めて思いました。実際のところ本人がどう思ってたのかはわからないんですけどね。

英語の話

PR を送ってコード修正するのは別に大したことではなかったんですけど、 README に説明書き足してくれって言われちゃって、まともな英語かけるかなあなんて最初はビクビクしたんですけど。結局てきとうに書いて送ったら一応意味は通じたっぽくて、 the つけろとか細かい指摘もらったのであとはもう言われるがままに整えて一応なんとかなりました。

英語が得意じゃなかったとしても、とりあえずそれっぽく書いておけば誰かが指摘するなり直してくれたりするし、やるだけやってみるとよいと思いました。

ただまあ README だけじゃなくて、 Pull Request 自体全体でコメント 80 件以上ついてるように結構なやりとりをしなければならなくて、そこにだいぶ時間をとられたなーとは思ってるので、英語力を鍛えるのは課題だなあと改めて認識しました。

まとめ

なんかさらっと終わらせるつもりだったんですけどだいぶあれこれ書いちゃいました。その労力をパーフェクト PHP 改訂版に回せって話ですね。すいませんがんばります。来年にはきっと..

さてさて、 PHP CS Fixer を活用すればコーディング規約を準拠するコストがぐっと抑えられることが伝わったらいいなーと思います。あとはまあ PSR の採用が進むといいなと。

PHP CS Fixer は使い方自体は簡単で、あとはどう設定するかだと思います。ドキュメントは1ページでだいたいまとまってるので、何かあったらドキュメントを見てみるのがよいでしょう。

最近 Go でスクリプト書いたりしてるんですけど、 Go には標準で gofmt ってのがあって細かいこと気にせずにコードかけるんで、どんな言語であれ規約に沿って自動的に直してくれるツールは積極的に導入していきましょう。

宣伝

直接は関係ないんですけど、今度 スクー でコードレビューについてお話しさせていただく予定です。日付とかまだ調整中なんですけど、決まったらまた告知するんでよかったらみてください。

追記

12/24 19:00 に決まりました!ぜひ見ていってください。

コードレビュー入門 小川 雄大 先生 - Schoo(スクー)

P.S.

そういえば PHP_CodeSniffer とかどうなってるのかなーと思って今みたら 12/5 に 2.0 がリリースされたばかりっぽくて、いまでも開発続いてるみたいなんで使いたい人は使ってみればいいんじゃないでしょうか。