pre-commitでphp-cs-fixerを実行してみました

# はじめに

- 個人開発に整形ツールを導入するにあたり以下の点を意識いたしました
    - pre-commitの練習
    - 例えば既存のプロジェクトなど導入する場合などを想定して編集したファイルのみを整形対象とする

- pre-commitとは
    - gitコマンドでコミットする前に、文法や体裁をチェックしてくれるもの
- php-cs-fixerとは
    - コーディング規約に沿ってPHPコードを美しく整形してくれるオープンソースソフトウェア

## 環境
- Laravel 11.14.0
- Sail v8.3
- Vim 9.0

# 導入手順
### 1. php-cs-fixerを以下のコマンドで導入
```bash
sail composer require --dev friendsofphp/php-cs-fixer
```

### 2. プロジェクトルートにphp-cs-fixerの整形設定ファイルである".php-cs-fixer.dist.php"を作成
- こちらはひとまず作成いたしましたphp-cs-fixerの設定ファイルです
- 個人開発を進めていく中で微調整を行う予定でございますで参考までに
```.php-cs-fixer.dist.php
<?php

declare(strict_types=1);

$finder = PhpCsFixer\Finder::create()
    ->in([
        __DIR__ . '/app',
        __DIR__ . '/config',
        __DIR__ . '/database/seeders',
        __DIR__ . '/routes',
        __DIR__ . '/tests',
    ]);

$config = new PhpCsFixer\Config();

return $config
    ->setRiskyAllowed(true)
    ->setRules([
        '@PSR12' => true,

        'array_indentation' => true,
        'array_syntax' => ['syntax' => 'short'],
        'binary_operator_spaces' => ['operators' => ['=' => 'align_single_space_minimal', '=>' => 'align_single_space_minimal']],
        'blank_line_after_opening_tag' => true,
        'blank_line_before_statement' => ['statements' => ['return']],
        'braces' => ['allow_single_line_closure' => true],
        'cast_spaces' => ['space' => 'single'],
        'concat_space' => ['spacing' => 'one'],
        'declare_equal_normalize' => ['space' => 'single'],
        'function_typehint_space' => true,
        'global_namespace_import' => ['import_constants' => true, 'import_functions' => true, 'import_classes' => true],
        'method_chaining_indentation' => true,
        'native_function_type_declaration_casing' => true,
        'no_blank_lines_after_phpdoc' => true,
        'no_empty_statement' => true,
        'no_singleline_whitespace_before_semicolons' => true,
        'no_trailing_comma_in_singleline_array' => true,
        'no_unused_imports' => true,
        'no_whitespace_before_comma_in_array' => true,
        'no_whitespace_in_blank_line' => true,
        'ordered_imports' => ['sort_algorithm' => 'alpha'],
        'phpdoc_indent' => true,
        'ternary_to_null_coalescing' => true,
        'trailing_comma_in_multiline' => ['elements' => ['arrays', 'arguments', 'parameters']],
        'trim_array_spaces' => true,
        'unary_operator_spaces' => true,
    ])
    ->setFinder($finder);
```

### 3. ".gitignore"ファイルにphp-cs-fixerのキャッシュファイルを追記しバージョン管理から除外
- キャッシュファイルは開発環境ごとに異なるためです
```.gitignore
.php-cs-fixer.cache
```

### 4. CLI(Vim)にてpre-commitを編集
- 存在しませんでしたら新規作成してください
```bash
vim .git/hooks/pre-commit
```
```.git/hooks/pre-commit
#!/bin/sh

# 編集したPHPファイルのみを取得
FILES=$(git diff --cached --name-only --diff-filter=ACMR | grep '\.php$')

# 変更されたPHPファイルがない場合は何もしない
if [ -z "$FILES" ]; then
  exit 0
fi

# PHP CS Fixerを変更されたファイルに対して実行
./vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.php -- $FILES

# 変更されたファイルを再度ステージング
git add $FILES
```

<details>
<summary>`--diff-filter=ACMR`の文字の意味について</summary>

- A: Added（追加されたファイル）
- C: Copied（コピーされたファイル）
- D: Deleted（削除されたファイル）
- M: Modified（変更されたファイル）
- R: Renamed（リネームされたファイル）
- T: Type changed（ファイルのタイプが変更された）
- U: Unmerged（未マージのファイル）
- X: Unknown（不明なファイル）
- B: Broken pairing（ペアリングが壊れたファイル）
</details>

### 5. pre-commit フックスクリプトに実行権限を付与
```bash
chmod +x .git/hooks/pre-commit
```

### 6. 終了！
- 以下のような記述を行い`git commit`を実行してみますと自動的に整形されます
```
$arr = array();
```

### 余談
- 構文エラーと静的解析もコミット時に確認するようにいたしましたので現状は下記のような内容のpre-commitファイルになりました
```
#!/bin/sh

# 編集したPHPファイルのみを取得
FILES=$(git diff --cached --name-only --diff-filter=ACMR | grep '\.php$')

# 変更されたPHPファイルがない場合は何もしない
if [ -z "$FILES" ]; then
  exit 0
fi

# 各PHPファイルの構文チェックを実行
for FILE in $FILES; do
  php -l $FILE
  if [ $? -ne 0 ]; then
    echo "Syntax error found in $FILE. Please fix it before committing."
    exit 1
  fi
done

# PHP CS Fixerを変更されたファイルに対して実行
./vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.php -- $FILES

# Larastanを実行し、エラーを返した場合はコミットを中断
./vendor/bin/phpstan analyse --level=max --memory-limit=1G $FILES
if [ $? -ne 0 ]; then
  echo "Larastan has found errors. Please fix them before committing."
  exit 1
fi

# 変更されたファイルを再度ステージング
git add $FILES
```

- 現状は他の実装者とpre-commitを共有ができないためカスタムGitフックやYAMLファイルを使用する方法を採用しても良いかと考えました
    - [pre-commitを使ってgit pushする前にBlack、Isortでコードを事前にチェックしよう](https://qiita.com/shun198/items/7352a5c67bb3284583d1)
    - [【Git】コミット直前に自動でファイルを整形する「pre-commit」が便利すぎたので紹介したい](https://dev.classmethod.jp/articles/introduce-pre-commit/)