PHPライブラリ開発のためのgulpfileとGruntfile 比較

ファイル変更を監視して、コマンドを実行してくれる最近のフロントエンドビルドツール。Gruntとgulpが有名どころっぽいんですが、PHPの開発にも併用して使うと楽しいです。

やりたいこととしては、以下の工程を自動化したいわけです。ローカル環境でもっと高頻度にCIを回したいという感じ。

1. ブラウザとエディタとターミナルを準備。
  - ブラウザには http://localhost:9000/ などでカバレッジレポートを表示。
  - ターミナルでは常時PHPUnitの結果が表示される
  - エディタはもちろんソースコードとユニットテストコードを書く(何を使ってもよい)
2. ソースコード・テストコードをエディタで書いて、保存する
3. ファイル変更を検知して、自動でphpunitコマンドを実行
4. PHPUnitのカバレッジレポート(HTML版)をlivereloadし、テストの足りなさそうなところを通知

昔はエディタを選んだり、環境を選んだりして結構苦労して実現していたらしいですが、今はツールをインストールしてちょちょっと設定するだけです。いい時代になったもんだ。

以下、PHPUnitやapigenなどの設定ファイルは省略しているので、こちらも見てください。
https://github.com/spindle/spindle-lib-template


# Grunt

まずはGruntで実現したい場合。Gruntはこの手のツールのパイオニア的存在らしいですが、設定ファイルが煩雑になりがちで、後発のgulpの人気に押され始めているとか。
上記を実現する程度であれば、Gruntでもgulpでもさして手間は変わりません。
[PHP 開発でも Grunt を使う - ngの日記](http://ngyuki.hatenablog.com/entry/2013/12/03/231619)
こちらをだいたいパクりました。

### Gruntのインストール

他にも解説している記事が一杯あるので、適当なところで。node.jsとnpmはインストール済みであるとします。

```bash
$ npm install -g grunt-cli

$ cd path/to/php-lib-repository

$ echo '{}' > package.json
$ npm install --save-dev grunt
$ npm install --save-dev grunt-shell
$ npm install --save-dev load-grunt-tasks
$ npm install --save-dev grunt-contrib-watch
$ npm install --save-dev grunt-contrib-connect
```

これでpath/to/php-lib-repositoryの中ではgruntコマンドが使えるようになります。 gruntの設定ファイルを書きます。

### Gruntfile.coffee

```coffeescript:Gruntfile.coffee
#
# Gruntfile.coffee for php-library development
#
# 1. install node.js & npm
# 2. $ npm install -g grunt-cli
# 2. $ npm install
# 3. $ grunt
# 4. open http://localhost:9000/ (livereload enabled)
# 5. coding on src/* and tests/*
#
# enjoy!
# @license https://creativecommons.org/publicdomain/zero/1.0/ CC0-1.0 (No Rights Reserved.)

module.exports = (grunt) ->
    grunt.initConfig
        shell:
            phpunit:
                command: 'vendor/bin/phpunit'
                options: {stdout: true, stderr: true}

            apigen:
                command: 'vendor/bin/apigen.php'
                options: {stdout: true, stderr: true}

            pdepend:
                command: [
                    'vendor/bin/pdepend'
                    '--jdepend-chart=builds/pdepend.svg'
                    '--overview-pyramid=builds/pyramid.svg'
                    '--summary-xml=builds/summary.xml'
                    'src/'
                ].join ' '
                options: {stdout: true, stderr: true}

        connect:
            server:
                options:
                    port: 9000
                    base: 'builds'
                    livereload: true

        watch:
            php:
                tasks: 'shell:phpunit'
                files: [
                    'src/**/*.php'
                    'tests/**/*.php'
                ]
                options:
                    livereload: true

    require('load-grunt-tasks')(grunt)

    grunt.registerTask 'default', ['connect', 'watch']
    grunt.registerTask 'test', ['shell:phpunit']
    grunt.registerTask 'all', ['shell']
```

あとは`grunt`と打ち込むと、ファイルの監視とlivereloadが有効になったWebサーバーが立ち上がります。
http://localhost:9000/ をブラウザで開いておき、ソースを書いていきます。PHPUnitの最近のカバレッジレポートはかなり綺麗なので、テンション上げながら作業ができます。

# gulp

ぶっちゃけPHP関連のコマンドを実行する程度であれば、Gruntからgulpに乗り換えるメリットはあまりないかもしれません。設定ファイルの行数もあまり変わりませんし。

### gulpのインストール

こちらもnode.jsとnpmはインストール済みであるとします。

```bash
$ npm install -g gulp

$ cd path/to/php-lib-repository

$ echo '{}' > package.json
$ npm install --save-dev gulp
$ npm install --save-dev gulp-connect
```

### gulpfile.js

`gulp test`でPHPUnit実行、`gulp inspect`でapigenとPHP_Depend実行、`gulp server`でファイル監視とサーバー立ち上げを開始です。

```js:gulpfile.js
/**
 * gulpfile.js for php-library developement
 *
 * 1. install node.js & npm
 * 2. $ npm install
 * 3. $ gulp server
 * 4. open http://localhost:9000/ (livereload enabled)
 * 5. coding on src/*.php and tests/*.php
 *
 * enjoy!
 *
 * @license https://creativecommons.org/publicdomain/zero/1.0/ CC0-1.0 (No Rights Reserved.)
 */
var gulp = require('gulp');
var exec = require('child_process').exec;
var connect = require('gulp-connect');

gulp.task('default', ['test', 'inspect']);

gulp.task('help', function(){
    console.log('gulp test\t... kick vendor/bin/phpunit command');
    console.log('gulp inspect\t... kick vendor/bin/apigen and vendor/bin/pdepend');
    console.log('gulp server\t... start static web server on http://localhost:9000/');
    console.log('\tcoverage report... http://localhost:9000/coverage/');
    console.log('\tApiGen document... http://localhost:9000/api/');
});

gulp.task('test', function(done){
    exec('vendor/bin/phpunit', function(err, stdout, stderr){
        console.log(stdout);
        console.error(stderr);
        done();
    });
});

gulp.task('inspect', function(done){
    var i = 0, count = function(){ if (++i > 1) done() };
    exec([
        'vendor/bin/pdepend',
        '--jdepend-chart=builds/pdepend.svg',
        '--overview-pyramid=builds/pyramid.svg',
        '--summary-xml=builds/summary.xml',
        'src/'].join(' '), count);
    exec('vendor/bin/apigen.php', count);
});

gulp.task('connect', ['default'], function(){
    connect.server({
        root: [__dirname + '/builds/'],
        port: 9000,
        livereload: true
    });
});

gulp.task('reload', ['test'], function(){
    return gulp.src('builds/coverage/**')
        .pipe(connect.reload());
});

gulp.task('server', ['connect'], function(){
    gulp.watch(['src/**', 'tests/**'], ['reload']);
});

```

カバレッジレポートの出力先であったり、watch対象のディレクトリは適宜実情に合わせて調整してください。