PHP CS Fixer v2 でもっと快適PHPライフ

2014年に書いた PHP CS Fixer の記事 が今でも読まれているのですが、2016年末にリリースされた PHP CS Fixer v2.0後方互換のない変更が多く入っており情報が古くなっているため、改めて v2 について書いてみようと思います。なお現時点での最新版は v2.1.2 となります。

fivestar.hatenablog.com

PHP CS Fixer とは

PHP CS FixerPHP コードをコーディング規約 (CS = Coding Standards) に沿って補正してくれるライブラリです。歴史を辿ると、元々は Symfony のプロジェクトリードの fabpot 氏が作成したもので、 PSR-½ 、あるいは Symfony のコーディング規約 に沿ってコードを補正する機能が実装されています。

github.com

チームでコードを書くときはコーディング規約に沿って書くのが大前提ですが、例えばコードレビューをするときにいちいちコーディング規約の指摘をするのも不毛ですし、かといってメンバーが全員 100% 守れるかというとそれもなかなか大変なわけですが、 PHP CS Fixer を導入することでコーディング規約に沿ったコードに自動変換をかけられるためそういったわずらわしさから解放される、とてもすばらしい仕組みなわけです。

基本的にはコマンドラインツールとして提供されていて、ファイル単位や、特定のディレクトリ以下に適用するなど、 CLI での操作に慣れていればとてもかんたんに使うことが可能です。今日ではメジャーなライブラリとなっており、主要な IDE との連携も様々用意されています

前回の記事では、 Git のコミットフックで補正する方法を書きましたが、現在僕は保存時に都度 PHP CS Fixer で補正をするように IDE を設定して使っており、今時のだいたいの PHPer は IDE でコード書いてると思うので保存の際に自動補正という結論でよいと思います。

PHP CS Fixer v2 の変更点と使い方

前回の記事の時点では v1.2 でしたが、 v1.13 を経て現在は v2.1 までリリースされています。正直あまり細かくは追っておらず、ある日バージョンアップしたら v2.0 になって色々動かなくなったので、改めて README に目を通した程度なので、ざっくりとした解説となります。

これまでのバージョンアップの流れを見ると、コア機能自体に大きな変更はなく、拡張性・柔軟性の向上と、ルールの細分化・多様化といったことが主な変更内容に見受けられます。

レベル/フィクサーからルール/ルールセットに

v1.x は PSR-½ といったレベル (Level) の設定と、補正項目に当たるフィクサー (Fixer) でコーディング規約の設定をしていましたが、これらの扱いが「ルール (Rule) 」と「ルールセット (RuleSet) 」に変更されました。1つ1つの補正ルールと、複数の補正ルールの集合という形となり、再帰的なルール管理ができる他、複数のルールセットを適用できるため、より柔軟なルールの管理ができる構造になっています。

再帰的なルール管理とは、例えば 「Symfony」というルールは「PSR-2」を内包し、「PSR-2」は「PSR-1」を内包する、といった具合で活用されています。

複数のルールセットの適用については、ルールセットもルールの1つとして設定するような仕組みになっており、例えば次のコマンドのように「@ で始まっているのがルールセット」「それ以外を単一のルール」として --rules オプションで一括設定できるといったものです。 (ちなみに v1 で level と fixers を分離したのは僕だったりします)

$ php-cs-fixer fix /path/to/src --rules='@PSR2,@MyCompanyRuleSet,my_project_rule'

PHP のバージョンアップ用のマイグレーションルール (@PHP71Migration など) も用意されており、この構造変更により使い方の幅が広がりました。

ルールの一覧は README 、 v1 からルール名が変更されたものの確認は UPGRADE をご覧ください。

挙動が変わる恐れのあるルールかどうか区別できるように

ルールの中には挙動が変わる恐れがあるもの(リスキーなルール)があります。例えば ereg_to_preg は ereg 系関数を preg 系関数に置き換えるというルールです。こういったルールについては risky というフラグが設定されており、 --allow-risky オプションをつけて呼び出さないと実行に制限がかかる仕様になりました。

名前空間の変更

名前空間Symfony\CS から PhpCsFixer に変更されました。前回の記事で GitHubリポジトリオーナーが FriendsOfPHP に移管された話を書きましたが、コード側も Symfony から独立したものになりました。合わせて Composer での名称が fabpot/php-cs-fixer から friendsofphp/php-cs-fixer になっています。

.php_cs.dist の導入

