🐘

Laravelを使ったプロジェクトを始めるならLarastanくらいは導入しようよ

2023/04/22に公開

TL;DR

先日、オタクコミュニティの形成と自身の技術力向上を目的に OSS プロジェクトを立ち上げました。

すると、早速フィードバックをいただくことができました。

https://github.com/ysknsid25/otaku-tool/issues/41

こちらの issue の中で、「品質担保のために Linter を入れるといいよ」というアドバイスをいただいています。

そこで今回はせっかくの新規プロジェクト立ち上げたいい機会なのでいろいろと勉強しながら Linter を導入していくまでに調べたことなどを自分への戒めも兼ねて書きます。

今回のゴールは PHPStan(Larastan)を導入し、GitHub Actions のワークフローにも静的解析を追加し、PR 上で静的解析結果を確認できるレビュー環境を作るところまでです。

https://phpstan.org/user-guide/getting-started

ちなみに

この記事のタイトルは Natsuki Ikeguchi さんの"GitHub を使うなら通知くらいまともに設定してくれ"からパ…インスパイアを受けたものです。

https://zenn.dev/siketyan/articles/you-are-not-using-github-correctly

なにはともあれインストール

composer require --dev phpstan/phpstan

インストール後、導入できたことを確認します。

./vendor/bin/phpstan -h

ヘルプ情報が返って来れば、無事に導入できています。

ハマりポイント

phpstan 固有のハマりポイントではなく、composer require のハマりポイントだったのですが一応メモとして。

composer require すると以下のようなエラーが出ました。

Your requirements could not be resolved to an installable set of packages.

  Problem 1
    - laravel/framework is locked to version v10.5.0 and an update of this package was not requested.
    - laravel/framework v10.5.0 requires composer-runtime-api ^2.2 -> found composer-runtime-api[2.0.0] but it does not match the constraint.
  Problem 2
    - laravel/framework v10.5.0 requires composer-runtime-api ^2.2 -> found composer-runtime-api[2.0.0] but it does not match the constraint.
    - spatie/laravel-ignition 2.0.0 requires illuminate/support ^10.0 -> satisfiable by laravel/framework[v10.5.0].
    - spatie/laravel-ignition is locked to version 2.0.0 and an update of this package was not requested.


Installation failed, reverting ./composer.json and ./composer.lock to their original content.

どうやら composer 自身のバージョンが古いとこういうエラーが出るようです。

以下のコマンドを実行後、再度 composer require すると解消しました。

composer selfupdate

初めての実行

(某キテレツの ED みたいな章題ですが…狙ってはいないです...)

./vendor/bin/phpstan analyse app tests

analyze の後ろには分析対象のディレクトリを指定します。

ディレクトリはカレントディレクトリ基準で、指定したディレクトリ以下を再帰的に見ていってくれます。

結果

おーでてるでてる

これでソースについて検証してくれるようになりましたね。

設定ファイルの作成

さて、静的解析ができるようになったところでもう少し便利にしていきたいと思います。

まずは、静的解析対象のディレクトリやレベルの指定などを設定ファイルに書き出してメンテナンス性を向上させていきます。

phpstan の設定ファイルはphpstan.neonという NEON 形式(初めて聞いた)のファイルになるようです。

phpstan.neon
parameters:
	level: 0
	paths:
		- app
		- tests

では作成した設定ファイルを使って静的解析を行います。

オプションは以下の通りです。

./vendor/bin/phpstan analyse -c phpstan.neon

そのほかの設定については公式ドキュメントをご参照あれ。

ルールレベル

PHPStan にはどうやらチェックのレベルがあるようです。

公式ドキュメントによると以下のとおり、0~9 存在するみたいです。

  1. 基本的なチェック、不明なクラス、不明な関数、 で呼び出された不明なメソッド$this、それらのメソッドと関数に渡された引数の数が間違っている、常に未定義の変数
  2. 未定義の変数、call と get を持つクラスの未知のマジックメソッドとプロパティがある可能性がある
  3. ($this だけでなく)すべての式で未知のメソッドをチェックし、PHPDocs を検証する
  4. 戻り値の型、プロパティに割り当てられた型
  5. 基本的なデッドコードチェック - instanceof やその他の型チェックが常に false、到達しない else 文、return 後の到達不能コードなど
  6. メソッドや関数に渡される引数の型をチェックする
  7. タイプヒントの欠落を報告する
  8. 部分的に間違っている論理和型の報告 - 論理和型の一部の型にしか存在しないメソッドを呼び出した場合、レベル 7 はそのことを報告し始めます(その他の不正確な状況も)
  9. メソッドの呼び出しと null 許容型のプロパティへのアクセスを報告する
  10. 混合型に厳密であること - この型で唯一許される操作は、この型を別の混合型に渡すことである

デフォルトの実行レベルは 0 に設定されています。

では試しにレベルを 1 に変更して実行してみましょう。

Laravel の Facade はマジックメソッドをフル活用しているので、おそらく Facade を使った箇所のエラーが増えるはずです。

ルールレベル 1 で静的解析を行なった結果

./vendor/bin/phpstan analyse --level 1 tests app

はい、案の定 Facade を使った場所はエラーになってますね。

Larastan(ララスタン)

先に見たように、マジックメソッドを使っている箇所に関して静的解析に引っかかってしまうと、マジックメソッドをフル活用している Laravel(というか Symfony?)は全体的にエラーになってしまいます。

ではこの状況を PHPStan の製作者たちは想定していなのか?と言われるとそんなことはなく、個別に拡張ライブラリを導入することでエラーを回避できるようになっています。

ただし、Laravel に関しては Larastan という非公式の拡張ライブラリしか提供されていません

が、非公式のものしかないのであればそれを使うしかないので導入していきます。

使い方

まずはインストール。

(どうも Larastan は PHPStan と依存関係にあるので、最初から Larastan を入れておけば一緒に PHPStan も入ってきてくれたっぽい)

composer --dev require nunomaduro/larastan

使い方は簡単で、先に作成した phpstan.neon に larastan の設定ファイルを include するだけ。

phpstan.neon
#この行を追加
includes:
    - ./vendor/nunomaduro/larastan/extension.neon

parameters:
	level: 1
	paths:
		- app
		- tests

再実行

では Facade 周りのエラーが消えるか確認していきます。

./vendor/bin/phpstan analyse -c phpstan.neon

結果

いい感じ!

テストコード内部での$this の呼び出し

Laravel のフレームワーク思想に則った形に静的解析が効き始めましたが、まだ多くのエラーが出ています。

しかし、その内容はどれも同じで、なおかつ tests フォルダ以下でのエラーです。

ではなぜ怒られているのか確認すると、

tests/Feature/Auth/RegistrationTest.php
<?php

use App\Providers\RouteServiceProvider;

test('registration screen can be rendered', function () {
    $response = $this->get('/register');

    $response->assertStatus(200);
});

まあメッセージの通りなのですがこのファイル単体で見たときに未定義の$this を呼んでいるせいですね。

けれども、コード的には問題ない(というか Breeze がそう書いてるので半分仕方ない)ので、tests フォルダ以下の「Undefined variable: $this」というエラーについては無視したいと思います。

そのために、設定ファイルを以下のように修正します。

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

parameters:
  level: 1
  paths:
    - app
    - tests
  ignoreErrors:
    -
      message: '#Undefined variable: \$this#'
      path: tests

修正後、静的解析を再実行します。

再実行結果

LGTM!!

真にエラーっぽい内容だけが残りました。

GitHub Actions と PHPStan(Larastan)を繋げて PR のコード上に自動で静的解析結果を表示する

上で LGTM とかほざいてますが、まだエラーが残ってますね。

これは残ってるのではなく、章題の検証のために残してるんですよええ。

(実は静的解析導入前にあった潜在的なエラーだったとは言えない)

では GitHub Actions と PHPStan(Larastan)を繋げて PR のコード上に自動で静的解析結果を表示するために、workflow の最後に PHPStan での静的解析を追加します。

# ~略~
- name: unit test
    run: php artisan test
    working-directory: /home/runner/work/otaku-tool/otaku-tool/framework
- name: Check with PHPStan
    run: ./vendor/bin/phpstan analyse -c phpstan.neon
    working-directory: /home/runner/work/otaku-tool/otaku-tool/framework

PHPStan には Output Format という機能があり、GitHub や GitLab などの主要なサービスに対応した形式に沿ったエラーを吐いてくれる機能があるのですが、どうやら GitHub Actions の場合はなにもせずともいい感じに出力してくれるみたいです。

出力ですが、PR の Checks > 当該ワークフローを選択し詳細画面へ移動すると、Annotations の部分に表示されていることがわかります。

では静的解析で怒られたエラーを解消して、ローカルで静的解析を再実行します。

すると…

オールグリーン!

PR 上でもエラーが消えたことを確認できました。

おわりに

最高のプルリクレビュー環境ができたと同時に、静的解析のノウハウも蓄積できてとてもよかったです。

こういう自分だけで考えてると気づかないことに、全世界のエンジニアの先輩たちから気づきを与えてもらえる点が OSS の魅力じゃないでしょうか。

また、この記事をご覧になられたかたで、さらなるノウハウをお持ちでしたらぜひともコメントからご教授ください!

メンバー募集中!

サーバーサイド Kotlin コミュニティを作りました!

Kotlin ユーザーはぜひご参加ください!!

https://serverside-kt.connpass.com/

また関西在住のソフトウェア開発者を中心に、関西エンジニアコミュニティを一緒に盛り上げてくださる方を募集しています。

よろしければ Conpass からメンバー登録よろしくお願いいたします。

https://blessingsoftware.connpass.com/

Discussion