Linter / FormatterからLaravelへの贈り物

## はじめに

この記事は [Laravel Advent Calendar 2020 - Qiita](https://qiita.com/advent-calendar/2020/laravel) の 14 日目 の記事です:gift:


アドベントカレンダーの貴重な１枠に、今更感のあるネタを挟んで申し訳ない気持ちもありますが、
`Linter(静的解析ツール)` と `Formatter(コード整形ツール)` の持つ魅力に感動したので書かせて頂きます。


また、英語スピーキングにご興味をお持ちの方は
[本エントリを英語でLT登壇した資料 (登壇日：2020.11.27)](https://www2.slideshare.net/noelchris3/fix-static-code-analysis-into-our-project)も合わせてご覧頂けると嬉しいです。



## 環境

|   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 | レビューの前に、機械的に指摘できるエラーを摘み取ることが可能なので、<font color="magenta"><strong>レビューでは設計などの高度な問題に集中できる</strong></font>。  |
|  PHP CS Fixer   | Formatter |  あらかじめ設定しておいたコーディングルールと異なるものを検出し、<font color="magenta"><strong>自動でその箇所を修正してくれる</strong></font>。  |


### 具体例

３人編成のチームがいる想定です。(Aさん, Bさん, Cさん)
癖の強いコーディングをするチームメイト(Bさん・Cさん)が居た場合に、
コマンド一発で３人同じルールの書き方に統一しよう！ 
というものですね。

まず、整形前の３人のコードをご覧ください。
一目瞭然なくらい、書き方が変なチームメイトを混ぜておきました。


#### 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;
    }
}

```
<br>

#### 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;}
}
```

<br>

#### 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;
}
}
```

これをコマンド一発で３人の書き方の癖を直して、
同じものに整形しよう！というものですね！



- 今回の整形に用いる項目は、5つです。

|No.|ルール|
|:--:|:---|
| 1 |  クラスの各プロパティおよび各メソッドの間に、空白１行だけ入れる。 |
| 2 |  Docブロックに対して、インデントを揃える。 |
| 3 |  useでインポートの記述がある関数に関して、引数や戻り値として完全修飾形式で書かれている場合、非修飾形式として無駄を省く形に変換。 |
| 4 |  PHPDocの記述において、始めと終わりの周囲には無駄な行を入れない。 |
| 5 |  ルールセット`@PSR2`の適用。|
 

ポチッとな！

```sh
./vendor/bin/php-cs-fixer fix ./app
```

#### <font color="red"><strong>ワンクリックで３人のコードが以下に統一される！</strong></font>

```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;
    }
}

```

ああ、幸せな世界になった。:sparkles:

<br>



## 導入方法について


#### どちらも(Larastan & PHP-CS-Fixer)使い方はシンプルです。
  

1. composerでインストール  
2. 所定の設定ファイルにルールを書く。  
3. runコマンドを実行。  

これだけです。  
面倒なのは、 `2.所定の設定ファイルにルールを書く` 所ですね。  


<br>

### (A) Larastan

#### (A-1) composerを用いて開発環境用にインストール
 
```
composer require --dev nunomaduro/larastan
```

#### (A-2) 設定ファイル (phpstan.neon.dist)を作成

[phpstan.neon.distの形式](https://github.com/nunomaduro/larastan#-getting-started-in-3-steps)

- 下記は、公式ページの設定そのままです。
  
```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-３) RUNコマンドの実行

##### composer.jsonのscriptsキーに省略形を書いておき、

```composer.json
"scripts": {
    "larastan": [
        "./vendor/bin/phpstan analyse"
    ],
}
```

##### その省略コマンドを実行

```sh
composer larastan
```

<br>
<br>


### (B) PHP-CS-Fixer

#### (B-1) composerを用いて開発環境用にインストール
```sh
composer require --dev friendsofphp/php-cs-fixer
```

#### (B-2) 設定ファイル (.php_cs.dist)を作成

