Laravel 10 時代に備えよう(Larastan の導入)
前書き
Laravel 10 になると、ユーザー側のファイルには、PHP 言語の「型」が記載されてくることになります。
例)
/**
* Update the specified resource in storage.
*/
public function update(Request $request, string $id): Response
{
//
}
もちろん、だからと言って強制ではないので、我々が書くときは、その型をカットして書く事もできたりしますが、とは言え、そこら中に型が付いていると、我々も型を書くような流れになるだろうと思います😄
きっちり型を定義して不要なバグを防ぐのが目的ではありますが、場合によっては、型を定義したばかりにバグってしまうという事もあり得ます。(本末転倒というか😅)
という事で今回の記事は、「こんな時にバグってしまったりして、Larastan(PHPStan を元にした Laravel 用静的解析ツール) を入れていると、助けられるよ」という話です。
なお、記事のタイトルには、「Larastan の導入」と書かれていますが、インストール方法や設定については、省略しています🙇♂️(検索すると、既に素晴らしい記事が沢山あります)
(以下、動作環境:PHP8.2、Laravel 9.43、Larastan 2.29 )
本題
では、まずはどんな時にバグってしまうか見ていきます。(あくまで1つのシナリオです)
users
テーブルに nickname
という項目を追加します。
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
+ $table->string('nickname');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
}
NOT NULL としています。
続けて、このニックネームを取得するメソッドを User モデルに用意します。
public function getNickname()
{
return $this->nickname;
}
全く意味の無いメソッドですが、そこは気にしないで下さい。
DB にはデータは埋まっているとして、以下のように記述したとします。
Route::get('/', function () {
$user = User::findOrFail(1);
dd($user->getNickname());
});
すると、1番さんのニックネームが表示されます。
ここまでは問題無いですね。
そこで、この段階で Larastan のレベル6以上(執筆時点)で Larastan を実行すると、以下のエラーが出ます。
------ ---------------------------------------------------------------------
Line Models/User.php
------ ---------------------------------------------------------------------
45 Method App\Models\User::getNickname() has no return type specified.
------ ---------------------------------------------------------------------
怒られてしまったので、先程のメソッドに return の型を指定します。
public function getNickname(): string
{
return $this->nickname;
}
これで Larastan のエラーも出なくなります。
ですが、例えば3ヶ月後とかに仕様の変更があり、「ニックネームは非必須でお願い🍷」という要望があり、nickname
を nullable
に設定したとします。
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('nickname')->nullable(); // ← ★ ここの nullable()
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
}
(本来はファイルを分けるべき所ですが、今回はその辺はカットして、先程のファイルを直接編集しています。(分けても以降で見る結果は同じです))
さて、このままで終わらせてしまうと、バグあり状態となります。
例えば、10人に1人の割合でニックネームを入力しない人がいて、nickname
が null
の人がいたりすると、そのユーザーで $user->getNickname() すると、以下の PHP エラーが表示されます。
App\Models\User::getNickname(): Return value must be of type string, null returned
ニックネームありの人の場合は大丈夫だけど、無しの人の場合はエラーが出るという、厄介な話ですね。
もし、この時、Larastan のレベル8以上(執筆時点)で Larastan を実行すると、以下のエラーを吐いてくれます。
------ -------------------------------------------------------------------------------------
Line Models/User.php
------ -------------------------------------------------------------------------------------
47 Method App\Models\User::getNickname() should return string but returns string|null.
------ -------------------------------------------------------------------------------------
と言うことで、先程の return の型を以下のように修正します。
public function getNickname(): ?string
{
return $this->nickname;
}
「?string」は、「string|null」でも大丈夫です。これで PHP と Larastan のエラーは出なくなります。リリース前などに Larastan を実行していれば、上記のバグにも気づける訳ですね。
Larastan は、マイグレーションファイルを見て、nickname
は nullable
という事を認識し、「そこ string だけじゃ無いですよね。null の可能性もありますよね😎」と指摘してくれた訳ですね。Larastan 凄い!
そんな Larastan ですが、あくまで Laravel の後を追っていく形になりますので、Laravel の最新機能に追いついてない事もあります。例えば、執筆時点で言えば、マイグレーションの $table->ulid(); なんかには、まだちゃんと対応していないです。
後書き
PHP の緩い所がまた良さ?の一つではありますが、今後は、型が多い堅苦しい(← 😄)言語に向かっていきそうですね。
間違い等ありましたらコメント下さい。
Discussion