PHP Code Beatifier and Fixer を使って CakePHP2 のコーディング規約違反を自動で直す

社内でかなり前に CakePHP2 で開発されていたアプリを見たら、リファクタリングうんぬんの前にコーディング規約が乱れすぎていて、読むのもつらい状況でした。

手動で直せる分量ではなく、どうしたものかと困っていましたが、[2014年12月にリリースされていた PHP_CodeSniffer 2.0.0](https://www.squizlabs.com/php-codesniffer/2.0.0-released) からコーディング規約チェッカー (phpcs) の他に修正もできるツール (PHPCBF; PHP Code Beatifier and Fixer) も同梱されるようになっていたので、これを使ってみることにしました。


インストール手順
----------------
今回は Composer を使って、プロジェクトグローバルに PHPCS をインストールしてみます。

### Composer をインストール

```bash
curl http://getcomposer.org/installer | php 
```

### CodeSniffer をインストール

```bash
composer global require "squizlabs/php_codesniffer"
composer global show --installed
```

### PATH に Composer がインストールするバイナリの保存先ディレクトリを設定する

```bash
vim ~/.bash_profile

(PATHの行を以下のように修正)
PATH=$PATH:$HOME/bin:$HOME/.composer/vendor/bin

source ~/.bash_profile

phpcs -h
phpcbf -h
(それぞれのツールのヘルプが表示されれば OK)
```

### CakePHP 用ルールセットをインストール

[cakephp/cakephp-codesniffer](https://github.com/cakephp/cakephp-codesniffer/) の master ブランチにある最新バージョン (2.0.x) は CakePHP3 に合わせて [^1][PSR-2](http://www.infiniteloop.co.jp/docs/psr/psr-2-coding-style-guide.html) 準拠になっており、CakePHP2のコーディング規約とかなり違うので、[^2][update-fxer-2 ブランチ](https://github.com/cakephp/cakephp-codesniffer/tree/update-fxer-2) を使います。

[^1]: タブの代わりにスペース4つを使うなど
[^2]: `fixer` のタイポではなく本当にそういうブランチ

```bash
mkdir sniffs
curl -LO https://github.com/cakephp/cakephp-codesniffer/archive/update-fxer-2.zip
unzip update-fxer-2.zip -d sniffs
cd sniffs/
mv cakephp-codesniffer-update-fxer-2 CakePHP
```

### cakephp-codesniffer のルールセットを phpcs に登録する

```bash
phpcs --config-set installed_paths ~/sniffs/
phpcs -i
phpcbf -i
(CakePHP が追加されていれば OK)
```


添削＆修正してみる
------------------
phpcs, phpcbf ともに、`--standard=CakePHP` というオプションを付ければ CakePHP2 のルールに沿ってコードの添削＆修正が行われます。

### 添削
規約違反を一覧で表示するには以下のように叩きます。

```bash
$ phpcs --standard=CakePHP /PATH/TO/MYAPP/app/Controller/AppController.php

FILE: /PATH/TO/MYAPP/app/Controller/AppController.php
----------------------------------------------------------------------
FOUND 93 ERRORS AFFECTING 58 LINES
----------------------------------------------------------------------
  35 | ERROR | [x] Tabs must be used to indent lines; spaces are not
     |       |     allowed
  35 | ERROR | [ ] Scope modifier not specified for member variable
     |       |     "$today"

...

----------------------------------------------------------------------
PHPCBF CAN FIX THE 66 MARKED SNIFF VIOLATIONS AUTOMATICALLY
----------------------------------------------------------------------

Time: 41ms; Memory: 4.5Mb
```

具体的な修正内容を確認したければ、`--report=diff` を付けます。

```bash
$ phpcs --report=diff --standard=CakePHP /PATH/TO/MYAPP/app/Controller/AppController.php

--- /PATH/TO/MYAPP/app/Controller/AppController.php
+++ PHP_CodeSniffer
@@ -32,80 +32,82 @@
  */
 class AppController extends Controller {

-    var $today = "";
-    var $components = array('RequestHandler', 'Security');
-    var $oneTimeString = "";
...
+       var $today = "";
+
+       var $components = array('RequestHandler', 'Security');
+
+       var $oneTimeString = "";
```

### 修正
phpcs の代わりに phpcbf コマンドを使えば、ファイルが修正されます。

```bash
$ phpcbf --standard=CakePHP /PATH/TO/MYAPP/app/Controller/AppController.php

Changing into directory /PATH/TO/MYAPP/app/Controller
Processing AppController.php [PHP => 732 tokens in 111 lines]... DONE in 17ms (66 fixable violations)
        => Fixing file: 0/66 violations remaining [made 3 passes]... DONE in 60ms
Patched 1 file
Time: 123ms; Memory: 5.5Mb
```


コーディング規約違反は心の乱れ。素早く直しましょう。


補足: Inflector に依存する修正がエラーで止まる
----
CakePHP には「大文字<->小文字」や「CamelCase <-> snake_case」の変換を司る Inflector というクラスが存在しますが、これに依存するコード修正が動くとエラーになってしまいました。

```bash
$ phpcbf --standard=CakePHP /PATH/TO/MYAPP/app/Controller/FooBarController.php

Changing into directory /PATH/TO/MYAPP/app/Controller
Processing FooBarController.php [PHP => 2400 tokens in 327 lines]... DONE in 75ms (492 fixable violations)
        => Fixing file: 492/492 violations remainingPHP Fatal error:  Class 'Inflector' not found in /home/vagrant/sniffs/CakePHP/Sniffs/NamingConventions/ValidVariableNameSniff.php on line 281
[PHP => 2400 tokens in 327 lines]...
```

どうやら phpcbf が Inflector を読み込めていないようです。

phpcbf 実行時にどう読み込むべきか悩みましたが、ひとまず今はコード修正だけできればいいので、phpcbf スクリプトの中で直接 Inflector を `require_once` するという超汚いパッチで回避しました。

`~/.composer/vendor/squizlabs/php_codesniffer/scripts/phpcbf` を以下のように修正します。

```php
#!/usr/bin/env php
<?php
// 以下の行を追加
require_once '/PATH/TO/MYAPP/Vendor/cakephp/cakephp/lib/Cake/Utility/Inflector.php';
```

もっとスマートなやり方があれば教えて下さい。


参考
----
- [PHP_CodeSniffer - CodeSnifferのインデントをタブ対応にする方法 - Qiita](http://qiita.com/dozo/items/d581597570dba436dd8c)
- [PHP Code Snifferを使ってCakePHPのコーディング規約をチェックする - Composerで簡単インストール](http://tech.basicinc.jp/php/2013/08/18/php_composer/)
- [Fixing Errors Automatically · squizlabs/PHP_CodeSniffer Wiki](https://github.com/squizlabs/PHP_CodeSniffer/wiki/Fixing-Errors-Automatically)
- [FriendsOfPHP/PHP-CS-Fixer](https://github.com/FriendsOfPHP/PHP-CS-Fixer)
- [squizlabs/PHP_CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer)