[PHP]10分で静的解析による統一されたコーディング環境を整える(Phing / php-md / php-cs-fixer)

なんでやるの?

コードを静的解析させることにより開発者の書き方のクセをなくす。

コードレビューの時に正しく動いててもフォーマット通りの書き方じゃないと
見てて違和感感じますよね?汗

書いてるうちに無意識に宣言していたキャメルケースでない変数とかも拾っておきたいとか

要するに最低限人に見せられるように整形してからコミットしましょうねということ

対象としては、php-mdやphp-cs-fixerを使ったことのない方だったら
多少ためになるかと思います

Phing

PHPのbuildツールを束ねさせることができる便利なやつ

例えば、下記コマンドを打てばphingが設定ファイル(build.xml)を読み込んで
phpunitやphp-mdなどを実行させることができる。

$ vendor/bin/phing build

実際の実行結果

$ vendor/bin/phing build

Buildfile: /var/www/html/build.xml

プロジェクト名 > hoge:

     [...] 

プロジェクト名 > phpmd:

    [phpmd] Processing files...
    [phpmd] Finished processing files

プロジェクト名 > phpunit:

     [exec] PHPUnit 4.8.31 by Sebastian Bergmann and contributors.
     [exec] 
     [exec] ....
     [exec] 
     [exec] Time: 205 ms, Memory: 15.00MB
     [exec] 
     [exec] OK (4 tests, 2 assertions)

プロジェクト名 > hoge:


BUILD FINISHED

Total time: 1.0854 second

とこんな感じにコマンド叩けば教えてくれます。

本題

インストール

composer.jsonに下記を追加

phpunitはphpのバージョンに合わせて変えてください
php7系ならば、phpunitのバージョンは5ですかね
今回はphp5.6の前提でいてください

phanを使わないのはphp5.6だからです、、

composer.json
"require-dev": {
    .
    .
    "phpunit/phpunit": "~4.0",
    "phpmd/phpmd" : "*", //コードを静的解析し、命名規則やコードサイズが大きすぎないか等検査するために使用
    "phing/phing": "*",
    "fabpot/php-cs-fixer": "1.*" //コーディングスタイルを合わせるために使用。psr-2への変換やphpdocを整形できる。設定ファイルは.php_cs
   }
$ composer install

補足:シンタックスエラーのチェック
あと、gitでコミット前のステージングファイルにsyntax Errorがないかもついでにチェックしておきたいので
下記のようなスクリプトも用意しました。

syntaxCheck
#!/usr/bin/env php
<?php

exec('git status -uno --short | grep -E \'^[AUM].*.php$\'| cut -c3-', $changes);

$isError =false;

foreach ($changes as $file) {
    $results = [];
    exec('php -l '.trim($file), $results);
    foreach ($results as $result) {
        if (preg_match('/Fatal error/', $result)) {
            echo $result . PHP_EOL;
            $isError = true;
        }
    }
}

if (!$isError) {
    echo 'Syntax check is OK !!' . PHP_EOL;
}

設定

php-cs-fixer

php5.6 / laravel5.1での設定例

.php_cs
<?php

$finder = Symfony\CS\Finder\DefaultFinder::create()
    ->exclude('bootstrap/cache')
    ->exclude('resources/assets')
    ->exclude('resources/views')
    ->exclude('resources/lang')
    ->exclude('storage')
    ->exclude('vendor')
    ->exclude('public')
    ->exclude('node_modules')
    ->name('*.php')
    ->in(__DIR__)
    ->notName('*.blade.php')
    ->ignoreDotFiles(true);

$fixers = [
  '-psr0',
  'align_equals', // =の位置を揃える
  'align_double_arrow', // => の位置を揃える
  'short_array_syntax', // array() → []
  'no_empty_phpdoc', // 空のphpdoc禁止.補完ではない
  'no_multiline_whitespace_around_double_arrow', // 演算子 => は複数行の空白に囲まれない
  'no_multiline_whitespace_before_semicolons', // セミコロンを閉じる前の複数行の空白は禁止
  'no_unused_imports', // 未使用のuse文は削除
  'ordered_imports', // use文の整列
  'single_quote', // 単純な文字列の二重引用符を一重引用符に変換
  'standardize_not_equals ', // すべての<>を!=
];

//defaultのコードスタイルは、symfony2
return Symfony\CS\Config\Config::create()
    ->fixers($fixers)
    ->finder($finder)
    ->setUsingCache(true);

phing
build.xmlを追加

build.xml
<?xml version="1.0" encoding="UTF-8"?>
<project name="プロジェクト名" default="build">

    <property name="outputDir" value="./storage/phing/" override="false"/>

    <!-- 初期設定 -->
    <target name="prepare">
        <echo msg="コード静的解析開始...." />
        <!--<mkdir dir="./build" />-->
    </target>

    <!-- コーディングスタイルを合わせる(psr-2など) -->
    <target name="php-cs-fixer">
        <exec command="vendor/bin/php-cs-fixer fix -v" logoutput="true" />
    </target>

    <!-- コードの品質を検査 -->
    <target name="phpmd">
        <exec command="vendor/bin/phpmd ./ text codesize,controversial,design,naming,unusedcode (--exclude 除外したいディレクトリ)" logoutput="true" />
    </target>

    <!-- コミット予定ファイルからsyntaxError検出 -->
    <target name="syntaxCheck">
        <exec command="php syntaxCheck" logoutput="true" />
    </target>

    <!-- ユニットテスト実行 -->
    <target name="phpunit">
        <exec command="vendor/bin/phpunit" logoutput="true" />
    </target>

    <target name="build" depends="prepare, php-cs-fixer, phpmd, syntaxCheck, phpunit" /><!-- 実行順序指定 -->
