ソースコードの“赤ペン先生”PHP-CS-Fixerのインストールと設定

[PHP-CS-Fixer]はコマンドひとつでPHPコードを美しく整形してくれる魔法のような開発ツールだ。ソースコードのスタイルがおかしなところを指摘するだけでなく訂正までしてくれる。いわばソースコードの「赤ペン先生」だ。

特に複数人が携わる開発現場では、PHP-CS-Fixerを取り入れることで、各自のコーディングの癖を取り除き、コーディングスタイルが統一され、コードの保守性向上が期待できる。

## PHP-CS-Fixerのデモ

こういうやる気のないコードでも……

```php
<?php
use UnusedImportedClass;
class A {
	/**
	  * 適当なDocComment
	  */
	private function hoge(int $x, int $y){}
private $arr = array(1,2,3,);
		/**
	* @param int $a
* @param string $bar
*/
function hoge( $a,   $bar ) {
		if ($a === 1) {
			# 特殊なケース
       return $bar;
} else {
//
return $bar.$bar;
}
}
}
```

PHP-CS-Fixerにかかると、美しくなる！

```php
<?php

declare(strict_types=1);

class A
{
    private $arr = [1, 2, 3];

    /**
     * @param int    $a
     * @param string $bar
     */
    public function hoge($a, $bar)
    {
        if ($a === 1) {
            // 特殊なケース
            return $bar;
        }

        return $bar . $bar;
    }

    /**
     * 適当なDocComment.
     * @param int $x
     * @param int $y
     */
    private function hoge(int $x, int $y): void
    {
    }
}

```

## コードレビュー(人力)によるコード整形との比較

| コードレビュー(人力) | PHP-CS-Fixer  |
| ----------- | ------------- |
| 手間がかかる      | すぐ終わる         |
| お互い気を使う     | 相手が機械だから気兼ねない |


## IDEの自動整形機能との比較

| IDEの整形機能            | PHP-CS-Fixer     |
| ------------------- | ---------------- |
| コードの自動整形ができる        | コードの自動整形ができる     |
| スニペット単位の整形が可能          | ファイル単位が整形の最小単位        |
| 個人的な整形に向いている        | チームで整形に取り組める     |
| 整形ルールを共有しにくい        | 整形ルールをGitに入れておける |
| みんなで同じIDEを使わないといけない | 開発者のエディタに制約がない   |
| CIでは使いにくい           | CIでも使いやすい        |
| PhpStormは有料         | 無料、有料ライセンス不要     |


[PHP-CS-Fixer]: https://github.com/FriendsOfPHP/PHP-CS-Fixer

## PHP-CS-Fixerをインストールする

composer.jsonがあるディレクトリにて、composerコマンドでphp-cs-fixerをインストールする。

```
composer require --dev friendsofphp/php-cs-fixer
```

インストールできたか確認する。

```
❯ ./vendor/bin/php-cs-fixer --version
PHP CS Fixer 2.9.0 Speechless by Fabien Potencier and Dariusz Ruminski
```

## 整形予定を確認する

さっそくフォーマットしてもいいが、その前にどう変わるか見ておこう。

```
❯ ./vendor/bin/php-cs-fixer fix --dry-run ./src
Loaded config default.
Using cache file ".php_cs.cache".
   1) src//Json.php
   2) src//Json/EncodingContext.php
   3) src//Json/DecodingException.php
   4) src//Json/Encoder.php
   5) src//Json/DecodingContext.php
   6) src//Json/EncodingException.php
   7) src//Json/Decoder.php

Checked all files in 0.725 seconds, 10.000 MB memory used
```

`php-cs-fixer fix $変更対象のディレクトリ`のコマンドに`--dry-run`オプションを付けると整形無しに変更予定のファイル一覧を見ることができる。この例では、7つのファイルが変更予定だ。

次にどんな整形予定か差分を確認しよう。`--dry-run`オプションに加えて`--diff`を加える。更にお好みで差分の表示形式指定`--diff-format udiff`を加えても良い。

```
./vendor/bin/php-cs-fixer fix --dry-run --diff --diff-format udiff ./src
```

次が差分の出力例だ。赤色が消されるコード。緑色が足されるコードだ。次例では有るべきでない改行を取り除くことを意味している。

<img width="525" alt="1.png" src="https://qiita-image-store.s3.amazonaws.com/0/889/195048b4-42fa-8a92-2f2f-367c982eea4d.png">


