LarastanでLaravelプロジェクトを静的解析しよう！

# はじめに
[Laravel Advent Calendar 2021](https://qiita.com/advent-calendar/2021/laravel) 2日目の投稿です。
PHPのソースコードの品質管理はどのように実施していますでしょうか。

チームで開発している場合はコーディング規約に従ってコードレビューを実施している場合もあるかと思います。
しかし、コードレビューを人の手で実施している場合は以下のような問題があるかと思います。

# 人力によるコードレビューのつらみ

* レビューに時間がかかる
    * 修正コード量が多いと修正差分を見ただけで「うっ・・・」っとなります

* ルール違反していても見逃してしまう場合がある
    * ルールが増えれば増えるほどチェック漏れのリスクが増加します

* コードレビューしてくれる人を探さなければならない
    * コードレビューできる人が限られている場合、修正したコードがマージされるまでにかなり時間差が生まれることも

* コードレビューできない人をできるように育てないといけない
    * 特定のスーパープログラマがレビュアーになっている場合は新しいメンバーに後任を務めてもらえるようにするのは簡単ではありません

* 差し戻し時にコードレビューする人の主観が入ってしまう場合がある
    * 意見が半々に分かれるような指摘は受け入れられない場合があります

# そもそものやりたいことって？

コードレビュー時は `型がかけているか` とか `PHPDocがちゃんと書けているか` などを１つ１つ血眼でチェックしたいわけではないと思います。

基本的なルールは機械的にチェックしてしまい、コードレビューでは可読性や保守性を高めるにはどうしたらよいかといったコードと向き合う時間をできるだけ多く確保したいですね。

# 静的解析

静的解析ではプログラムを実行せずともソースコードを解析することで構文のチェックができます。つまり、プログラムを実行する前に、エラーとなりうる実装箇所や非推奨となっている実装箇所を発見することができます。

PHP はスクリプト言語であるため、プログラムを実行するまでそのソースコードが文法上問題ないかがわかりません。場合によっては特殊な条件でしか処理されないような分岐があったとして、その分岐内の処理がテストされないままリリースされてしまった結果、リリース後に不具合が発覚するなどもありえます。そのため、静的解析ツールでソースコード全体を機械的に構文チェックすることが有効です。

## PHPStan

[PHPStan](https://phpstan.org/) はPHPで有名な静的解析ツールの１つです。

Composerでインストール可能であったり公式のDockerコンテナが存在するなど、手軽に導入できる点が嬉しいです。
また、解析の厳密さを示すレベルを9段階で指定できることから、いきなりすべてのエラーに対応するのが大変な巨大なプロジェクトにも比較的導入しやすいかと思います。

各レベル別の解析内容は公式のドキュメントを参照していただけますと幸いです。

https://phpstan.org/user-guide/rule-levels

## Larastan

前置きが長くなりましたが本題の[Larastan](https://github.com/nunomaduro/larastan)について記述していきます。

[Larastan](https://github.com/nunomaduro/larastan) は PHPStanの拡張であり、Laravel プロジェクトの静的解析が可能です。

Larastan では Laravel でアプリケーションを作成する上でパフォーマンスを劣化させる可能性がある実装箇所や実行時エラーになりえる箇所をチェックすることができます。

https://github.com/nunomaduro/larastan

### 導入方法

公式のREADMEにもしっかりと記載されていますが改めて解説します。

#### composer 経由での取得

Laravelプロジェクトのルートに移動して以下コマンドを実行

```
composer require nunomaduro/larastan --dev
```

#### 設定ファイルの作成

Laravelプロジェクトのルートにて `phpstan.neon` または `phpstan.neon.dist` というファイル名のファイルを作成して以下の内容を記載します。

```
includes:
    - ./vendor/nunomaduro/larastan/extension.neon

parameters:

    paths:
        - app

    # The level 9 is the highest level
    level: 5

    ignoreErrors:
        - '#Unsafe usage of new static#'

    excludePaths:
        - ./*/*/FileToBeExcluded.php

    checkMissingIterableValueType: false
```

各パラメータの詳しい内容は以下をご参照ください。

https://phpstan.org/config-reference

#### Larastan による静的解析の実行

以下のコマンドを実行することで Larastan による静的解析が実行できます。

```
./vendor/bin/phpstan analyse
```

解析結果はPHPStanによる実行結果に加えてLarastanで独自拡張された解析ルールで解析された結果が表示されます。

### Larastanの解析ルール

Larastanによって解析できる特別な解析ルールをいくつかご紹介いたします。

解析ルールの詳細は[GitHub上のドキュメント](https://github.com/nunomaduro/larastan/blob/master/docs/rules.md)より参照させていただきます。

#### NoModelMake

Modelクラスの静的メソッド`make（）`を利用している箇所を検知してくれます。

```php
User::make();
```

ソースコード上、上記ような書き方をしてもエラーにはなりませんが、インスタンスが無駄に２つ生成されてしまいます。
モデルクラスを利用する場合は単純に new でインスタンスを生成した方が効率的という理由から解析対象となっております。

内部実装を知っていなければそれがパフォーマンスを悪化させる原因になっていることに気づけないのでとてもありがたいですね。

#### NoUnnecessaryCollectionCall

`Illuminate\Support\Collection` とそのサブクラスの組み合わせにおいて、無駄に重いSQLが生成される可能性がある実装箇所を検知してくれます。

```php
// 改善前
User::all()->count();
$user->roles()->pluck('name')->contains('a role name');

// 改善後
User::count();
$user->roles()->where('name', 'a role name')->exists();
```

こういうことも知っていないとうっかりやってしまいそうですね。

#### RelationExistenceRule

Eloquent 経由でDBアクセスする際、存在しないカラムに対してデータ取得しようとしている場合に検知することができます。

```php
// Userにfooというカラムが存在しない場合にエラーを検知
\App\User::query()->has('foo');
// 複数カラムに対するデータ取得の場合でもすべてのカラムが存在しているかが検知可能
\App\Post::query()->has('users.transactions.foo');
```

マイグレーションを行い、DB構造が変わった場合などに特定処理が壊れてしまっていないかなどを簡単に検知できそうです。

#### CheckDispatchArgumentTypesCompatibleWithClassConstructorRule

ジョブのディスパッチ引数の型がジョブクラスのコンストラクターと互換性があるかをチェックします。

```php
class ExampleJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    /** @var int */
    protected $foo;
    
    /** @var string */
    protected $bar;
    
    public function __construct(int $foo, string $bar)
    {
        $this->foo = $foo;
        $this->bar = $bar;
    }

    // Rest of the job class
}

// 引数が足りないパターン
ExampleJob::dispatch(1);

// 引数の型が一致していないパターン
ExampleJob::dispatch('bar', 1);
```

これも嬉しいですね。
バッチ処理は通常の画面操作テストでは確認しづらい部分かと思いますので、単純ミスなどが早期に見つかる仕組みがあるのは心強いです。

# おわりに

Larastan は比較的簡単に導入できますが、きっちり有益なエラーを検知してくれます。実行時にようやく見つかるような不具合をより早い段階で気づけるようにしておくことで実装時の手戻りも減らせると思います。また、どういうコードが不適切であるかは知らなければ気付けません。コード品質を高めるための前提知識を静的解析経由で知ることができるのも導入のメリットかも知れません。