はじめに
この記事は Laravel Advent Calendar 2020 - Qiita の 14 日目 の記事です
アドベントカレンダーの貴重な1枠に、今更感のあるネタを挟んで申し訳ない気持ちもありますが、
Linter(静的解析ツール)
と Formatter(コード整形ツール)
の持つ魅力に感動したので書かせて頂きます。
また、英語スピーキングにご興味をお持ちの方は
本エントリを英語でLT登壇した資料 (登壇日:2020.11.27)も合わせてご覧頂けると嬉しいです。
環境
Package | Version |
---|---|
PHP | 7.4.11 |
Laravel | 6.18.43 |
nunomaduro/larastan | 0.6.9 |
friendsofphp/php-cs-fixer | 2.16.7 |
Laravelプロジェクトにとって、どう嬉しいの?
パッケージ名 | 種別 | メリット |
---|---|---|
Larastan | Linter | レビューの前に、機械的に指摘できるエラーを摘み取ることが可能なので、レビューでは設計などの高度な問題に集中できる。 |
PHP CS Fixer | Formatter | あらかじめ設定しておいたコーディングルールと異なるものを検出し、自動でその箇所を修正してくれる。 |
具体例
3人編成のチームがいる想定です。(Aさん, Bさん, Cさん)
癖の強いコーディングをするチームメイト(Bさん・Cさん)が居た場合に、
コマンド一発で3人同じルールの書き方に統一しよう!
というものですね。
まず、整形前の3人のコードをご覧ください。
一目瞭然なくらい、書き方が変なチームメイトを混ぜておきました。
Aさん ( 無難に書いてくれる人 )
UserService.php
<?php
namespace App\Services;
use App\Repositories\UserRepositoryInterface;
class UserService
{
/**
* @var UserRepositoryInterface
*/
private $user;
/**
* @param UserRepositoryInterface $user
*/
public function __construct(UserRepositoryInterface $user)
{
$this->user = $user;
}
}
Bさん ( なるべく短い行数におさめたい派 )
UserService.php
<?php
namespace App\Services;
use App\Repositories\UserRepositoryInterface;
class UserService{
/**
* @var UserRepositoryInterface
*/
private $user;
/**
* @param UserRepositoryInterface $user
*/
public function __construct(UserRepositoryInterface $user) {$this->user = $user;}
}
Cさん ( 気ままに書いてたら、動いちゃったからOK派 )
UserService.php
<?php
namespace App\Services;
use App\Repositories\UserRepositoryInterface;
class UserService
{
/**
*
*
* @var UserRepositoryInterface
*
*/
private $user;
/**
* @param UserRepositoryInterface $user
*
*/
public function __construct(\App\Repositories\UserRepositoryInterface $user)
{
$this->user = $user;
}
}
これをコマンド一発で3人の書き方の癖を直して、
同じものに整形しよう!というものですね!
- 今回の整形に用いる項目は、5つです。
No. | ルール |
---|---|
1 | クラスの各プロパティおよび各メソッドの間に、空白1行だけ入れる。 |
2 | Docブロックに対して、インデントを揃える。 |
3 | useでインポートの記述がある関数に関して、引数や戻り値として完全修飾形式で書かれている場合、非修飾形式として無駄を省く形に変換。 |
4 | PHPDocの記述において、始めと終わりの周囲には無駄な行を入れない。 |
5 | ルールセット@PSR2 の適用。 |
ポチッとな!
./vendor/bin/php-cs-fixer fix ./app
ワンクリックで3人のコードが以下に統一される!
UserService.php
<?php
namespace App\Services;
use App\Repositories\UserRepositoryInterface;
class UserService
{
/**
* @var UserRepositoryInterface
*/
private $user;
/**
* @param UserRepositoryInterface $user
*/
public function __construct(UserRepositoryInterface $user)
{
$this->user = $user;
}
}
ああ、幸せな世界になった。
導入方法について
どちらも(Larastan & PHP-CS-Fixer)使い方はシンプルです。
- composerでインストール
- 所定の設定ファイルにルールを書く。
- runコマンドを実行。
これだけです。
面倒なのは、 2.所定の設定ファイルにルールを書く
所ですね。
(A) Larastan
(A-1) composerを用いて開発環境用にインストール
composer require --dev nunomaduro/larastan
(A-2) 設定ファイル (phpstan.neon.dist)を作成
- 下記は、公式ページの設定そのままです。
phpstan.neon.dist
includes:
- ./vendor/nunomaduro/larastan/extension.neon
parameters:
paths:
- app
level: 5
ignoreErrors:
- '#Unsafe usage of new static#'
excludes_analyse:
- ./*/*/FileToBeExcluded.php
checkMissingIterableValueType: false
(A-3) RUNコマンドの実行
composer.jsonのscriptsキーに省略形を書いておき、
composer.json
"scripts": {
"larastan": [
"./vendor/bin/phpstan analyse"
],
}
その省略コマンドを実行
composer larastan
(B) PHP-CS-Fixer
(B-1) composerを用いて開発環境用にインストール
composer require --dev friendsofphp/php-cs-fixer
(B-2) 設定ファイル (.php_cs.dist)を作成
- 下記は、個人的に好むルールにアレンジした状態です。
.php_cs.dist
<?php
return PhpCsFixer\Config::create()
->setRiskyAllowed(true)
->setRules([
'@PhpCsFixer' => true,
'blank_line_before_statement' => [
'statements' => [
'for', 'foreach', 'if', 'switch', 'try'
],
],
'final_static_access' => true,
'global_namespace_import' => true,
'phpdoc_add_missing_param_annotation' => [
'only_untyped' => false
],
'yoda_style' => [
'always_move_variable' => false,
'equal' => false,
'identical' => false
],
])
->setFinder(PhpCsFixer\Finder::create()
->exclude('vendor')
->in(__DIR__)
)
;
(B-3) RUNコマンドの実行
composer.jsonのscriptsキーに省略形を書いておき、
composer.json
"scripts": {
"csfixer-run": [
"./vendor/bin/php-cs-fixer fix ./app"
],
}
その省略コマンドを実行
composer csfixer-run
ルール設定について深掘り
(A) Larastanのルール設定を深掘り
- 基本的に、PHPStanのルールに準拠しています。
- 上位の検証レベルは、下位の検証レベルで定義されているルールを内包しています。
- (具体例を挙げると、検証レベル5は、レベル0からレベル4までの全てのルールも合わせて検証されます。)
Level | 処理内容 |
---|---|
0 |
$this で呼ばれる未定義のクラス、未定義の関数、未定義のメソッドが無いかどうか。また、 $this で呼ばれるメソッドや関数に渡す引数の数が等しいかどうか。 |
1 | マジックメソッド( __call と __get )によって、未定義の変数などが用いられていないかどうか。 |
2 | PHPDocsを参照しながら、(Level 0, Level 1と異なり、)$thisで呼ばれるもの以外も含め全てにおいて、未定義のメソッドがないかどうか。 |
3 | 戻り値の型とクラスのプロパティで宣言された型が一致するかどうか。 |
4 | 使用されていないコードが無いかどうか。 instanceofの指定に一致しないインスタンスが無いかどうか。 条件分岐において、通過することのない不要なelse分岐が無いかどうか。 return文の後に記述されているが、処理がなされないコードが無いかどうか。 |
5 | メソッドや関数の引数として型があっているかどうか。 |
6 | タイプヒントの書き忘れが無いかどうか。 |
7 | 部分的に誤りのあるユニオン型の記述が無いかどうか。 |
8 | NULL許容型でメソッドを呼び出しを行なっていないかどうか。NULL許容型でプロパティへアクセスしている部分が無いかどうか。 |
(B) PHP CS Fixerのルール設定を深掘り
- 筆者が好んで用いるものをピックアップして書きます。
- なお、既存のルールセット (
@PSR2
や@PhpCsFixer
)が検査する項目の◯Xも付けています。
ルール名 | 処理内容 | @PSR2 |
@PhpCs Fixer
|
---|---|---|---|
align_multiline_comment | 複数行のDocコメントをPSR-5準拠させ、1行目のアスタリスクの位置に揃える | X | ◯ |
blank_line _after_namespace |
名前空間宣言の下に入れるスペースは1行だけにする。 | ◯ | ◯ |
blank_line _after_opening_tag |
開始タグ <?php の後には、1行(以上)空白をあけるように整形 |
X | ◯ |
blank_line _before_statement |
'break', 'case', 'continue', 'for', 'foreach', 'if', 'include_once', 'require_once', 'return', 'switch', 'throw', 'try', 'while'などを配列で指定してあげれば、その前には、 1行だけ空白をあけるように整形。 |
X | ◯ |
cast_spaces | 型キャストと変数の間に半角スペースを入れるか否か | X | ◯ |
class_attributes_separation | クラス, トレイト, インターフェイスの各プロパティおよび各メソッドの間に、空白1行を入れるか否か。 | X | ◯ |
constant_case | 真偽値(true, false)およびnull値に関して、小文字に揃えるか否か。 | X | ◯ |
final_static_access | finalクラスにおいて宣言されたstatic修飾子をself修飾子に変換する。 | X | X |
fully_qualified_strict_types | インポートの記述がある関数に関して、引数や戻り値として完全修飾形式で書かれている場合、非修飾形式として無駄を省く形に変換。 | X | ◯ |
function_declaration | 関数の宣言において、半角スペース1つを入れるか否か | ◯ | ◯ |
function_typehint_space | 関数の引数とタイプヒントの間に、半角スペース1つを入れるか否か。 | X | ◯ |
global_namespace_import | クラス名/関数名/定数名を使用する際に、完全修飾形式(バックスラッシュ)で記載していると、グローバルにuse宣言した方法に変換してくれる。 | X | X |
indentation_type | タブでインデントを取っていたら、半角スペースに直してくれる。 | ◯ | ◯ |
lowercase_cast | 型キャストは、全て小文字で書かれるように変換。 | X | ◯ |
magic_constant_casing | マジック定数を、全て大文字に変換。 | X | ◯ |
magic_method_casing | マジックメソッドを、全て小文字に変換。 | X | ◯ |
method_argument_space | メソッドが複数の引数を取る場合の空白の入れ方。 複数の引数を1行に纏めるのか、複数行に纏めるのか。 |
◯ | ◯ |
method_chaining _indentation |
メソッドチェーンに用いられる矢印のインデントを揃える。 | X | ◯ |
new_with_braces | new演算子を用いて、新しいインスタンスを生成する箇所では、必ずブレース()を付けるか否か。 | X | ◯ |
no_blank_lines _after_class_opening |
クラスの開始ブレースの直後には、空白行を作らない。 | X | ◯ |
no_blank_lines _after_phpdoc |
Docブロックと中身の記述の間には、空白行を作らない。 | X | ◯ |
no_extra _blank_lines |
空白行は1行ずつと定め、それ以外の余分な空白行を削除する。 | X | ◯ |
no_multiline_whitespace _around_double_arrow |
=> 演算子の前後を複数行にせず、1行に纏める |
X | ◯ |
no_null_property _initialization |
クラスのプロパティをnullで初期化しない記法に揃える。 | X | ◯ |
no_spaces _after_function_name |
メソッドや関数の呼び出しを行う記述の際には、直後にスペースを入れない。 | ◯ | ◯ |
no_spaces _around_offset |
配列の要素を添字で指定して呼び出す際に、余分な半角スペースを入れない。 | X | ◯ |
no_spaces _inside_parenthesis |
小括弧の中にスペースを含めない。 | ◯ | ◯ |
no_trailing_whitespace | 文末のセミコロンの後には、半角スペースを含めない。 | ◯ | ◯ |
no_trailing_whitespace _in_comment |
PHPDocの中に余分なスペースを含めない。 | ◯ | ◯ |
no_unneeded _control_parentheses |
'break', 'continue', 'echo', 'print', 'return', 'switch_case', 'yield'などに関して、不要な小括弧を取り除く。 | X | ◯ |
no_unused_imports | 使用されていないuse句を削除。 | X | ◯ |
no_useless_else | 不要なelse分岐を除く。 | X | ◯ |
no_useless_return | 不要なreturn宣言を除く。 | X | ◯ |
no_whitespace _before_comma_in_array |
配列処理の記述の中で、コンマの直前に半角スペースを入れない。 | X | ◯ |
ordered_class_elements | クラス内に宣言されているclasses/interfaces/traitsの規則性を持たせて順序を揃える。 | X | ◯ |
ordered_imports | use句でインポートするクラスなどをアルファベット順に並べる。 | X | ◯ |
php_unit_fqcn_annotation | PHPUnitで記載するアノテーションには、完全修飾形式で書くように修正する。 | X | ◯ |
phpdoc_add_missing _param_annotation |
全ての引数に@param を付与する。 |
X | ◯ |
phpdoc_indent | Docブロックに対して、インデントを揃える。 | X | ◯ |
phpdoc _no_empty_return |
@return void と@return null のアノテーションを、PHPDocから削除する。 |
X | ◯ |
phpdoc_order | PHPDoc内におけるアノテーションの順序は、@param => @throws => @return と整列させる。 |
X | ◯ |
phpdoc_scalar | スカラー型に関して、 integer => int, boolean => bool, real, double => float で表記を統一する。 |
X | ◯ |
phpdoc_trim | PHPDocの記述において、始めと終わりの周囲には無駄な行を入れない。 | X | ◯ |
phpdoc_types | PHPDocの型記述は、全て小文字で書く。 | X | ◯ |
phpdoc_types_order | PHPDoc内にユニオン型で書かれた型の並び順をソートする。 | X | ◯ |
phpdoc_var _without_name |
@var アノテーションや@type アノテーションには、変数名を書かない。 |
X | ◯ |
protected_to_private | finalクラスなどで宣言されているprotectedプロパティとprotectedメソッドを、privateに変換する。 | X | ◯ |
return_type_declaration | 戻り値の型宣言の前のコロンの前後に関しては、コロン前には半角スペース無しで、コロン後に1つだけ半角スペースを入れる。 | X | ◯ |
single_line _comment_style |
1行コメントの場合は、// 記法に整形する。 |
X | ◯ |
single_quote | 変数の展開などを含まない単純文字列を囲むクォーテーションは、シングルクォートに統一する。 | X | ◯ |
ternary_operator_spaces | 三項演算子を用いている前後のスペースは、半角スペース一個に統一する。 | X | ◯ |
yoda_style | ヨーダ記法に則るか否か。 | X | ◯ |
Fixer Setの種類
- 頻繁に使われるルールの組み合わせは、Fixer Setとして纏めて下さっているようです。
- また、筆者は、
risky
が付いたルールセットは好んで利用しないようにしています。
Fixer セット |
---|
@PSR2 |
@PhpCsFixer |
@Symfony |
@PHP70Migration |
@PHP73Migration |
@PHP80Migration |
@PhpCsFixer:risky |
@Symfony:risky |
etc |
さいごに
クリスマスイブまで、たった10日なんですね!
来年もどうぞよろしくお願いします