##  整形を実行する

整形予定が確認できたら整形を実行する。

```
./vendor/bin/php-cs-fixer fix ./src
```

もし変更前のコードがGit管理下にあるなら、PHP-CS-Fixerが加えた変更を`git`コマンドで確認できるだろう。

```
❯ git status -s
 M composer.json
 M src/Json.php
 M src/Json/Decoder.php
 M src/Json/DecodingContext.php
 M src/Json/DecodingException.php
 M src/Json/Encoder.php
 M src/Json/EncodingContext.php
 M src/Json/EncodingException.php
```

## 整形設定ファイル .php_cs.dist を作る

PHP-CS-Fixerは設定なくともよしなに整形してくれるが、整形方法を細かくカスタマイズすることもできる。整形設定を好みのものにするには、まず`.php_cs.dist`という名前で整形ルール設定ファイルを作る。このファイルはGitにコミットし、プロジェクトチームで共有する。

```
touch .php_cs.dist
```

ちなみに、.php_cs.distと.php_csの2つのファイルが有ると、.php_csが優先して読み込まれる。.php_cs.distはチーム共有のもの、.php_csは開発者個人のものという位置づけだ。.php_csは.gitignoreでGitにコミットされないようにしておく。

.php_cs.distの設定は次のように書く。ちなみに下記と同等の「[PHP-CS-Fixerの設定が面倒くさい人のためのルールセット - Qiita](https://qiita.com/suin/items/192fd755bff4c46b1918)」もあるのでご参考に。

```php
<?php

return PhpCsFixer\Config::create()
    ->setRiskyAllowed(true)
    ->setRules([
        '@PSR2' => true,
        'align_multiline_comment' => true,
        'array_syntax' => ['syntax' => 'short'],
        'binary_operator_spaces' => true,
        'blank_line_after_opening_tag' => true,
        'blank_line_before_statement' => ['statements' => ['declare', 'do', 'for', 'foreach', 'if', 'switch', 'try']],
        'cast_spaces' => true,
        'class_attributes_separation' => true,
        'combine_consecutive_issets' => true,
        'combine_consecutive_unsets' => true,
        'compact_nullable_typehint' => true,
        'concat_space' => ['spacing' => 'one'],
        'declare_equal_normalize' => true,
        'declare_strict_types' => true,
        'dir_constant' => true,
        'ereg_to_preg' => true,
        'escape_implicit_backslashes' => true,
        'explicit_indirect_variable' => true,
        'explicit_string_variable' => true,
        'final_internal_class' => true,
        'function_to_constant' => true,
        'function_typehint_space' => true,
        'general_phpdoc_annotation_remove' => ['annotations' => ['class', 'package', 'author']],
        'hash_to_slash_comment' => true,
        'heredoc_to_nowdoc' => true,
        'include' => true,
        'is_null' => ['use_yoda_style' => false],
        'linebreak_after_opening_tag' => true,
        'list_syntax' => true,
        'lowercase_cast' => true,
        'magic_constant_casing' => true,
        'method_chaining_indentation' => true,
        'method_separation' => true,
        'modernize_types_casting' => true,
        'native_function_casing' => true,
        'no_alias_functions' => true,
        'no_blank_lines_after_class_opening' => true,
        'no_blank_lines_after_phpdoc' => true,
        'no_empty_comment' => true,
        'no_empty_phpdoc' => true,
        'no_empty_statement' => true,
        'no_extra_consecutive_blank_lines' => ['tokens' => ['break', 'continue', 'extra', 'return', 'throw', 'use', 'parenthesis_brace_block', 'square_brace_block', 'curly_brace_block']],
        'no_homoglyph_names' => true,
        'no_leading_import_slash' => true,
        'no_leading_namespace_whitespace' => true,
        'no_mixed_echo_print' => true,
        'no_multiline_whitespace_around_double_arrow' => true,
        'no_multiline_whitespace_before_semicolons' => true,
        'no_null_property_initialization' => true,
        'no_php4_constructor' => true,
        'no_short_bool_cast' => true,
        'no_singleline_whitespace_before_semicolons' => true,
        'no_spaces_around_offset' => true,
        'no_trailing_comma_in_list_call' => true,
        'no_trailing_comma_in_singleline_array' => true,
        'no_unneeded_control_parentheses' => true,
        'no_unneeded_curly_braces' => true,
        'no_unneeded_final_method' => true,
        'no_unreachable_default_argument_value' => true,
        'no_unused_imports' => true,
        'no_useless_else' => true,
        'no_useless_return' => true,
        'no_whitespace_before_comma_in_array' => true,
        'no_whitespace_in_blank_line' => true,
        'normalize_index_brace' => true,
        'object_operator_without_whitespace' => true,
        'ordered_class_elements' => true,
        'ordered_imports' => true,
        'php_unit_construct' => true,
        'php_unit_dedicate_assert' => true,
        'php_unit_mock' => true,
        'php_unit_namespaced' => true,
        'phpdoc_add_missing_param_annotation' => ['only_untyped' => false],
        'phpdoc_align' => ['tags' => ['param']],
        'phpdoc_annotation_without_dot' => true,
        'phpdoc_indent' => true,
        'phpdoc_inline_tag' => true,
        'phpdoc_no_access' => true,
        'phpdoc_no_empty_return' => true,
        'phpdoc_no_package' => true,
        'phpdoc_order' => true,
        'phpdoc_return_self_reference' => true,
        'phpdoc_scalar' => true,
        'phpdoc_single_line_var_spacing' => true,
        'phpdoc_summary' => true,
        'phpdoc_to_comment' => true,
        'phpdoc_trim' => true,
        'phpdoc_types' => true,
        'phpdoc_types_order' => true,
        'phpdoc_var_without_name' => true,
        'pow_to_exponentiation' => true,
        'protected_to_private' => true,
        'random_api_migration' => true,
        'return_type_declaration' => true,
        'self_accessor' => true,
        'semicolon_after_instruction' => true,
        'short_scalar_cast' => true,
        'simplified_null_return' => true,
        'single_blank_line_before_namespace' => true,
        'single_line_comment_style' => true,
        'single_quote' => true,
        'space_after_semicolon' => ['remove_in_empty_for_expressions' => true],
        'standardize_not_equals' => true,
        'ternary_operator_spaces' => true,
        'ternary_to_null_coalescing' => true,
        'trailing_comma_in_multiline_array' => true,
        'trim_array_spaces' => true,
        'unary_operator_spaces' => true,
        'void_return' => true,
        'whitespace_after_comma_in_array' => true,
        'yoda_style' => ['equal' => false, 'identical' => false],
    ])
    ->setFinder(PhpCsFixer\Finder::create()
        ->exclude('vendor')
        ->in(__DIR__)
    )
    ;
```



### 効率的に設定を書くコツ

PHP-CS-Fixerの整形ルールは数十ある。ひとつひとつ精査していると丸一日がかりの作業になる。効率的に設定を書くには次を注意しよう。

#### 完璧を目指しすぎない

プロジェクトに携わる皆で揺れの少ないコードを書くことが目的ならば、ルールの詳細にこだわらず、まずは自動整形できる環境を作ること優先しよう。ルールは後からでも変えられる。

#### 迷ったらデフォルトで

ルールの選択で5秒考えて迷ったらひとまずデフォルトにしておこう。PHP-CS-Fixerのデフォルト値は標準的なコーディング規約に従っていたり、より一般的なものが選択されているので、デフォルトでも全く問題はない。

#### ルールの詳細は`describe`コマンドで確認しよう

ルールの詳細は`describe`コマンドで確認できるので、よく分からないものはそれを見ながら設定すると良い。整形前のコードと整形後のコードまで示してくれる親切設計だ。

```
php-cs-fixer describe array_syntax
```

<img width="709" alt="2.png" src="https://qiita-image-store.s3.amazonaws.com/0/889/cec03e0d-1b95-e718-5ee0-efdfa4dc898e.png">


#### PHP-CS-Fixer Configuratorを活用しよう

設定を画面でポチポチやっていきたい場合は[PHP-CS-Fixer Configurator](https://mlocati.github.io/php-cs-fixer-configurator/)が便利。加えたいルールプリセットやルールを選んでいくと設定ファイルを書き出してくれる。

<img width="1439" alt="3.png" src="https://qiita-image-store.s3.amazonaws.com/0/889/5555cf6a-4e06-c29d-1291-97070e100943.png">


---

最後までお読みくださりありがとうございました。Twitterでは、Qiitaに書かない技術ネタなどもツイートしているので、よかったらフォローお願いします:relieved:→[Twitter@suin](https://twitter.com/suin)