プロジェクトごとのルールの設定を .php_cs というファイルで管理できるようになっていましたが、各自の環境ごとにカスタマイズできるよう、標準設定としての .php_cs.dist と個別設定用の .php_cs という扱いで分けられるようになりました。PHPUnitphpunit.xml.dist なんかと一緒ですね。プロジェクトのルートディレクトリに .php_cs があればそれを、なければ .php_cs.dist を参照します。

Git でプロジェクト管理をしている場合、 v1 までは .php_cs ファイルを作成してリポジトリに格納していましたが、 v2 を使う場合は .php_cs.dist としてリポジトリに格納しておき、 .php_cs.gitignore に指定しておきましょう。

.php_cs.dist の書き方について

改めて .php_cs.dist の書き方を見てみましょう。まずライブラリの名前空間が PhpCsFixer に変更されています。ルールの設定は setRules() で行いますが、設定したいルール名を配列のキーに、 true で有効、 false で無効となります。ルールによってはオプションの設定もできるようになっています。

ちなみにオプション付きのルールをデフォルト設定で有効化したい場合は [] を指定するのではなく true を設定します。気になって実装を見てた感じだと、 [] を渡すと false に変換されて無効になるようでした。

CLI--allow-risky オプションに相当するのが setRiskyAllowed() です。デフォルトでは false なので、有効にしたい場合は true にしましょう。

<?php
// .php_cs.dist
namespace PhpCsFixer;

return Config::create()
    ->setRiskyAllowed(true)
    ->setRules([
        '@PSR2' => true,
        'single_import_per_statement' => false,
        'array_syntax' => [
            'syntax' => 'short',
        ],
    )])
    ->setFinder(
        Finder::create()
            ->exclude('resources')
            ->in(__DIR__)
    )
;

ルールの一部解説

ルールは PSR や Symfony のコーディング規約に定義されたものを細分化して定義されていて様々な種類があらかじめ用意されています。また、特定のコーディング規約に含まれていないルールもあるため、余裕がある方は README に書かれているルールを一通りチェックしてみるとよいかと思います。 ただ、正直 README だけだとどういう挙動になるのか判断つかないものもあって、 テストケース を確認しながら設定していったので、この辺はもうちょっと充実していると助かりますね。

(追記)

describe コマンドなるものを教えていただきました。実行結果イメージを確認できるのでとてもわかりやすいですね。

f:id:Fivestar:20170330235135p:plain

(追記おわり)

せっかくなのでいくつかのルールをピックアップして紹介してみます。

ちなみにルールを適用した場合に手元のコードがどうなるのか確認したい時は、 CLI--dry-run オプションと --diff オプションをつけて実行すると適用した場合の差分が確認できます。

$ php-cs-fixer fix /path/to/src --dry-run --diff

@PSR1, @PSR2, @Symfony, @Symfony:risky: 標準ルールセット

PSR-2 にリスキーなルールはありませんが、 Symfony コーディング規約は一部リスキーなものを含んでいるため、リスキーなものを含む Symfony のコーディング規約を指定する場合は2つのルールを設定します。

<?php
return Config::create()
    ->setRules([
        '@Symfony' => true,
        '@Symfony:risky' => true,
    ]);

ちなみに僕は基本的に PSR-2 をベースにその他のルールから必要な分だけピックアップして使うようにしています。 Symfony のコーディング規約は比較的細かく、以前指定していた際に思わぬタイミングで変更が入ったり、バージョンアップで挙動が変わったりと煩わしいケースがあったので、こだわりがある人は PSR-2 + 必要なルールを定義すると安定します。

@PHP70Migration, @PHP71Migration: PHPマイグレーション用ルールセット

PHP のバージョンアップに追従する用のルールセットもあります。この辺はバージョンあげる際に CLI で実行して差分を見るとかがよいのかなと思いますが、あんまり大したことはしてないので一応あるくらいでみておくとよいでしょう。

array_syntax: 配列の表記を array() or [] に統一

配列の表記を統一するルールです。 v1 の short_array_syntax を設定したい場合は、 syntax オプションを 'short' にします。

<?php
return Config::create()
    ->setRules([
        'array_syntax' => [
            'syntax' => 'short',
        ],
    ]);

binary_operator_spaces: =>= のアライメント