</project>

ディレクトリはこうなります(laravel5.1)

.
├── app
├── artisan
├── bootstrap
├── build.xml    //phing 設定
├── composer.json
├── composer.lock
├── config
├── database
├── .env.example
├── .git
├── .gitattributes
├── .gitignore
├── gulpfile.js
├── package.json
├── .php_cs        //php-cs-fixer
├── .php_cs.cache  //php-cs-fixerのキャッシュ
├── phpspec.yml
├── phpunit.xml
├── public
├── readme.md
├── resources
├── server.php
├── storage
├── syntaxCheck //syntax checkスクリプト
├── tests
└── vendor

実行(laravelプロジェクトで試しに実行)

$ vendor/bin/phing build

Buildfile: /var/www/html/tmp/laravel/build.xml

プロジェクト名 > prepare:

     [echo] コード静的解析開始....

プロジェクト名 > php-cs-fixer:

     [exec] Loaded config from "/var/www/html/tmp/laravel/.php_cs".
     [exec] 
     [exec] Legend: ?-unknown, I-invalid file syntax, file ignored, .-no changes, F-fixed, E-error
     [exec] Fixed all files in 0.261 seconds, 8.750 MB memory used

プロジェクト名 > phpmd:

     [exec] /var/www/html/tmp/laravel/app/Exceptions/Handler.php:30     Avoid variables with short names like $e. Configured minimum length is 3.
     [exec] /var/www/html/tmp/laravel/app/Exceptions/Handler.php:43     Avoid variables with short names like $e. Configured minimum length is 3.

プロジェクト名 > syntaxCheck:

     [exec] Syntax check is OK !!

プロジェクト名 > phpunit:

     [exec] PHPUnit 4.8.31 by Sebastian Bergmann and contributors.
     [exec] 
     [exec] E
     [exec] 
     [exec] Time: 1.68 seconds, Memory: 13.50MB
     [exec] 
     [exec] There was 1 error:
     [exec] 
     [exec] 1) ExampleTest::testBasicExample
     [exec] RuntimeException: No supported encrypter found. The cipher and / or key length are invalid.
     [exec] 
     [exec] /var/www/html/tmp/laravel/bootstrap/cache/compiled.php:7076
     [exec] /var/www/html/tmp/laravel/bootstrap/cache/compiled.php:1289
     [exec] /var/www/html/tmp/laravel/bootstrap/cache/compiled.php:1242
     [exec] /var/www/html/tmp/laravel/bootstrap/cache/compiled.php:1783
     [exec] /var/www/html/tmp/laravel/bootstrap/cache/compiled.php:1334
     [exec] /var/www/html/tmp/laravel/bootstrap/cache/compiled.php:1318
     [exec] /var/www/html/tmp/laravel/bootstrap/cache/compiled.php:1304
     [exec] /var/www/html/tmp/laravel/bootstrap/cache/compiled.php:1242
     [exec] /var/www/html/tmp/laravel/bootstrap/cache/compiled.php:1783
     [exec] /var/www/html/tmp/laravel/bootstrap/cache/compiled.php:2264
     [exec] /var/www/html/tmp/laravel/vendor/laravel/framework/src/Illuminate/Foundation/Testing/CrawlerTrait.php:396
     [exec] /var/www/html/tmp/laravel/vendor/laravel/framework/src/Illuminate/Foundation/Testing/InteractsWithPages.php:61
     [exec] /var/www/html/tmp/laravel/vendor/laravel/framework/src/Illuminate/Foundation/Testing/InteractsWithPages.php:44
     [exec] /var/www/html/tmp/laravel/tests/ExampleTest.php:11
     [exec] 
     [exec] FAILURES!
     [exec] Tests: 1, Assertions: 0, Errors: 1.

プロジェクト名 > build:


BUILD FINISHED

Total time: 7.9815 seconds

とまぁ、こんな感じにフィードバックされるようになりました。

まとめ

php-cs-fixerによるコードの修正がかなり助かります。
修正後のファイルを見ると、意識していたけどpsr-2での書き方できてなかったりと気づきがありました。

phpdocも統一されたフォーマットで書き換えてくれるのでとてもありがたいです。

CircleCIやジェンキンスまでいくとハードル高いかもしれませんがこんな感じで
手軽にコードチェックでも整った開発がしていけそうですね。

今年最後の投稿になりそうです、読んでいただきありがとうございます。
良いお年を。。

下記参考にしました、ありがとうございます。
http://qiita.com/nyanchu/items/d881c34c3112a2ffcdf6

追記

php-cs-fixerは、--dry-runとすることでファイル変換はせずに警告表示だけにすることもできます