LaravelでN + 1を検知した際にログファイルに出力する
はじめに
今回Laravel Query Detector
というパッケージを使ってN+1を検知した際にログファイルに出力してみます。
環境
- PHP 8.2.12
- Laravel 10.29.0
事前準備
今回の検証に使うテーブルやEloquentモデルクラスを事前に作成しておきます。
テーブル作成
デフォルトで用意されているusersテーブルと1対多で紐づくテーブルを作成
database/migrations/2023_10_28_153502_create_tasks_table.php
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('tasks', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('user_id');
$table->string('title');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('tasks');
}
};
Eloquentモデルクラス
app/Models/Task.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Model;
class Task extends Model
{
use HasFactory;
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
}
database/factories/TaskFactory.php
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Task>
*/
class TaskFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'title' => Str::random(10),
];
}
}
デフォルトで用意されているUserモデルにリレーションの設定を追加しておきます。
use Illuminate\Database\Eloquent\Relations\HasMany;
class User extends Authenticatable
{
public function tasks(): HasMany
{
return $this->hasMany(Task::class);
}
}
データ作成
tinker
を使って下記を実行し、usersテーブルと紐づくtasksテーブルのデータを作って起きます。
User::factory()
->has(Task::factory()->count(10))
->count(10)
->create();
N+1が起きるコード
welcomeページのルートに次のコードを仕込んでみます。
Route::get('/', function () {
$tasks = Task::all();
foreach ($tasks as $task) {
$task->user->id;
}
return view('welcome');
});
使い方
インストール
composer require beyondcode/laravel-query-detector --dev
この記事で使うバージョンは1.7.0です。
$ composer show | grep beyondcode/laravel-query-detector
beyondcode/laravel-query-detector 1.7.0 Laravel N+1 Query Detector
インストール後、設定ファイルをコピーしておきます。
php artisan vendor:publish --provider="BeyondCode\QueryDetector\QueryDetectorServiceProvider"
ログファイルに出力する設定
デフォルトでログファイルに出力する設定になっているかもしれないですが、
なっていない場合はコピーした設定ファイル(config/querydetector.php
)の内容を次に書き換えます。
'output' => [
\BeyondCode\QueryDetector\Outputs\Log::class, // <- 追記する
]
また、.envファイルのAPP_DEBUG
にtrue
を設定しておく必要があります。
お試し
事前準備で用意したwelcomeページにブラウザでアクセスすれば
storage/logs/laravel-yyyy-mm-dd.log
ファイルができていると思いますので
中身を確認すると次のような内容が出力していると思います。
[yyyy-mm-dd XX:XX:XX] local.INFO: Detected N+1 Query
[yyyy-mm-dd XX:XX:XX] local.INFO: Model: App\Models\Task
Relation: user
Num-Called: 100
Call-Stack:
〜〜〜 以降はスタックトレースの内容 〜〜〜
まとめ
知らない間にN+1のコードを作成している場合があったりするので、
ログファイルに出力しておけばCIでユニットテストを実行してN+1の場所を探し出せたりするから便利かなって思います。
また、今回は試していないですが、ブラウザのコンソールに出力すれば動作確認中でも見つけることができるので
設定ファイルのoutput
配列に\BeyondCode\QueryDetector\Outputs\Console::class
を追記しておくのもいいかなって思います。
Discussion