【PHP】gitのpre-commitフックを利用してコードチェックを自動化する

### 前提
#### pre-commitフックで実現する内容
* `php -l`コマンドによるシンタックスチェック
* php-cs-fixerによるコード整形
* phpmdによるコーディグルールのチェック
* phpstanによるシンタックスチェック
参考サイト↓
[[PHP] Git Commit 時にコードの整形、静的解析を実行](https://qiita.com/K-D-H/items/4ebba4504af6b4ec8637)


#### 環境
Docker使用
Laravelプロジェクト

#### ディレクトリ構成
backendにLaravelが格納されています。
その一個上の階層がプロジェクトのルートディレクトリとなっていて、そこに`.git`があるイメージですね。
Dockerを使用していると、こういう感じになっているかと思います。
**Laravelの１つ上の階層がルートディレクトリ**というのがpre-commitを書く上でも重要になってきます。

```bash
$ tree -L 1 -a
.
├── .DS_Store
├── .git // .git/hooks/pre-commitにスクリプトを書く
├── .gitignore
├── .php-cs-fixer.cache
├── .vscode
├── README.md
├── backend //　ここがLaravelプロジェクトが入っているところ
├── docker-compose.yml
├── infra
└── test.txt
```

##### .gitのディレクトリ構成
```
.git
├── COMMIT_EDITMSG
├── HEAD
├── config
├── description
├── hooks
│   ├── applypatch-msg.sample
│   ├── commit-msg.sample
│   ├── fsmonitor-watchman.sample
│   ├── post-update.sample
│   ├── pre-applypatch.sample
│   ├── pre-commit
│   ├── pre-commit.sample
│   ├── pre-merge-commit.sample
│   ├── pre-push.sample
│   ├── pre-rebase.sample
│   ├── pre-receive.sample
│   ├── prepare-commit-msg.sample
│   ├── push-to-checkout.sample
│   └── update.sample
（以下略）
```
hooksの中にはpre-commit以外のsampleも色々入っていますね。
**最初はpre-commitファイルはありません。作成する必要があります**

### pre-commitを書くまでの手順
#### インストールするライブラリ
* phpstan
* phpmd
* php-cs-fixer

#### インストール方法
```
// PHPのコンテナ内のLaravelのプロジェクトのルートディレクトリで以下のコマンドを実行
composer require --dev friendsofphp/php-cs-fixer
composer require --dev phpmd/phpmd
composer require --dev phpstan/phpstan
```
ちなみに`--dev`オプションで開発環境向けのインストールをします。
本番環境で`composer install`を実行する際に`--no-dev`オプションをつけて実行することで、composer.jsonの`require dev`オプションに記載されているライブラリ自体はvendorへインストールされません。

```bash:本番環境でのライブラリインストールのコマンド
composer install --no-dev
```

#### pre-commitの作成
Git管理のルートディレクトリから以下のコマンドを実行し、pre-commitを作成していく。
```bash
vi .git/hooks/pre-commit
```
そして以下の内容をpre-commitに記述して`:wq`で保存する。


```bash
#!/bin/bash
   
echo -e $'\e[1;45m ***** pre-commit hookを実行します ***** \e[m'
   
# プロジェクトディレクトリ
project_dir=backend
   
if git rev-parse --verify HEAD >/dev/null 2>&1
then
    against=HEAD
else
    # Initial commit: diff against an empty tree object
    against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
fi 
   
# Redirect output to stderr.
exec 1>&2
SYNTAX_CHECK=0
   
## 静的解析、及びフォーマッターの実行
# コミットされるファイルのうち、.phpで終わるもの
for FILE in `git diff-index --name-status $against -- | grep -E '^[AUM].*\.php$'| cut -c3-`; do
    # シンタックスのチェック
    echo -e $'\e[36mphp -l シンタックスチェック \e[m'
    if php -l $FILE; then
        # PSR準拠でコード書き換え
        echo -e $'\e[36m\nphp-cs-fixerを実行します \e[m'
        $project_dir/vendor/bin/php-cs-fixer fix $FILE
        git add $FILE
   
        # PHPMDで未使用変数などのチェック
        echo -e $'\e[36m\nphpmdを実行します \e[m'
        if ! $project_dir/vendor/bin/phpmd $FILE text backend/ruleset.xml; then
            SYNTAX_CHECK=1
            echo -e $'\e[31;43m phpmdのチェックエラーを直してください \e[m'
        fi
   
        # phpstanでコード静的解析
        echo -e $'\e[36m\nphpstanを実行します \e[m'
        if ! $project_dir/vendor/bin/phpstan analyse $FILE; then
          SYNTAX_CHECK=1
            echo -e $'\e[31;43mphpstanのチェックエラーを直してください \e[m'
        fi
    else
        SYNTAX_CHECK=1
    fi
done
   
   
## 最後の確認
if [ $SYNTAX_CHECK -eq 0 ]; then
    exit 0
else
    echo -e $'\e[31;43m 修正を行った上で再度コミットしてください \e[m'
    exit 1
fi 
```

phpmdチェックで引っ掛かるように、以下のコードを
* backend/app/Http/Controllers/Controller.php
* backend/app/Models/User.php
に追記しています。

```php:phpmdチェックに引っ掛かるコード
// メソッド名がキャメルケースになっていないコード
    public function test_test()
    {
    }
```

#### 差分比較についての補足
```bash
for FILE in `git diff-index --name-status $against -- | grep -E '^[AUM].*\.php$'| cut -c3-`; do
```
`[AUM]`という部分について、そういう正規表現があるのかな？って思ったんですけど、そういうわけではなくて、
`git diff-index --name-status HEAD`コマンドをを実行した時に表示される、ファイル名のリストの先頭に
```bash
$ git diff-index --name-status HEAD
M       backend/app/Http/Controllers/Controller.php
M       backend/app/Models/User.php
```
先頭にA or U or Mが表示されます。それぞれの意味は
* A 追加
* M 更新
* U 競合

ということらしい。
参考にしたサイト
http://lost-waldo.blogspot.com/



#### 注意点
いろいろ参考になる記事が世の中にありますが、ポイントとしては
`Git管理のルートディレクトリがどこか`
を意識しないと、pre-commit内で実行するコマンドのパスが変わってくるので重要なんですね。

今回はDockerを使用していて、Laravelプロジェクトの１つ上の階層がルートになり、そのディレクトリに`.git`ディレクトリが存在しています。

php-cs-fixerなどのライブラリを使用しているので、
`backend/`vendor/bin/phpmdと書く必要があるということです。
（すぐ気づかないといけませんが。。。）

#### 表示例
ちょこちょこ色付けしてみました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/668402/d8b10740-8f2e-6224-06bd-3f016f001ff8.png)

なんかいまいち見やすくならなかったかなぁ・・・


### 最後に
php-cs-fixerのコマンド実行のパス指定の注意点があったので、他につまづいている方がいましたら参考になればと思います。

また、他にもpre-commitフックに追加した方がいいチェックや
間違いなどがありましたらご指摘いただければと思います。