📰

Laravel 8.42 で加わった withExists() を試す

2021/05/20に公開

前書き

タイトルの通り、Laravel 8.42 で加わったやや地味目な QueriesRelationships の withExists を試してみます。

これはどういう時に使ったりするかと言うと、例えば、管理者がユーザーの一覧を表示し、ブログ記事を投稿した事があるユーザーと無いユーザーを区別する際に役立ちます。

という事でこの記事では、ユーザーの一覧を表示し、まだブログ記事を作成してない人に「催促する」ボタンを表示する所を見て行きます。

ちなみに、以前からある withCount() を使っても実現できたりしますが、存在チェックするだけなので、カウントするよりも効率が良い、というのが売りです。
またもちろん、いわゆる N+1 問題も解消しています。

参考1:GitHub Add withExists method to QueriesRelationships
参考2:本家ドキュメント Other Aggregate Functions (詳しい説明は無し)

データの準備

以下のコマンドで、ブログ用のモデル、マイグレーション、ファクトリファイルを作ります。

php artisan make:model Post -mf

マイグレーションは、以下のように編集します。(titleとか今回は使用しませんが、一応)

    public function up()
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->id();
            $table->foreignId('user_id');
            $table->string('title');
            $table->text('body');
            $table->timestamps();
        });
    }

PostFactoryは、以下のような感じで。

    public function definition()
    {
        return [
            'title' => $this->faker->realText(10),
            'body' => $this->faker->realText(30),
        ];
    }

DatabaseSeederは、以下のように変更します。

use App\Models\Post;
use App\Models\User;

    public function run()
    {
        User::factory(10)->create()->each(function ($user) {
            Post::factory(random_int(0, 3))->create(['user_id' => $user]);
        });
    }

上記のSeederで、各ユーザーに対して、0~3件のブログ投稿を登録しています。
0件の場合は、まだブログを作成していない、という事ですね。

以下でマイグレーションとシーディングを実行します

php artisan migrate --seed

ファイル編集

Models/User に以下を追加します。

    public function posts()
    {
        return $this->hasMany(Post::class);
    }

web.php を以下のようにします。

<?php

use App\Models\User;
use Illuminate\Support\Facades\Route;

Route::get('/', function () {

    $users = User::withExists('posts')->get();  // ← 今回のポイント1

    return view('welcome', compact('users'));
});

views/welcome.php を以下とします。

<ul>
    @foreach($users as $user)
    <li>
        {{ $user->name }} /

        @if($user->posts_exists)   <!-- 今回のポイント2 -->
            ブログあり
        @else
            <button>催促する</button>
        @endunless
    </li>
    @endforeach
</ul>

これをブラウザでアクセスした時、下記のようなイメージになります。
表示イメージ

リレーションメソッド名_exists(今回の場合、posts_exists) とする事により、そのユーザーがブログを投稿しているか否かが分かります。(true / false で取得)

ちなみに、この一覧表示で発行されるSQLは、以下の1つのみです。

select `users`.*, exists(select * from `posts` where `users`.`id` = `posts`.`user_id`) as `posts_exists` from `users`

雑感

これを使用する機会に恵まれるといいですが、あまり無さそうです…。

おかしな箇所等あったら、コメント下さい。

Discussion