PHP CodeSniffer(phpcs) は、コードを一定の規約に沿って管理してくれるものでWEB開発においてとても重要な役割を担っています。
例えば一つのサービスを複数人で開発するとしても、同じコードの書き方を良い意味で強要されるので、PSRなどの規約に沿った品質の高いコードを担保できます。
■ pre-commit が必要な背景
コードを変更し、コミット前に手動で composer cs-check
とすれば、同じ効果が得られるので別に自動チェックせずとも問題はないのですが、繰り返す作業は煩雑になりがちですし、あまり頻繁に出入りしないメンバーがコード修正などを行った場合とか、新規メンバーが参入した場合など、cs-check
自体をしないままコミットをしてしまう、という問題点が出てきます。
そこで、Gitのフック機能を利用して、コミット時に cs-check
を自動実行するように設定しておけば、サービスの背景を知らない人が、コードの変更を行ったとしても、コード規約に沿っていなければ、コミット自体を弾くことができます。
結果的にチェック漏れは発生しないですし、保守性が高まります。
以下、その方法です。
■ pre-commit導入前提条件
- composer などでphpcsはすでに導入されていること
- 規約条件は phpcs.xml で設定されていること
- composer cs-check で実行できる
- Cakephp4での導入なので、laravelなどは別途パスなどを変更する必要ありかも
■ .git-hooks.sampleディレクトリ作成
.git-hooks.sample/pre-commit
のサンプルディレクトリを作成しました。別に、直接 .git/hooks
ディレクトリにぶち込んでも良いかとは思います。
このあたりは管理に対する自身のポリシーで良さそうです。
■ pre-commit内容
#!/bin/bash # PHP CodeSniffer pre-commit hook for git # https://github.com/ekino/git-hooks/blob/master/php/phpcs/pre-commit # バックサイトでもフロントでも名前をご自由に BACK_SITE="{YOUR URL}" TMP_STAGING="${BACK_SITE}/.pre-commit" # stolen from template file if git rev-parse --verify HEAD > /dev/null then against=HEAD else # Initial commit: diff against an empty tree object against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 fi # this is the magic: # retrieve all files in staging area that are added, modified or renamed # but no deletions etc FILES=$(git diff-index --name-only --cached --diff-filter=ACMR $against -- ) if [ "$FILES" == "" ]; then exit 0 fi # create temporary copy of staging area if [ -e $TMP_STAGING ]; then rm -rf $TMP_STAGING fi mkdir $TMP_STAGING # match files against whitelist FILES_TO_CHECK="" PHPCS_FILE_PATTERN="\.php$" for FILE in $FILES do echo "$FILE" | egrep "$PHPCS_FILE_PATTERN" | egrep -q "$BACK_SITE" RETVAL=$? if [ "$RETVAL" -eq "0" ] then FILES_TO_CHECK="$FILES_TO_CHECK ${FILE#*/}" fi done if [ "$FILES_TO_CHECK" == "" ]; then exit 0 fi # Copy contents of staged version of files to temporary staging area # because we only want the staged version that will be commited and not # the version in the working directory STAGED_FILES="" for FILE in $FILES_TO_CHECK do ID=$(git diff-index --cached $against $BACK_SITE/$FILE | cut -d " " -f4) # create staged version of file in temporary staging area with the same # path as the original file so that the phpcs ignore filters can be applied mkdir -p "$TMP_STAGING/$(dirname $FILE)" git cat-file blob $ID > "$TMP_STAGING/$FILE" STAGED_FILES="$STAGED_FILES $FILE" done OUTPUT=$(cd $BACK_SITE && vendor/bin/phpcs --standard=phpcs.xml --extensions=php $STAGED_FILES) RETVAL=$? # delete temporary copy of staging area rm -rf $TMP_STAGING if [ $RETVAL -ne 0 ]; then echo -e "$OUTPUT" echo -e "\e[31m[pre-commit] phpcs error\e[m" fi exit $RETVAL
ファイル名や拡張子の対応は別途環境により調整する必要があります。
against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
という部分は empty tree
を示す特別なハッシュ値らしく、空の状態から最初のコミットを含む全ての差分を取得したい場合は決まり文句的にこれを使えばよい、ということらしいです。
参考URL: https://stackoverflow.com/questions/40883798/how-to-get-git-diff-of-the-first-commit
■ 作成したpre-commitを.git/hooks以下にぶち込む
作成した pre-commit ファイルを .git/hooks
以下にぶち込みます。この時、ファイルは実行可能であることを確認してください。
権限変更が必要な場合、 chmod 755 pre-commit
で大丈夫かと。
■ 実際にコミットをして弾かれるか確認
コミットしようとして、弾かれたところ。「Gitログを開く」で確認します。
「出力」タグで以下のように cs-check エラーが表示されていた
command
----------------------------------------------------------------------
FOUND 2 ERRORS AFFECTING 1 LINE
----------------------------------------------------------------------
46 | ERROR | [x] Expected 1 space(s) after IF keyword; 0 found
46 | ERROR | [x] Expected 1 space(s) after closing parenthesis;
| | found 0
----------------------------------------------------------------------
PHPCBF CAN FIX THE 2 MARKED SNIFF VIOLATIONS AUTOMATICALLY
----------------------------------------------------------------------
これで、指摘箇所を修正して、コミットが成功すれば、Gitのフック機能を利用して、コミット時に自動 cs-check が走る形になります。