PHP
php-cs-fixer

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

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

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

PHP-CS-Fixerのデモ

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

<?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

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をインストールする

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

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

1.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」もあるのでご参考に。

<?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

2.png

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

設定を画面でポチポチやっていきたい場合はPHP-CS-Fixer Configuratorが便利。加えたいルールプリセットやルールを選んでいくと設定ファイルを書き出してくれる。

3.png