gitの「pre-commit」を使って静的テストをする

## 概要
`git`に用意されている**フック機能**を使ってコミット直前に**静的テスト**を行えるようにしてみる。  

キーワードは、
- **pre-commit** : gitのhook機能でコミット直前の処理を定義できる  
- **PHP Lint** : PHP標準のシンタックスチェック機能  
- **phpmd** : PHPの静的コード解析ツール  

の３つになります。  

## 実行環境
- CentOS 7.6
- PHP 7.2.24

## ディレクトリ構成
検証用に作成したディレクトリ構成になります。  
```
app:
├─.gitignore
├─composer.json
├─composer.lock
├─test_success.php
├─test_error.php
│  
├─vendor
│      
└─git_template
    └─hooks
         └─pre-commit
```
主なのだけ説明  
- <u>`test_success.php`、`test_error.php`</u>
検証用のテストコードです。  
- <u>`git_template`</u>  
`pre-commit`を各々の開発環境に適用するために、設定ファイルを格納する。  
- <u>`composer.json`、`vender`</u>  
`phpmd`を使うためのパッケージ設定ファイルです。  

## pre-commitについて
「**pre-commit**」とは、gitの特定のアクションが発生した時にカスタムスクリプトを叩くことが出来るGitフックという機能の一部で、pre-commitを設定することによりコミット直前のイベントを定義することが出来ます。  
他にpush、rebase、merge時のイベントなども定義できます。詳細は[こちら](https://git-scm.com/book/ja/v2/Git-%E3%81%AE%E3%82%AB%E3%82%B9%E3%82%BF%E3%83%9E%E3%82%A4%E3%82%BA-Git-%E3%83%95%E3%83%83%E3%82%AF)。

## 設定関連ファイルを作成
- phpmdを使うので **composer.json**を作成

``` json:composer.json
{
    "require": {
        "phpmd/phpmd" : "@stable"
    }
}
```

- コミット直前にしたい処理を記述した"**git_template/hooks/pre-commit**"を作成  

``` shell:hooks/pre-commit
#!/bin/sh

if git rev-parse --verify HEAD >/dev/null 2>&1
then
    against=HEAD
else
    against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
fi

exec 1>&2

EXIT_CODE=0

error_check() {
  if [ $1 -gt 0 ]; then
    EXIT_CODE=$1
  fi
}

cecho () {
    str=$1
    color=$2
    echo -e "\033[0;${color}m${str}\033[0;39m"
}

cecho '##########################' 32
cecho '# Checking PHP syntax... #' 32
cecho '##########################' 32
echo ''

for CHECK_FILE in `git diff-index --name-status ${against} -- | grep -E '^[AUM].*\.php$' | cut -c3-`; do
  cecho "check... ${CHECK_FILE}" 32
  php -l ${CHECK_FILE}
  error_check $?
done

echo ''

cecho '################' 32
cecho '# Run phpmd... #' 32
cecho '################' 32

for CHECK_FILE in `git diff-index --name-status ${against} -- | grep -E '^[AUM].*\.php$' | cut -c3-`; do
  cecho "check... ${CHECK_FILE}" 32
  vendor/bin/phpmd ${CHECK_FILE} text codesize,design,naming,unusedcode,controversial
  error_check $?
done

if [ ${EXIT_CODE} -gt 0 ]; then
  cecho 'Commit Failed...' 31
else
  cecho 'Pre commit check all green!!' 32
fi

echo ''
exit ${EXIT_CODE}
```
処理の詳細は省きますが、やってることは    
① `git`の差分ファイルを取得  
② phpの`Lint`で検証  
③  `phpmd`で検証。例では「`codesize,design,naming,unusedcode,controversial`」が検証ルールです。  
　 それぞれのルールは以下のような感じです。  ※[抜粋](https://qiita.com/yuji0602/items/28b0c2363bae8fce055a)
> * codesize：循環的複雑度などコードサイズ関連部分を検出するルール
> * controversial：キャメルケースなど議論の余地のある部分を検出するルール
> * design：ソフトの設計関連の問題を検出するルール
> * naming：長すぎたり、短すぎたりする名前を検出するルール
> * unusedcode：使われていないコードを検出するルール

④ ②③で問題が発生していた場合コミットを中止

な流れです。  
…まぁエラソーに書いてますが、ソースは9割がたネットで拾ったやつです。  

## pre-commitはgit管理対象外の場所にいるのでなんとかする
なんとかなってないかもです。  
完全に自動化する方法があれば良いのですが、今回は思いつかなかったので**composer**を利用しました。  
git hooksは`.git/hooks`に予約語名称で保存する必要があるので、「`pre-commit`」という名称のファイルを設置します。  
ってことで**composer.json**を以下の用に変更して、`git_template`配下の`pre-commit`ファイルをコピーできるようにします。
``` json:composer.json
{
    "require": {
        "phpmd/phpmd" : "@stable"
    },
    "scripts": {
        "post-install-cmd": [
            "@setting_git"
        ],
        "setting_git": [
            "cp -rf git_template/hooks .git/ ",
            "chmod a+x .git/hooks/pre-commit"
        ]
    }
}
```
これで`composer install`または`composer setting_git`でhooksファイルを更新出来るようになりました。
※`post-install-cmd`は`composer`が特定の処理で勝手に呼び出してくれるイベントです。他にも [たくさんあります](https://getcomposer.org/doc/articles/scripts.md#command-events])

## 検証環境の準備
ここまでで  
- composer.json
- git_template/hooks/pre-commit

の2ファイルが出来たので、git初期化およびパッケージのインストールをしていきます。
```shell
## git初期化
git init

## パッケージインストール（ここでpre-commitのコピーも行われます。）
composer install
```

## 検証用のコードを作成

- エラー無：**test_success.php**

``` php:test_success.php
<?php
class MdTest
{

    public function testFunction()
    {
        print "md test dayo!!";
    }
}

$hoge = new MdTest();
$hoge->testFunction();
```
- エラー有：**test_error.php**

``` php:test_error.php
<?php
class MdTest
{

    public function test_function($un_used)
    {
        print "md test dayo!!";
    }
}

$hoge = new MdTest();
$hoge->test_function()
```
実行結果は以下な感じです。
```shell
php test_success.php
> md test dayo!!

php test_error.php
> PHP Parse error:  syntax error, unexpected end of file in /home/vagrant/tmp/test/test_error.php on line 12
```
とまぁ当然**test_error.php**の方はエラーになります。

## エラーの状態と正常な状態それぞれでcommitしてみる
まずはエラーの状態で、  
addして、
```shell
git add test_error.php
git add test_success.php
```
commit!
```shell
git commit -m "test dayo"
```
結果
```shell
##########################
# Checking PHP syntax... #
##########################

check... test_error.php
PHP Parse error:  syntax error, unexpected end of file in test_error.php on line 12
Errors parsing test_error.php
check... test_success.php
No syntax errors detected in test_success.php

################
# Run phpmd... #
################
check... test_error.php
/home/vagrant/tmp/test2/test_error.php:5        Avoid unused parameters such as '$un_used'.
/home/vagrant/tmp/test2/test_error.php:5        The method test_function is not named in camelCase.
/home/vagrant/tmp/test2/test_error.php  -       Unexpected end of token stream in file: /home/vagrant/tmp/test2/test_error.php.
Commit Failed...
```
想定通りエラーになり、コミットが取り消されました。  
エラー内容はざっくり
- 【Lint】12行目にシンタックスエラーがあるよ！
- 【phpmd】5行目に$un_usedっていう使ってない変数があるよ！
- 【phpmd】5行目の`test_function`がキャメルケースになってないよ！

なので、修正します。
```php:test_error.php
<?php
class MdTest
{

    public function testFunction($unUsed)
    {
        print "md test dayo!!{$unUsed}\n";
    }
}

$hoge = new MdTest();
$hoge->testFunction(' tukatteruyo!!');
```
これで再度add,commit!!
```shell
git add test_error.php
git commit -m "test dayo"
```
結果
```shell
##########################
# Checking PHP syntax... #
##########################

check... test_error.php
No syntax errors detected in test_error.php
check... test_success.php
No syntax errors detected in test_success.php

################
# Run phpmd... #
################
check... test_error.php
check... test_success.php
Pre commit check all green!!
```
オールグリーン！上手くいきました。

## 参考URL
https://qiita.com/yuji0602/items/28b0c2363bae8fce055a

https://getcomposer.org/doc/articles/scripts.md#command-events