Phing入門 ～基本的な使い方～

Phingのインストールガイドや簡単な紹介記事は見かけますが、日本語のドキュメントはまだ少ないです。
そこで、よく使いそうな設定やタスクをメインに紹介しようと思います。
なお、Phingのインストール方法についてはよく紹介されているので割愛します。

# Phingとは

![phing.gif](https://qiita-image-store.s3.amazonaws.com/0/27159/a7af024a-bccd-6d12-f5ca-d026135f08a0.gif)

Phingとは、PHP向けのビルドツールです。
Phingを使うことによってテストや解析などを一元管理し、コマンド一発で実行できるようになります。

まだイメージがつかないでしょうか。
例えば、あるプログラムファイルを修正するとします。修正後、関係するテストを実行、ついでにPHPMDも実行・・・などをしようとすると

```bash
$ phpunit tests/foo/bar.php
$ phpunit tests/foo/bar/baz.php
$ phpmd foo.php xml ruleset.xml
$ phpcpd --min-lines 5 --min-tokens 5 foo/bar.php
...
```

などとたくさんのコマンドを実行する必要があります。
ところが、Phingを導入していると以下のコマンド一発で上記の全てをやってくれるのです。

```bash
$ phing build
```

とても簡単になりましたね。
また、設定ファイルをバージョン管理することによってみんなで共有できるので、人によってテスト内容に差異が出ません。


# Phingの実行方法

## build.xmlの生成

Phingで実行したい内容はbuild.xmlというビルドファイルに記述します。
難しいものではないので、例を見たほうが早いでしょう。

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

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

    <!-- 初期設定 -->
    <target name="prepare">
        <echo msg="初期設定" />
        <mkdir dir="./build" />
    </target>

    <target name="phpmd">
        <phpmd rulesets="ruleset.xml">
            <!-- 対象ディレクトリをmain下に設定 -->
            <fileset dir="/main">
                <!-- phpファイルのみを対象にする -->
                <include name="**/*.php" />
                <!-- vendorsディレクトリ下を対象外にする -->
                <exclude name="vendors/" />
            </fileset>
            <!-- 結果をXMLでphpmd.xmlとして出力 -->
            <formatter type="xml" outfile="${outputDir}/phpmd.xml" />
        </phpmd>
    </target>

    <target name="phpunit">
        <phpunit codecoverage="false" bootstrap="bootstrap.php">
            <batchtest>
                <fileset dir="/main" >
                    <include name="**/*Test.php" />
                    <exclude name="vendors/" />
                </fileset>
            </batchtest>
            <formatter type="xml" outfile="${outputDir}/phpunit.xml"/>
        </phpunit>
    </target>

    <target name="build" depends="prepare, phpmd, phpunit" />

</project>
```

なるほど。phpmdとphpunitが実行される指定です。さらに、初期設定も行っているようですね。

## Phingの実行

build.xmlを記述したら、あとはPhingコマンドを実行するだけです。
実行コマンドは以下。

```bash
phing <オプション> -buildfile <build.xmlのパス> <ターゲット>
```

```bash:実行例
phing -DoutputDir=./result/ -buildfile ./test/build.xml phpmd
```

`<build.xmlのパス>`は指定しない場合、デフォルトで`build.xml`を見に行きます。
`<ターゲット>`はどのタスクを実行するかを指定できます。デフォルトで`build`という名前のターゲットを実行します。詳しくは後ほど説明しましょう。

### オプション

ついでにPhingコマンドのオプションを紹介しておきましょう。
よく使うオプションは以下。

* `-l -list` ビルドファイルで実行されるターゲットの一覧が見られます。
* `-D<property>=<value>` プロパティを指定できます。ここで指定したプロパティはbuild.xml内で使用できます。
* `-buildfile` ビルドファイルを指定します。デフォルトは`build.xml`です。

その他のオプション。

* `-h -help` ヘルプが見られます。
* `-v -version` Phingのバージョンが確認できます。
* `-propertyfile <file>` プロパティをファイルからロードします。
* `-debug` デバッグ出力できます。
* `-q -quiet` 指定するとビルド実行時に何も出力しません。
* `-verbose` ビルド実行の詳細な内容を出力します。
* `-logfile <file>` ビルド実行のログ出力先を指定します。
* `-logger path.to.Logger` ロガーを指定します。デフォルトはphing.listener.AnsiColorLoggerです。
 * その他のロガーは phing.listener.NoBannerLogger, phing.listener.DefaultLogger, phing.listener.XmlLogger, phing.listener.TargetLogger, phing.listener.HtmlColorLogger があります。


# メインのタグとその属性の説明

## project

projectは1つのbuild.xmlに必ず1つでなければなりません。
projectタグの中に実行したいタスクを記述していきます。

また、`default`でPhing実行時にターゲットが指定されていなかった場合に実行するタスクを指定できます。

```xml
<project name="mainProject" default="main" >
```

上記のように書くと、何も指定せずPhingを実行した場合はmainと名前のつけられたタスクが実行されます。



## target
targetタグの中には、実行したいタスクをまとめて書いておくことが出来ます。

targetには`name`を指定します。こうすることによって、Phingを実行する際に特定のタスクだけ実行したり、実行する順番を指定できたりします。
これは最初のbuild.xmlの例を見るとイメージしやすいでしょう。

`depends`を指定すると他のtargetに依存させることが出来ます（カンマ区切りで複数指定可能）。便利ですが、この指定の仕方が勘違いしやすいところなので注意しましょう。
例えば、以下の例を見てみます。

```xml
<target name="a" />
<target name="b" depends="a" />
```

これを見ると b が実行された後に a が実行されるかと思うのでしょうが、逆です。b は a に"依存"しているので、a が実行された後に b が実行されるのです。複数指定になるともっと複雑になるので注意しましょう。

## property

プロパティ（変数的なもの）を設定できます。例えば、以下をご覧ください。

```xml
<property name="outputDir" value="./result/" override="false"/>
```

outputDirというプロパティに「./result」を代入しています。
そうするとのちの記述で

```xml
<formatter type="xml" outfile="${outputDir}/phpunit.xml"/>
```

のようにして使用することが出来るのです。
結果ファイルの出力先を固定したり、設定値を持つのに便利ですね。
また、Phingは実行時にパラメータを受け取れるので、そのパラメータの初期値として設定しておくのも良いかもしれません。

```bash
phing build -DoutputDir=./result
```

ちなみに、`override="true"`を指定しておくと、外から何を指定しようがbuild.xmlの記述でオーバーライドされます。逆に`override="true"`を指定すると外からパラメータを指定した場合はそれが使用されます。


# 実行対象ファイルの絞り込みに使うタグ

タスクの実行対象のファイルを絞り込む時に使うタグを紹介します。

## fileset

対象のファイル・ディレクトリを指定することが出来ます。
`dir`でディレクトリを指定でき、`include`で対象ファイルを指定、`exclude`で非対象ファイルを指定できます。

ファイル指定は`*`(アスタリスク)が使用できます。また、`**`で全てのディレクトリを対象に出来ます。

```xml
<!-- 対象ディレクトリをmain下に設定 -->
<fileset dir="/main">
    <!-- 全てのphpファイルのみを対象にする -->
    <include name="**/*.php" />
    <!-- vendorsディレクトリ下を対象外にする -->
    <exclude name="vendors/" />
</fileset>
```

## filelist

対象ファイルのリスト形式で指定できます。
`dir`で対象のディレクトリを指定し、`files`や`filelist`で実際にファイルを指定します。

`files`では、単なるファイルのリストを羅列して指定できます。

```xml
<filelist dir="/conf" files="foo.conf,bar.conf,baz.conf"/>
```

ファイルの羅列をbuild.xmlに書くのが嫌な場合は、別ファイルにして読み込ませることも出来ます。
その場合は`listfile`にそのリストを羅列したファイルを以下のように指定します。

```xml
<filelist dir="conf/" listfile="conf/file_list.txt"/>
```

# オプションタスク

さて、続いてはオプションタスクです。
"オプション"と言いますが、実際メインで使いたいのはこれでしょう。phpunit、phpmd、phpcpd等、テスト系のタスクが実行できます。

書くのが疲れてきたので代表的なもののリンクを貼っておきます。
*あとでまた書きます*

## phpunit

http://www.phing.info/docs/guide/trunk/apcs59.html

## phpmd

http://www.phing.info/docs/guide/trunk/apcs53.html

## phpcpd

http://www.phing.info/docs/guide/trunk/apcs51.html

## phplint

http://www.phing.info/docs/guide/trunk/apcs58.html


# その他、よく使う基本的なタスク

ちょっと凝ったビルド設定を書かなければいけない時は以下のタスクが役に立ちそうです。

## echo

メッセージを出力します。ビルドの開始メッセージや、デバッグなどに使えますね。
シンプルな書き方は2パターン。

```xml
<echo message="ビルド開始" />

<echo>ビルド終了</echo>
```


## if
if文が使えます。
環境やフラグによって条件分岐できるのは便利ですね。`<elseif>`も使えます。

```xml
<if>
    <istrue value="${success}" />
    <then>
        <echo message="build success" />
    </then>
    <else>
        <echo message="build failur" />
    </else>
</if>
```

条件指定は例えば以下のものがあります。

* `<equals arg1="${foo}" arg2="test" />`
* `<istrue value="${flag}" />`
* `<isfalse value="${flag}" />`
* `<isset value="${dir}" />`

## condition

ifタスクでは1つの条件しか判定できませんが、複雑な条件で何かを判定したい場合にはconditionタスクを使用します。

```xml
<condition property="isLocal">
    <equals arg1="${env}" arg2="local" />
    <socket server="localhost" port="80" />
</condition>
```

こうすると、isLocalというプロパティに結果が格納されます。
条件にどんな指定が出来るかはドキュメントをご覧ください。
http://www.phing.info/docs/guide/trunk/ch05s08.html


## delete

ファイルの削除が出来ます。
テストによって作られたテンポラリファイルを削除するのに使ったりとかしますね。
`quiet`をtrueにすると、タスクが失敗してもエラーと判定しません。
`verbose`をtrueにしておくと、タスクが失敗した場合に詳細なエラーメッセージを出してくれます。

```xml
<delete file="/tmp/foo.bar" />
```

ディレクトリごと削除したい場合はこう。

```xml
<delete dir="/tmp/darl" includeemptydirs="true" verbose="true" failonerror="true" />
```

## copy

ファイルのコピーが出来ます。
`file`でコピー元を指定、`tofile`でコピー先を指定します。また、`overwrite="true"`でファイルを上書きします。

```xml
<copy file="somefile.txt" tofile="/tmp/anotherfile.bak" overwrite="true"/>
```

あるディレクトリにいろんなファイルをコピーしたいときは以下のように指定します。

```xml
<!-- /backupディレクトリにconfとiniファイルをコピーする -->
<copy todir="/backup">
    <fileset dir=".">
        <include name="**/*.conf" />
        <include name="**/*.ini" />
    </fileset>
</copy>
```


## chmod

chmodがかけられます。
deleteタスクと同様に`quiet`と`verbose`が指定できます。

```xml
<chmod file="/home/test/mine.txt" mode="0500" verbose="true" />
```

## loadfile

ファイルを読み込んでプロパティにセットすることが出来ます。
環境変数や設定値を切り出しておいて読み込むとか。

```xml
<loadfile property="version" file="version.txt"/>
```

## php

最後の力技手段。phpを直接実行できます。

```xml
<php expression="echo 'test';">
```


# その他

## composerインストールしたパッケージが使える
phingで実行するphpunitやphpmdは通常pearでインストールされたパッケージが使われますが、composerインストールしたphpunitやphpmdを使うことも出来ます。
その場合は特にbuild.xmlに記述を増やす必要はなく、同じくcomposerインストールしたPhingを指定して実行したらautoload.phpが呼ばれて自動的に解決してくれます。

```bash:実行例
./vendors/bin/phing -DoutputDir=./result/ -buildfile ./test/build.xml phpmd
```

## Phingが定義した変数が使える
実行ホストのOSバージョンを取得したり、環境変数を取得できたりします。
環境によって切り替えたいものがある場合は重宝しそうです。
http://www.phing.info/docs/guide/stable/apa.html

## PhingのJenkinsプラグインがある
PhingはJenkinsプラグインがあるので、連携するとかなり強力になります。
GitにマージされたらJenkins経由でPhingを実行し、結果を画面に出力・・・なんてことも簡単にできます。