Laravelでアプリケーションを開発していて、遅くなってきたなあという時はありませんか?
クエリが大量に実行されている可能性があり、そんな場合はwith
メソッドを利用することで高速化することができます。
今回は、Laravelのwith
メソッドについて、使い方をサンプル付きでまとめました。
Laravelの高速化に役立ててください。
目次
with(Eagerロード)とは
EloquentORMでは、リレーションにアクセスする際のデータは、アクセスした時に取得するようになっています。
例えば、ユーザーの一覧に、ユーザーが書いた記事のリストに対して処理を行う場合、
@foreach ($users as $user)
@foreach ($user->posts as $post)
...
@endforeach
@endforeach
以下のような、クエリが実行されることとなります。
- ユーザーのリストを取得するクエリ
- ユーザーのリスト数 × ユーザーの記事のリストを取得するクエリ
このようなn+1問題
を解決する手段として、withメソッドにより、事前にまとめて読み込んでおくことができます。
では次に、サンプルを交えながら、使い方をまとめました。
サンプルのデータベース
ユーザーは複数の記事の著者であり、記事に複数のタグが登録されている。
このようなブログやメディアなどのCMSによくあるデータベースを例とします。
基本的な使い方
User::with(['posts'])->get();
基本的な使い方は、with
にモデルのリレーションを指定するのみです。
例として、上記の画像のように、
ユーザーの一覧・ユーザーの記事の一覧を表示する場合を考えてみます。
コントローラーで、ユーザーのリストを取得し、
$user = User::all();
ビューをリストを表示します。
@foreach ($users as $user)
<div class="mb-5">
<h2 class="h5 mb-3">
{{ $user->name }}さんが書いた記事
</h2>
<ul class="list-group">
@foreach ($user->posts as $post)
<li class="list-group-item">
{{ $post->title }}
</li>
@endforeach
</ul>
</div>
@endforeach
この場合、実行されるクエリは、以下のように4回実行されています。
そして、ユーザーが増える毎にクエリの実行回数も増えていきます。
select * from `users`
select * from `posts` where `posts`.`user_id` = 1 and `posts`.`user_id` is not null
select * from `posts` where `posts`.`user_id` = 2 and `posts`.`user_id` is not null
select * from `posts` where `posts`.`user_id` = 3 and `posts`.`user_id` is not null
では、with
を利用してユーザーのリストを取得時に、記事の情報も読み込むようにします。
コントローラーで、ユーザーのリストを取得している処理にwith
を使います。
$user = User::with(['posts'])->get();
とすると、実行されるクエリは、下記のように2回のみになります。
こちらは、ユーザーが増えても、実行されるクエリの回数が増えることはありません。
select * from `users`
select * from `posts` where `posts`.`user_id` in (1, 2, 3)
複数のリレーションをロードする
画像のように、ユーザー名の横に、ユーザーが所属する組織情報を追加する場合を考えてみます。
User::with(['posts', 'organization'])->get();
with
に、読み込みたいリレーションを配列で指定して、複数のリレーションを読み込むことができます。
ネストしたリレーションをロードする
画像のように、記事のタグを表示する場合は、タグの情報も先に読み込みたいものです。
User::with([
'posts',
'posts.tags',
'organization'
])->get();
この場合は、ドット記法で、ネストしたリレーションの情報も読み込むことができます。
リレーションに条件をつけてロードする
ユーザーの記事の一覧を表示していますが、下書きの記事が含まれてしまっています。
これを公開中の記事だけにしてみます。
User::with([
'posts' => function ($query) {
$query->where('is_published', 1);
},
'posts.tags',
'organization'
])->get();
このようにクエリを追加することができます。
または、User.php
にて、公開記事用のリレーションを定義して、
public function publishedPosts()
{
return $this->hasMany('App\Post')->where('is_published', 1);
}
ユーザー取得時に、以下のように指定することもできます。
User::with([
'publishedPosts',
'publishedPosts.tags',
'organization'
])->get();
特定のカラムだけ取得する
今までの例では、記事の一覧を表示していますが、記事の本文は表示していません。
記事の本文は長いテキストになることもあり、クエリを実行する時に、本文のカラムを取得しないようにしたいこともよくあります。
そのような場合には、:
のあとに必要なカラムを指定します。
※ ,
で区切る時にスペースを空けないように注意してください。
User::with([
'publishedPosts:id,title,user_id',
'publishedPosts.tags',
'organization'
])->get()
user_id
は、表示には使っていませんが、どのユーザーの記事か判別するために必要になります。
ソートする
記事のリストが、公開日時の降順になるように設定してみます。
先ほどの「リレーションに条件つける」と同じように、ソートのクエリを追加することができます。
User::with([
'publishedPosts' => function ($query) {
$query->orderBy('published_at', 'desc');
},
'publishedPosts.tags',
'organization'
])->get();
もしくは、モデルでリレーションを設定した時でも、ソートを設定することが可能です。
public function publishedPosts()
{
return $this
->hasMany('App\Post')
->where('is_published', 1)
->orderBy('published_at', 'desc');
}
さいごに
with
メソッドの利用方法は以上です。Laravelの高速化に役立ててください。
コメントを残す