Laravel with(Eagerロード)の使い方・サンプル付き

Laravelでアプリケーションを開発していて、遅くなってきたなあという時はありませんか?

クエリが大量に実行されている可能性があり、そんな場合はwithメソッドを利用することで高速化することができます。

今回は、Laravelのwithメソッドについて、使い方をサンプル付きでまとめました。
Laravelの高速化に役立ててください。

with(Eagerロード)とは

EloquentORMでは、リレーションにアクセスする際のデータは、アクセスした時に取得するようになっています。

例えば、ユーザーの一覧に、ユーザーが書いた記事のリストに対して処理を行う場合、

@foreach ($users as $user)
    @foreach ($user->posts as $post)
        ...
    @endforeach
@endforeach

以下のような、クエリが実行されることとなります。

  1. ユーザーのリストを取得するクエリ
  2. ユーザーのリスト数 × ユーザーの記事のリストを取得するクエリ

このようなn+1問題を解決する手段として、withメソッドにより、事前にまとめて読み込んでおくことができます。

では次に、サンプルを交えながら、使い方をまとめました。

サンプルのデータベース

laravel with DB

ユーザーは複数の記事の著者であり、記事に複数のタグが登録されている。
このようなブログやメディアなどの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の高速化に役立ててください。

  
人気Laravelの学習におすすめの本を2選!

この記事と関連している記事