[php_cs.distの形式](https://github.com/FriendsOfPHP/PHP-CS-Fixer/blob/2.16/.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-３) RUNコマンドの実行

##### composer.jsonのscriptsキーに省略形を書いておき、

```composer.json
"scripts": {
    "csfixer-run": [
        "./vendor/bin/php-cs-fixer fix ./app"
    ],
}
```

##### その省略コマンドを実行

```sh
composer csfixer-run
```

<br>
<br>

## ルール設定について深掘り


## (A) Larastanのルール設定を深掘り

- 基本的に、PHPStanのルールに準拠しています。
- 上位の検証レベルは、下位の検証レベルで定義されているルールを内包しています。
  - (具体例を挙げると、検証レベル5は、レベル0からレベル４までの全てのルールも合わせて検証されます。)


|   Level   |   処理内容   |
| :------: | :------ | 
|   0   |   `$this`で呼ばれる未定義のクラス、未定義の関数、未定義のメソッドが無いかどうか。<br>また、`$this`で呼ばれるメソッドや関数に渡す引数の数が等しいかどうか。  | 
|   1   |   マジックメソッド( __call と __get )によって、未定義の変数などが用いられていないかどうか。 | 
|   2   |   PHPDocsを参照しながら、(Level 0, Level 1と異なり、)$thisで呼ばれるもの以外も含め全てにおいて、未定義のメソッドがないかどうか。  | 
|   3   |   戻り値の型とクラスのプロパティで宣言された型が一致するかどうか。  | 
|   4   |   使用されていないコードが無いかどうか。<br>instanceofの指定に一致しないインスタンスが無いかどうか。<br>条件分岐において、通過することのない不要なelse分岐が無いかどうか。<br>return文の後に記述されているが、処理がなされないコードが無いかどうか。  | 
|   5   |   メソッドや関数の引数として型があっているかどうか。  | 
|   6   |   タイプヒントの書き忘れが無いかどうか。  | 
|   7   |   部分的に誤りのあるユニオン型の記述が無いかどうか。  | 
|   8   |   NULL許容型でメソッドを呼び出しを行なっていないかどうか。NULL許容型でプロパティへアクセスしている部分が無いかどうか。  | 


<br>
<br>


## (B) PHP CS Fixerのルール設定を深掘り

[全ての公式ルールセットはこちら](https://mlocati.github.io/php-cs-fixer-configurator/#version:2.16)

* 筆者が好んで用いるものをピックアップして書きます。
* なお、既存のルールセット (`@PSR2`や`@PhpCsFixer` )が検査する項目の◯Xも付けています。

|   ルール名   |   処理内容   |   `@PSR2`   |   `@PhpCs`<br>`Fixer`  |
| :------: | :------ | :------: | :------: |
|   <font color="red">align_multiline_comment</font>   |   複数行のDocコメントをPSR-5準拠させ、１行目のアスタリスクの位置に揃える  | X | ◯ |
|  <font color="red">blank_line<br>\_after_namespace</font>  |  名前空間宣言の下に入れるスペースは１行だけにする。　 | ◯ | ◯ |
|  <font color="red">blank_line<br>\_after_opening_tag</font>   |  開始タグ `<?php`の後には、<br>１行(以上)空白をあけるように整形   | X | ◯ |
|  <font color="red">blank_line<br>\_before_statement</font>   |  'break', 'case', 'continue', 'for', 'foreach', 'if', 'include_once', 'require_once', 'return', 'switch', 'throw', 'try', 'while'などを配列で指定してあげれば、その前には、<br>1行だけ空白をあけるように整形。 | X | ◯ |
|  <font color="red">cast_spaces</font>   |  型キャストと変数の間に半角スペースを入れるか否か   | X | ◯ |
|  <font color="red">class_attributes_separation</font>   |  クラス, トレイト, インターフェイスの各プロパティおよび各メソッドの間に、空白１行を入れるか否か。   | X | ◯ |
|  <font color="red">constant_case</font>   |  真偽値(true, false)およびnull値に関して、小文字に揃えるか否か。   | X | ◯ |
|  <font color="red">final_static_access</font>   |  finalクラスにおいて宣言されたstatic修飾子をself修飾子に変換する。   | X | X |
|  <font color="red">fully_qualified_strict_types</font>   |   インポートの記述がある関数に関して、引数や戻り値として完全修飾形式で書かれている場合、非修飾形式として無駄を省く形に変換。  | X | ◯ |
|  <font color="red">function_declaration</font>   |  関数の宣言において、半角スペース１つを入れるか否か   | ◯ | ◯ |
|  <font color="red">function_typehint_space</font>   |  関数の引数とタイプヒントの間に、半角スペース１つを入れるか否か。   | X | ◯ |
|  <font color="red">global_namespace_import</font>   |  クラス名/関数名/定数名を使用する際に、完全修飾形式(バックスラッシュ)で記載していると、グローバルにuse宣言した方法に変換してくれる。  | X | X |
|  <font color="red">indentation_type</font>   |  タブでインデントを取っていたら、半角スペースに直してくれる。   | ◯ | ◯ |
|  <font color="red">lowercase_cast</font>   |  型キャストは、全て小文字で書かれるように変換。  | X | ◯ |
|  <font color="red">magic_constant_casing</font>   |  マジック定数を、全て大文字に変換。   | X | ◯ |
|  <font color="red">magic_method_casing</font>    |  マジックメソッドを、全て小文字に変換。    | X | ◯ |
|  <font color="red">method_argument_space</font>   |  メソッドが複数の引数を取る場合の空白の入れ方。<br>複数の引数を1行に纏めるのか、複数行に纏めるのか。   | ◯ | ◯ |
|  <font color="red">method_chaining<br>\_indentation</font>   |  メソッドチェーンに用いられる矢印のインデントを揃える。   | X | ◯ |
|  <font color="red">new_with_braces</font>   |  new演算子を用いて、新しいインスタンスを生成する箇所では、必ずブレース()を付けるか否か。  | X | ◯ |
|  <font color="red">no_blank_lines<br>\_after_class_opening</font>   | クラスの開始ブレースの直後には、空白行を作らない。   | X | ◯ |
|  <font color="red">no_blank_lines<br>\_after_phpdoc</font>  |  Docブロックと中身の記述の間には、空白行を作らない。   | X | ◯ |
|  <font color="red">no_extra<br>\_blank_lines</font>   |  空白行は1行ずつと定め、それ以外の余分な空白行を削除する。   | X | ◯ |
|  <font color="red">no_multiline_whitespace<br>\_around_double_arrow</font>   |  `=>`演算子の前後を複数行にせず、1行に纏める   | X | ◯ |
|  <font color="red">no_null_property<br>\_initialization</font>   |  クラスのプロパティをnullで初期化しない記法に揃える。  | X | ◯ |
|  <font color="red">no_spaces<br>\_after_function_name</font>   |  メソッドや関数の呼び出しを行う記述の際には、直後にスペースを入れない。 | ◯ | ◯ |
|  <font color="red">no_spaces<br>\_around_offset</font>   |  配列の要素を添字で指定して呼び出す際に、余分な半角スペースを入れない。   | X | ◯ |
|  <font color="red">no_spaces<br>\_inside_parenthesis</font>  |  小括弧の中にスペースを含めない。  | ◯ | ◯ |
|  <font color="red">no_trailing_whitespace</font>  |  文末のセミコロンの後には、半角スペースを含めない。  | ◯ | ◯ |
|  <font color="red">no_trailing_whitespace<br>\_in_comment</font>   |  PHPDocの中に余分なスペースを含めない。   | ◯ | ◯ |
|  <font color="red">no_unneeded<br>\_control_parentheses</font>   |  'break', 'continue', 'echo', 'print', 'return', 'switch_case', 'yield'などに関して、不要な小括弧を取り除く。   | X | ◯ |
|  <font color="red">no_unused_imports</font>   |  使用されていないuse句を削除。   | X | ◯ |
|  <font color="red">no_useless_else</font>   |  不要なelse分岐を除く。   | X | ◯ |
|  <font color="red">no_useless_return</font>   |  不要なreturn宣言を除く。   | X | ◯ |
|  <font color="red">no_whitespace<br>\_before_comma_in_array</font>    |  配列処理の記述の中で、コンマの直前に半角スペースを入れない。   | X | ◯ |
|  <font color="red">ordered_class_elements</font>   |  クラス内に宣言されているclasses/interfaces/traitsの規則性を持たせて順序を揃える。  | X | ◯ |
|  <font color="red">ordered_imports</font>    |  use句でインポートするクラスなどをアルファベット順に並べる。   | X | ◯ |
|  <font color="red">php_unit_fqcn_annotation</font>   |  PHPUnitで記載するアノテーションには、完全修飾形式で書くように修正する。   | X | ◯ |
| <font color="red">phpdoc_add_missing<br>\_param_annotation</font> | 全ての引数に`@param`を付与する。 |X | ◯ |
| <font color="red">phpdoc_indent</font> | Docブロックに対して、インデントを揃える。 |X | ◯ |
| <font color="red">phpdoc<br>\_no_empty_return</font> | `@return void`と`@return null`のアノテーションを、PHPDocから削除する。 |X | ◯ |
| <font color="red">phpdoc_order</font> | PHPDoc内におけるアノテーションの順序は、`@param` => `@throws` => `@return` と整列させる。 |X | ◯ |
| <font color="red">phpdoc_scalar</font> | スカラー型に関して、<br>integer => int,<br>boolean => bool,<br>real, double => float<br>で表記を統一する。 |X | ◯ |
| <font color="red">phpdoc_trim</font>  | PHPDocの記述において、始めと終わりの周囲には無駄な行を入れない。 |X | ◯ |
| <font color="red">phpdoc_types</font>  |  PHPDocの型記述は、全て小文字で書く。 |X | ◯ |
| <font color="red">phpdoc_types_order</font>  | PHPDoc内にユニオン型で書かれた型の並び順をソートする。 | X | ◯ |
| <font color="red">phpdoc_var<br>\_without_name</font>  | `@var`アノテーションや`@type`アノテーションには、変数名を書かない。 |X | ◯ |
| <font color="red">protected_to_private</font>   | finalクラスなどで宣言されているprotectedプロパティとprotectedメソッドを、privateに変換する。 |X | ◯ |
| <font color="red">return_type_declaration</font>  | 戻り値の型宣言の前のコロンの前後に関しては、コロン前には半角スペース無しで、コロン後に１つだけ半角スペースを入れる。 |X | ◯ |
| <font color="red">single_line<br>\_comment_style</font>  | 1行コメントの場合は、`//`記法に整形する。 |X | ◯ |
| <font color="red">single_quote</font>  | 変数の展開などを含まない単純文字列を囲むクォーテーションは、シングルクォートに統一する。  |X | ◯ |
| <font color="red">ternary_operator_spaces</font>  | 三項演算子を用いている前後のスペースは、半角スペース一個に統一する。 |X | ◯ |
| <font color="red">yoda_style</font>  | ヨーダ記法に則るか否か。 |X | ◯ |


<br>

## Fixer Setの種類

- 頻繁に使われるルールの組み合わせは、Fixer Setとして纏めて下さっているようです。
- また、筆者は、`risky` が付いたルールセットは好んで利用しないようにしています。

|   Fixer セット   | 
| :------: | 
| `@PSR2` |
| `@PhpCsFixer` |
| `@Symfony` |
| `@PHP70Migration` |
| `@PHP73Migration` |
| `@PHP80Migration` |
| `@PhpCsFixer:risky` |
| `@Symfony:risky` |
| etc |


## さいごに

クリスマスイブまで、たった10日なんですね！
来年もどうぞよろしくお願いします :tada:



## 参考文献

- [PHPStan Rule Levels (Larastanもこれに準拠)](https://phpstan.org/user-guide/rule-levels)
- [PHP CS Fixer Configurator](https://mlocati.github.io/php-cs-fixer-configurator/#version:2.16)
- [[PHP] 静的解析ツールPHPStanの機能概要](https://qiita.com/mizutsu/items/24bf34579119628eefe2)
- [PHPStan 0.12.0がリリースされました](https://www.phper.ninja/entry/2019/12/06/032456)