Wiz テックブログ

Wizは、最新のIoTやICTサービスをお客様に届ける「ITの総合商社」です。

php-cs-fixerをつかったコードフォーマットの自動化(git hook)

こんにちは、バックエンドエンジニアの三井です。

社内でコードフォーマットを統一する活動を行っており、phpのパッケージを使ったフォーマッターの導入と、git hookを使ったフォーマットの自動化に成功したので概要をまとめていきたいと思います!

導入したphpパッケージ


今回導入したパッケージはphp-cs-fixerです。

導入環境

laravel: 8.60.0
composer: 2.1.6
php: 8.0.9

概要

composer で導入できるphpのパッケージです。導入後コマンドを打つことで任意のファイルにフォーマッターをかけることができます。
フォーマットルールについて、PSR12をはじめ多くのフォーマットに対応しており、設定ファイルを作成することでオリジナルのフォーマットルールを生成することができます。
導入時はPSR12のみを導入して運用してましたが、今後社内でフォーマットを統一するためにルールの取り決めも行っていきたいと思っております!

導入方法・設定


インストール

今回は私の動作環境であるlaravelプロジェクト内での使用を想定して行います。

# パッケージのインストール
composer require friendsofphp/php-cs-fixer
# 設定ファイルをプロジェクトディレクトリに追加
touch .php-cs-fixer.dist.php
# gitignoreにキャッシュファイルを追加
.php-cs-fixer.cache

設定ファイル

設定ファイル.php-cs-fixer.dist.php内を以下のように記述します。 ルールはここで確認することができ、PSR12等複数のルールをまとめたルールセットを指定することも可能です。

<?php
/*
 * This document has been generated with
 * https://mlocati.github.io/php-cs-fixer-configurator/#version:3.1.0|configurator
 * you can change this configuration by importing this file.
 */
$config = new PhpCsFixer\Config();
return $config
        ->setRiskyAllowed(true)
    ->setRules([
        '@PSR12' => true, // ここにルール追記
    ])
    ->setFinder(PhpCsFixer\Finder::create()
        ->exclude([ // 除外ファイル
              'vendor'
        ]) 
        ->in(__DIR__)
    )
;

setRiskyAllowed() : riskyなルール(コードを書き換える破壊的な修正を含むルール)を有効にするかを設定します。
setRules() : 使用したいルールをここで列挙します。
setFinder()...->exclude(['']) : 除外したいディレクトリ名を列挙することでそのディレクトリ内のファイルは無視されます。

ルール設定方法

ルール生成用のツールで生成ファイルを作ることができます。

https://mlocati.github.io/php-cs-fixer-configurator/#version:3.1

f:id:mtimti:20210915192833p:plain

このページでは使用可能なルールが列挙されており、ページ内の歯車マークをクリックすると使用するルールの選別ができるようになります。

f:id:mtimti:20210915193249p:plain

またその状態で+ボタンを押すことでルールセットの指定も可能です。 最後にExportボタンを押すことで設定ファイルの出力ができます。

f:id:mtimti:20210915193037p:plain

f:id:mtimti:20210915193201p:plain

実行コマンド

# version バージョン確認
$ ./vendor/bin/php-cs-fixer --version

# dry-run (修正はされないが、--diffオプションで修正前後の確認ができる)
$ ./vendor/bin/php-cs-fixer fix -v --diff --dry-run

# fix 実際に修正実行
$ ./vendor/bin/php-cs-fixer fix -v --diff
--dry-run # 変更なし実行
--diff # 差分出力
--allow-risky # riskyルールの許可

git hookの概要


詳しい説明は割愛しますが簡単に説明すると、gitのコミット等のコマンドの直前・後にスクリプトを実行できるという仕組みです。 今回は"コミット直前にフォーマッターを実行し、修正後コミットする"という機構を作ります。 隠しディレクトリ.gitの配下にhooksディレクトリを作りpre-commitファイルを作成します。

#!/usr/bin/env bash

#ルートの設定(laravel projectの配下を指定)
ROOT=$(git rev-parse --show-toplevel)/projdir 

PHP_CS_FIXER="./vendor/bin/php-cs-fixer"
HAS_PHP_CS_FIXER=false

if [ -x $ROOT/vendor/bin/php-cs-fixer ]; then
    # パッケージの存在チェック
    HAS_PHP_CS_FIXER=true 
fi

LIST=$(git status | grep -e '\(modified:\|new file:\)'| grep '\.php' | cut -d':' -f2)
if $HAS_PHP_CS_FIXER; then
    ERRCNT=0
    for file in $LIST
    do 
  # --dry-runオプションで先にエラーチェック
        ./projdir/vendor/bin/php-cs-fixer fix $file --diff --dry-run --path-mode=intersection --config=$ROOT/.php-cs-fixer.dist.php >/dev/null 2>&1; 
        ERR=$?
  # ステータスコードが0(修正なし)か8(修正あり)以外はエラーに該当するのでエラーメッセージを表示
        if [ $ERR != 0 -a $ERR != 8 ]; then
            ERRCNT=$((ERRCNT+1))
            echo "php-cs-fixer-$ERR エラーが検出されました $file"
            echo "error $ERRCNT"
        else
   # 修正実行
            ./projdir/vendor/bin/php-cs-fixer fix $file --path-mode=intersection --config=$ROOT/.php-cs-fixer.dist.php >/dev/null 2>&1;
            git add "$file";
        fi
    done
        if [ $ERRCNT -gt 0 ]; then
            echo "php-cs-fixer $ERRCNT 件エラーが検出されました"
            exit 1
        fi
            echo "php-cs-fixer フォーマット修正が完了しました"
else
    echo ""
    echo "Please install php-cs-fixer, e.g.:"
    echo ""
    echo "composer require --dev fabpot/php-cs-fixer:dev-master"
    echo ""
    exit 1
fi

これでcommit時にこのスクリプトが自動実行され、フォーマッターが動きます。

参考にさせていただいたソース

実際に開発で運用してみた感想


今回フォーマッターの統一に至った経緯としては社内で使用エディタが統一されていなかったため、各々のフォーマットでコーディングされていたという状況を受けてのことでした。
実際に使ってみて思ったこととしては、エディタ内のフォーマッターと合わせて運用すると良いかもしれない、ということです。コミット直前に修正を行うデメリットとして、コードを書いている途中で整ったコードを見れないという点です。commit内容としては整っているコードですが、"実装中にコードを読みやすくする"にすることが目的なら本末転倒だと思います。そのため、"自分がわかりやすいように"エディタのフォーマッターも兼ねて運用するのも有効だと思いました。