連想配列や定数・変数定義で ==> の位置揃えに関するルールです。 align_double_arrowalign_equals の2つのオプションがあり、 true で揃える、 null で何もしない、 false で揃えない(方向に補正)となります。デフォルトではどちらも false で、 @Symfony だとデフォルトで有効です。 どちらもデフォルトで false となっており、

下記の例は => だけ揃えて、 = は何もしないという指定です。

<?php
return Config::create()
    ->setRules([
        'binary_operator_spaces' => [
            'align_double_arrow' => true,
            'align_equals' => null,
        ],
    ]);

blank_line_before_return, function_typehint_space, method_separation: 空白や空行の設定

return の前やメソッド定義の間は空行を入れるとか、タイプヒントと変数の間にはホワイトスペースを1つ入れるとか、リーダビリティに関するルールです。この辺は @Symfony で有効になっていますが、可読性のためにも基本有効にしておくとよいと思っています。

だいたい、タイプヒントと変数定義の間にホワイトスペースを入れるとか当たり前だろくらいに思いますけど、用意されているルールにはこういう当たり前だろって思うようなものもたくさんあり、だからこそ確実に設定しておきたいところです。

<?php
return Config::create()
    ->setRules([
        'blank_line_before_return' => true,
        'function_typehint_space' => true,
        'method_separation' => true,
    ]);

header_comment: PHP ファイルの共通コメント設定

コピーライトの表記などをすべてのファイルに共通で記載することができるルールです。たとえば公開用のライブラリでライセンス等を明記しておきたいとか、社内のルールで全てにコピーライトを記載する必要があるとか、あるいは自己顕示欲が強い方とかは活用するとよいでしょう。

<?php
$header = <<<'EOF'
(c) Ancar Inc.
EOF;

return Config::create()
    ->setRules([
        'header_comment' => [
            'header' => $header,
        ],
    ]);

no_unused_imports, ordered_imports: インポートの整理

no_unused_imports は未使用の use 文の削除、 ordered_importsuse 文のソートを行うルールです。 use 文が整理されていた方が依存状況が確認しやすいため、有効にしておくことをおすすめします。

<?php
return Config::create()
    ->setRules([
        'no_unused_imports' => true,
        'ordered_imports' => true,
    ]);

ちなみに ordered_imports を有効にした場合、 グルーピングしている部分もソート対象になります。

<?php
// before
use Model\{User, Article};

// after
use Model\{Article, User};

return_type_declaration: リターンタイプヒントのフォーマット

PHP 7.0 から導入されたリターンタイプヒントの書式ルールです。有効にすると function (): string のように : の後ろに1つホワイトスペースが入るようになります。 space_before オプションを 'one' にすると function () : string のように : の前にもホワイトスペースが入ります。

<?php
return Config::create()
    ->setRules([
        'return_type_declaration' => true,
    ]);

single_import_per_statement: インポートを1クラスごとに展開

1つの use 文につき1つのクラス定義のみ行うようにするルールです。これは @PSR2 に含まれているルールですが、 PSR-2 は PHP 7 が考慮されておらず、{...} を使ってグルーピングしているものも展開されてしまうため、僕はあえて無効にしています。

その辺は PSR-12: Extended Coding Style Guide の策定以降により細かく調整されるかと思いますので、 PSR-12 を待ちましょう。

<?php
return Config::create()
    ->setRules([
        '@PSR2' => true,
        'single_import_per_statement' => false,
    ]);

まとめ

PHP CS Fixer も v2 になって基本的な API 周りはだいぶ固まったかなという印象ですね。この記事で解説した以外にも設定項目があったり、様々なルールが定義されていますので、ぜひ自分にあった形で活用してもらえるとよいなと思います。

あとは PSR の方で、 PHP 7 対応のコーディング規約が待たれるところですね。あまりそっちも追ってるわけではないためどうなるのか不明ですが、何れにしても秩序をもたらす方向に歩んでいることはとてもすばらしいと思います。

コーディング規約を詳細に書いたドキュメントを用意する以上に .php_cs.dist にコーディング規約が定義されている状態の方が建設的ですし、 Coding Standards as Code な世界をエンジョイしましょう。

最後に、今僕がプロジェクトで使ってる .php_cs.dist を参考までに載せておきます。割と細かく指定があり全部解説はできないのですが、適用ルール把握のために PSR-2 + 個別ルールで設定・リスキーな補正はしない・言わなくてもやるようなルールであっても設定する、というポリシーで用意したため、チームの環境や文化に合わせて活用するとよいでしょう。

gist.github.com