Laravel初心者によるDB取り扱いメモ

環境確認

~$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 22.04.1 LTS
Release: 22.04
Codename: jammy

環境の準備

~$ sudo apt update
~$ sudo apt install zip unzip php-cli php-xml php-curl php-mysql mysql-server mysql-client

MySQLの設定

~$ sudo mysql

DBとDBユーザの作成

mysql> create database `example-app`;
mysql> create user 'example-app' identified by 'example-app';
mysql> GRANT ALL ON `example-app`.* TO 'example-app';

composerのインストール

~$ curl -sS https://getcomposer.org/installer | php
~$ sudo mv composer.phar /usr/local/bin/composer

Laravelプロジェクトの作成

~$ cd
~$ composer create-project laravel/laravel example-app
~$ cd example-app

Laravelバージョン確認

~/example-app$ php artisan --version
Laravel Framework 10.12.0

プロジェクトの環境設定

DB接続情報を設定

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=example-app
DB_USERNAME=example-app
DB_PASSWORD=example-app

ロケール設定

    /*
    |--------------------------------------------------------------------------
    | Application Timezone
    |--------------------------------------------------------------------------
    |
    | Here you may specify the default timezone for your application, which
    | will be used by the PHP date and date-time functions. We have gone
    | ahead and set this to a sensible default for you out of the box.
    |
    */

    'timezone' => 'Asia/Tokyo',

    /*
    |--------------------------------------------------------------------------
    | Application Locale Configuration
    |--------------------------------------------------------------------------
    |
    | The application locale determines the default locale that will be used
    | by the translation service provider. You are free to set this value
    | to any of the locales which will be supported by the application.
    |
    */

    'locale' => 'ja',

    /*
    |--------------------------------------------------------------------------
    | Application Fallback Locale
    |--------------------------------------------------------------------------
    |
    | The fallback locale determines the locale to use when the current one
    | is not available. You may change the value to correspond to any of
    | the language folders that are provided through your application.
    |
    */

    'fallback_locale' => 'en',

    /*
    |--------------------------------------------------------------------------
    | Faker Locale
    |--------------------------------------------------------------------------
    |
    | This locale will be used by the Faker PHP library when generating fake
    | data for your database seeds. For example, this will be used to get
    | localized telephone numbers, street address information and more.
    |
    */

    'faker_locale' => 'ja_JP',

DBテーブルの準備

postsテーブル

~/example-app$ php artisan make:migration create_posts_table

   INFO  Migration [database/migrations/2023_05_24_085628_create_posts_table.php] created successfully.

<?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('posts', function (Blueprint $table) {
            $table->id();
            $table->foreignId('user_id');
            $table->string('title', 50);
            $table->text('body');
            $table->timestamps();
            $table->index(['user_id']);
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('posts');
    }
};

commentsテーブル

~/example-app$ php artisan make:migration create_comments_table

   INFO  Migration [database/migrations/2023_05_24_085634_create_comments_table.php] created successfully.
<?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('comments', function (Blueprint $table) {
            $table->id();
            $table->foreignId('post_id');
            $table->string('name', 50);
            $table->text('body');
            $table->timestamps();
            $table->index(['post_id']);
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('comments');
    }
};

DBへの変更実施

~/example-app$ php artisan migrate

DB確認

~/example-app$ php artisan db
mysql> show tables;
+------------------------+
| Tables_in_example-app  |
+------------------------+
| comments               |
| failed_jobs            |
| migrations             |
| password_reset_tokens  |
| personal_access_tokens |
| posts                  |
| users                  |
+------------------------+
7 rows in set (0.00 sec)

mysql> show create table users\G
*************************** 1. row ***************************
       Table: users
Create Table: CREATE TABLE `users` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `email` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `email_verified_at` timestamp NULL DEFAULT NULL,
  `password` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `remember_token` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `created_at` timestamp NULL DEFAULT NULL,
  `updated_at` timestamp NULL DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `users_email_unique` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
1 row in set (0.01 sec)

mysql> show create table posts\G
*************************** 1. row ***************************
       Table: posts
Create Table: CREATE TABLE `posts` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT,
  `user_id` bigint unsigned NOT NULL,
  `title` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL,
  `body` text COLLATE utf8mb4_unicode_ci NOT NULL,
  `created_at` timestamp NULL DEFAULT NULL,
  `updated_at` timestamp NULL DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `posts_user_id_index` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
1 row in set (0.00 sec)

mysql> show create table comments\G
*************************** 1. row ***************************
       Table: comments
Create Table: CREATE TABLE `comments` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT,
  `post_id` bigint unsigned NOT NULL,
  `name` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL,
  `body` text COLLATE utf8mb4_unicode_ci NOT NULL,
  `created_at` timestamp NULL DEFAULT NULL,
  `updated_at` timestamp NULL DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `comments_post_id_index` (`post_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
1 row in set (0.00 sec)

Modelの準備

usersテーブル

<?php

namespace App\Models;

// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable
{
    use HasApiTokens, HasFactory, Notifiable;

    /**
     * The attributes that are mass assignable.
     *
     * @var array<int, string>
     */
    protected $fillable = [
        'name',
        'email',
        'password',
    ];

    /**
     * The attributes that should be hidden for serialization.
     *
     * @var array<int, string>
     */
    protected $hidden = [
        'password',
        'remember_token',
    ];

    /**
     * The attributes that should be cast.
     *
     * @var array<string, string>
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
        'password' => 'hashed',
    ];

    /**
     * postsテーブルのリレーション設定
     */
    public function posts()
    {
        return $this->hasMany(Post::class);
    }
}

※もともとあるものを流用

postsテーブル

~/example-app$ php artisan make:model Post

   INFO  Model [app/Models/Post.php] created successfully.
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    use HasFactory;

    protected $fillable = [
        'user_id',
        'subject',
        'body',
    ];

    public function user()
    {
        return $this->belongsTo(User::class);
    }

    public function comments()
    {
        return $this->hasMany(Comment::class);
    }
}

commentsテーブル

~/example-app$ php artisan make:model Comment

   INFO  Model [app/Models/Comment.php] created successfully.
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Comment extends Model
{
    use HasFactory;

    protected $fillable = [
        'post_id',
        'name',
        'body',
    ];

    public function post()
    {
        return $this->belongsTo(Post::class);
    }
}

テストデータ作成

<?php

namespace Database\Factories;

use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;

/**
 * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User>
 */
class UserFactory extends Factory
{
    /**
     * Define the model's default state.
     *
     * @return array<string, mixed>
     */
    public function definition(): array
    {
        return [
            'name' => fake()->name(),
            'email' => fake()->unique()->safeEmail(),
            'email_verified_at' => now(),
            'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
            'remember_token' => Str::random(10),
        ];
    }

    /**
     * Indicate that the model's email address should be unverified.
     */
    public function unverified(): static
    {
        return $this->state(fn (array $attributes) => [
            'email_verified_at' => null,
        ]);
    }
}

usersテーブルはデフォルトで追加されているものをそのまま利用

postsテーブル

~/example-app$ php artisan make:factory PostFactory

   INFO  Factory [database/factories/PostFactory.php] created successfully.
<?php

namespace Database\Factories;

use Illuminate\Database\Eloquent\Factories\Factory;

/**
 * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Post>
 */
class PostFactory extends Factory
{
    /**
     * Define the model's default state.
     *
     * @return array<string, mixed>
     */
    public function definition(): array
    {
        $date = $this->faker->dateTimeBetween('-1year');
        return [
            'user_id' => \App\Models\User::factory(),
            'title' => $this->faker->realText(random_int(10, 50)),
            'body' => $this->faker->realText(random_int(200, 800)),
            'created_at' => $date,
            'updated_at' => $date,
        ];
    }
}

commentsテーブル

~/example-app$ php artisan make:factory CommentFactory

   INFO  Factory [database/factories/CommentFactory.php] created successfully.
<?php

namespace Database\Factories;

use Illuminate\Database\Eloquent\Factories\Factory;

/**
 * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Comment>
 */
class CommentFactory extends Factory
{
    /**
     * Define the model's default state.
     *
     * @return array<string, mixed>
     */
    public function definition(): array
    {
        $date = $this->faker->dateTimeBetween('-1year');
        return [
            'post_id' => \App\Models\Post::factory(),
            'name' => $this->faker->name(),
            'body' => $this->faker->realText(random_int(200, 800)),
            'created_at' => $date,
            'updated_at' => $date,
        ];
    }
}
<?php

namespace Database\Seeders;

// use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     */
    public function run(): void
    {
// シンプルにusersへ10件追加 \App\Models\User::factory(10)->create();

// 1対1の内容にならないようにダミーデータ作成開始

// usersへ10件追加 $users = \App\Models\User::factory(10)->create();
// usersへの関連付けをランダムでpostsへ60件追加 $posts = \App\Models\Post::factory(60)->recycle($users)->create();
// postsへの関連付けをランダムでcommentsへ200件追加 \App\Models\Comment::factory(200)->recycle($posts)->create(); } }

※デフォルトで作成されているものを流用

DBへの挿入を実施

~/example-app$ php artisan db:seed

DBを確認してみる

~/example-app$ php artisan db
mysql> SELECT id,name FROM users;
+----+------------------+
| id | name             |
+----+------------------+
|  1 | 鈴木 里佳        |
|  2 | 加藤 太一        |
|  3 | 笹田 千代        |
|  4 | 村山 あすか      |
|  5 | 中村 七夏        |
|  6 | 斉藤 陽一        |
|  7 | 山口 英樹        |
|  8 | 高橋 修平        |
|  9 | 井上 花子        |
| 10 | 中津川 陽子      |
| 11 | 三宅 裕美子      |
| 12 | 伊藤 英樹        |
| 13 | 小泉 和也        |
| 14 | 藤本 英樹        |
| 15 | 西之園 舞        |
| 16 | 原田 香織        |
| 17 | 吉田 修平        |
| 18 | 廣川 学          |
| 19 | 原田 翼          |
| 20 | 原田 京助        |
+----+------------------+
20 rows in set (0.01 sec)
mysql> SELECT * FROM posts ORDER BY id DESC LIMIT 3\G
*************************** 1. row ***************************
        id: 60
   user_id: 13
     title: てり、誰だれからも、青白い霧きりんどもカムパネルラのうちにもつれているらしくから。
      body: ささぎが、立ってしませんやりしてそれを受けて、すぐたべるには、ちょうざんの書斎しょうどさっきを、どんでちゃんと水のようになることも鷺さぎをした。女の子が言いえましたら眼めをあげたりしてはいっぱいに風にゆっくるとあの人は、夜の軽便鉄道けい、そして気をつきました。ジョバンニ、おかし雁がん「お父さん見え、ぼくたちとわらいち手数てすっかくむいたろう。すぐに立ちあがりながら博士はかせの前のあの人が、眼鏡めがねの方で、硝子ガラスの葉はをさがどれほど、この窓まどの外へでて、あのね、その歌は歌い出そう言いう証拠しょにうつくしどものをきれいいと困こまれて行きそくじらだをおつかったとことを分けているのかな、きれいな緑みどりや、ばったよ。あれ」睡ねむっていし、青白くけむったのです。水もそっちの代かわるいことを祈いのって、まがろう」と言いっぺんにそれはもうあんなはきの女の子供こどもいいようなかった測量旗そくじゃないふりました。する。いいました。ジョバンニが言いいました。ジョバンニは、夜の軽便鉄道線路せんで、あかりを持もっと談はないや)ジョバンニもそっちへ進すすきっと西の方を知って、そこへ行ったよ。むかしい気持きもう頭を下に青年たちの方から下りも見つかれてね、ほんでしょうにしてからないといったねえさまでもすべていながれてるねえ」「あの河原かわるいかのある。もとが、じっとみちを見おろしまいました。うすを見ました。私は一度どに開いてもって、息いきおいのですか」青年は笑わらのすわって、たく早く見える」ジョバンニはびっくりしたらこっちに進すすきっぷの中にざあっちのように立っているまです。水も……」鳥捕とりと歴史れきの音のた。
created_at: 2023-03-11 04:53:59
updated_at: 2023-03-11 04:53:59
*************************** 2. row ***************************
        id: 59
   user_id: 20
     title: 手にも見え、スコップで走る汽車のなかったろう。今晩こんどん流なが。
      body: のお祭まつりに青くなって風が吹ふいて、その苹果りんごうしをかっているだろう」とジョバンニは叫さけびました。ほんとした。「おやすむと、ぼくなっていくほって、すぐに銀ぎんがの水あかりした。「おかの樽たるのでしょに進すすきだけでした。それを巨おおきました。「ああではあんまえはあのさいて誰だれから下りでにすこしの、大きなりましたら、早くあすこしてるんだが。今日はその白い巾きれでも集あついて叫さけびました。(ぼくがいいかと言いいな砂すなわち星が、苹果りんごを見おろしてこの次つぎからな島しました。ジョバンニは、ぎざぎざの図よりは一度どばかりました。「行ってよこの人鳥へ教えて川は二つの小さな林のまん中の三角標さん、いけないんさも出て来てくすよ」青年はとりとりは一度どこへ置おきました。「まあおとなり、三人それが少しの袂たもんだんだんうして島しました。そしてたような帯おびかのから、たいのように殻からからははこんばかりもうそこです。「あら、どんなこと」女の子は、ここで降おりて来た。この岸きしきますとみんなのでわずかにして、半分はんしつに何か忘わすれちがする。。
created_at: 2022-05-29 11:48:45
updated_at: 2022-05-29 11:48:45
*************************** 3. row ***************************
        id: 58
   user_id: 18
     title: い胸むねが冷つめて、われました。ジョバンニは、ガラスの鎖くさんがのお母っかりながらが夜のそとを考。
      body: けっしゃばにすから元気をつかってかけました。「ありません。ごと白服しろの入口の中や川で、カムパネルラが向むこう」と言いっしんくだとジョバンニはお菓子かしやだ。お父さんでした。ジョバンニは、みんなことは、やはりの字を印刷いんとうに思わず窓まどからとちゅうを、一生けんは、こっちを乗のり出して誰だれがみんなその正面しょう。わたれわたりしての海で、小さな停車場ている脂油あぶらの水をわたるのです。この男は、ジョバンニもカムパネルラが地図とを考えていました。ところほどありませんでもかまわないよ光ってどしどうしろくぼんやりして始終しじゅうもの。鉄てつ機きのまっていました。ジョバンニ、ラッコの上着うわぎが、そこらではいっしょに行くんだんだなに変かわけです。とこへ行ってはそっていねえ」「ああ、お父さんの方法ほうの列れつはお菓子かしをそらを見ました。マルソがジョバンニは力強ちかね、きれいな砂すなや、こんながらが、やさして、すっかりましたが、どちらっしょうだわ」青年は自分の望のぞきなオーケストリック辺へんにもって靴くつならん、183-7]とこを指ゆびをたべたからくしからなかったといっして来ました。ジョバンニは立派りっぱいにうかこまって行くような、おっかささぎをすました。ジョバンニが左手の崖がけの上の槍やり思いな緑みどりした。いやだ。あんまりも。
created_at: 2022-07-20 17:30:29
updated_at: 2022-07-20 17:30:29
3 rows in set (0.00 sec)
mysql> SELECT * FROM comments ORDER BY id DESC LIMIT 3\G
*************************** 1. row ***************************
        id: 200
   post_id: 29
      name: 津田 陽子
      body: で、黒いくくりおまえば、その人は見え、スターをうっとまるで遠くの先生は意外いがおもくさんは外へかけず、しばらく机つくなっていました。ごらんのことでも燃もやっていたことをして、岩いわよ。そこらになって、黒い脚あしずかによりももって来るわ。ちゃんとうに川だと思いな河原かわらったなくなったマルソに会あい、そっちからきっとそろえておくへ行ったので、見えまを遠くかたをあるい黒い平たいような気が直なおって見分けられて番号ばんごうせきでできたせっかさんの方へ行ってらしかくひょうばいあかしの前に女たちももっと遠くから、自分もだんだんだんだろうかない。ただきにすが、くっきりが射さしい人たちや町の坂さかを一本あげ、また言いいました。ジョバンニは」はママ]の緑みどりいるときの前を通ったまらな孔あなた方へ急いそよぎ、うつくんだん大きな帽子ぼうしをとりが、どうせきのように済すみきっと青年も立ってきたん、なぜかそうだめでんとあっとどこへ行くとちょうどここへかけたよ」ジョバンニはあのこみ。
created_at: 2022-10-30 03:34:44
updated_at: 2022-10-30 03:34:44
*************************** 2. row ***************************
        id: 199
   post_id: 31
      name: 佐々木 真綾
      body: ら博士はかすからすと、いろの天の川のなかったのでしたんでいるとみんな聞きなりました。尾おにそこの下から来てくすかないいながら通って行くの人もついてしまうよ」カムパネルラが首くびっくりした。「くじら、ほんとうちの幸福こうきな二つ光ってまた、わかによりらして島しまいたのですかなとこへ来たね。この方だわ、もって今朝けさのように眼めはどうの幸福こうけ取とりが、手ばやく三角標さんのはこをもっと立ち上がっきよりは、ばさばさのマジェランダムやらを見ていままでたびびとたたたんでした。青年がみんなの上着うわぎが来る黒い大きな一つずつ集あついてくるみの御前みました。カムパネルラは、どころに集あついたのでしょうあらゆる光でちゃんが狂気きょうどさそりの、と言いえ」「おったような、あたるのでした。もう見えるかだって口笛くちを出してもそれを二つの小さく小さな人たちの電燈まめで見たあちゃん。くじら見て話しまいました。「あの銀河ぎんが、おりなって席せきへ戻もどりいろが、何かせはジョバンニは、青い鋼青はがね。
created_at: 2022-11-05 03:11:09
updated_at: 2022-11-05 03:11:09
*************************** 3. row ***************************
        id: 198
   post_id: 40
      name: 井高 くみ子
      body: 種子たねえ。だから汽車の正面しょうほうせきに、ほんとくをきれでつめたくしはその上のしるのでした空のすきとおりの眼めの中にし、まるならべったり下ったというよう」とジョバンニもカムパネルラはきれでも集あつくや否いながれる鳥よりは、その葉ははいっぱりだし、まわって、そっと置おいがいますのは、また向むこうもので、だんうしろに来ているんだったりしてほんも出て来るらしいセロのような笛ふえのあたしたように小さな停車ているのが水から飛とび出。
created_at: 2022-06-09 09:48:17
updated_at: 2022-06-09 09:48:17
3 rows in set (0.00 sec)

名前はめちゃくちゃいい感じにダミーデータを生成してくれている
文章に関してはダミーデータなら使えると思う

Laravelのコードの組み方でどうSQLが発行されるか確認

せっかくなのでコマンドで実行できるようにしてみる

~/example-app$ php artisan make:command Example

   INFO  Console command [app/Console/Commands/Example.php] created successfully.
<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;

class Example extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'app:example';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Command description';

    /**
     * Execute the console command.
     */
    public function handle()
    {
        \DB::enableQueryLog();
        $this->info('start');
        $name = $this->ask('検索するユーザ名を入力してください。');
        $pat = '%' . addcslashes($name, '%_\\') . '%';

        $this->line("単純に検索");
        $rows = \App\Models\User::where('name', 'LIKE', $pat)->get();
        dump(\DB::getQueryLog());
        \DB::flushQueryLog();

        $this->line("検索条件をネスト");
        $rows = \App\Models\User::where('name', 'LIKE', $pat)->where(function($query) {
            $query->where('email', 'LIKE', '%example.com%')->orWhere('email', 'LIKE', '%@google.org%');
        })->get();
        dump(\DB::getQueryLog());
        \DB::flushQueryLog();

        $this->line("途中まで条件が同じモデルを複製");
        $model_1 = \App\Models\User::where('name', 'LIKE', $pat);
        $model_2 = clone $model_1;
        $rows_1 = $model_1->whereDate('created_at', '<', \Carbon\Carbon::today()->subDay(7))->get();
        $rows_2 = $model_2->whereDate('updated_at', '>', \Carbon\Carbon::today()->subDay(14))->get();
        dump(\DB::getQueryLog());
        \DB::flushQueryLog();

        $this->line("inner join");
        $rows = \App\Models\User::where('name', 'LIKE', $pat)->Join('posts', 'users.id', '=', 'posts.user_id')->get();
        dump(\DB::getQueryLog());
        \DB::flushQueryLog();

        $this->line("left join");
        $rows = \App\Models\User::where('name', 'LIKE', $pat)->leftJoin('posts', 'users.id', '=', 'posts.user_id')->get();
        dump(\DB::getQueryLog());
        \DB::flushQueryLog();

        $this->line("リレーション設定したデータ取得1(動的)");
        $rows = \App\Models\User::where('name', 'LIKE', $pat)->get();
        $rows->each(function($row) {
            $row->posts->each(function($post) {
                $post->comments;
            });
        });
        dump(\DB::getQueryLog());
        \DB::flushQueryLog();

        $this->line("リレーション設定したデータ取得2(Eager Loading)");
        $rows = \App\Models\User::with('posts', 'posts.comments')->where('name', 'LIKE', $pat)->get();
        dump(\DB::getQueryLog());
        \DB::flushQueryLog();

        $this->line("リレーション設定したデータ取得3(Lazy Eager Loading)");
        $rows = \App\Models\User::where('name', 'LIKE', $pat)->get();
        dump(\DB::getQueryLog());
        \DB::flushQueryLog();
        $this->line("何かしらの処理");
        $rows->load('posts', 'posts.comments');
        dump(\DB::getQueryLog());
        \DB::flushQueryLog();

        $this->line("リレーション先のpostsが存在するレコードのみ1");
        \App\Models\User::where('name', 'LIKE', $pat)->whereHas('posts', function($query) {
            $query->whereExists(function ($query) {
                return $query;
            });
        })->get();
        dump(\DB::getQueryLog());
        \DB::flushQueryLog();

        $this->line("リレーション先のpostsが存在するレコードのみ2");
        \App\Models\User::where('name', 'LIKE', $pat)->whereIn('id', \App\Models\Post::query()->select('posts.user_id'))->get();
        dump(\DB::getQueryLog());
        \DB::flushQueryLog();


        $this->line("リレーション先のpostsのリレーション先のcommentsが存在するレコードのみ");
        \App\Models\User::where('name', 'LIKE', $pat)->whereHas('posts.comments', function($query) {
            $query->whereExists(function ($query) {
                return $query;
            });
        })->get();
        dump(\DB::getQueryLog());
        \DB::flushQueryLog();

        $this->line("リレーション先を条件に設定1");
        $title_keyword = '会社';
        \App\Models\User::where('name', 'LIKE', $pat)->whereHas('posts', function($query) use ($title_keyword) {
            $query->where('title', 'LIKE', '%' . addcslashes($title_keyword, '%_\\') . '%');
        })->get();
        dump(\DB::getQueryLog());
        \DB::flushQueryLog();

        $this->line("リレーション先を条件に設定2");
        $title_keyword = '会社';
        \App\Models\User::where('name', 'LIKE', $pat)->whereIn('id', function($query) use ($title_keyword) {
            $query->from('posts')->select('user_id')->where('posts.title', 'LIKE', '%' . addcslashes($title_keyword, '%_\\') . '%');
        })->get();
        dump(\DB::getQueryLog());
        \DB::flushQueryLog();

        $this->line("リレーション先を条件に設定3");
        $title_keyword = '会社';
        \App\Models\User::where('name', 'LIKE', $pat)->whereIn('id', \App\Models\Post::query()->select('user_id')->where('posts.title', 'LIKE', '%' . addcslashes($title_keyword, '%_\\') . '%'))->get();
        dump(\DB::getQueryLog());
        \DB::flushQueryLog();

        return 0;
    }
}

実行

検索結果が存在しないパターン

~/example-app$ php artisan app:example
start

 検索するユーザ名を入力してください。:
 > 山田

単純に検索
array:1 [ // app/Console/Commands/Example.php:35
  0 => array:3 [
    "query" => "select * from `users` where `name` LIKE ?"
    "bindings" => array:1 [
      0 => "%山田%"
    ]
    "time" => 8.31
  ]
]
検索条件をネスト
array:1 [ // app/Console/Commands/Example.php:42
  0 => array:3 [
    "query" => "select * from `users` where `name` LIKE ? and (`email` LIKE ? or `email` LIKE ?)"
    "bindings" => array:3 [
      0 => "%山田%"
      1 => "%example.com%"
      2 => "%@google.org%"
    ]
    "time" => 0.63
  ]
]
途中まで条件が同じモデルを複製
array:2 [ // app/Console/Commands/Example.php:50
  0 => array:3 [
    "query" => "select * from `users` where `name` LIKE ? and date(`created_at`) < ?"
    "bindings" => array:2 [
      0 => "%山田%"
      1 => "2023-05-19"
    ]
    "time" => 0.52
  ]
  1 => array:3 [
    "query" => "select * from `users` where `name` LIKE ? and date(`updated_at`) > ?"
    "bindings" => array:2 [
      0 => "%山田%"
      1 => "2023-05-12"
    ]
    "time" => 0.31
  ]
]
inner join
array:1 [ // app/Console/Commands/Example.php:55
  0 => array:3 [
    "query" => "select * from `users` inner join `posts` on `users`.`id` = `posts`.`user_id` where `name` LIKE ?"
    "bindings" => array:1 [
      0 => "%山田%"
    ]
    "time" => 0.49
  ]
]
left join
array:1 [ // app/Console/Commands/Example.php:60
  0 => array:3 [
    "query" => "select * from `users` left join `posts` on `users`.`id` = `posts`.`user_id` where `name` LIKE ?"
    "bindings" => array:1 [
      0 => "%山田%"
    ]
    "time" => 0.36
  ]
]
リレーション設定したデータ取得1(動的)
array:1 [ // app/Console/Commands/Example.php:70
  0 => array:3 [
    "query" => "select * from `users` where `name` LIKE ?"
    "bindings" => array:1 [
      0 => "%山田%"
    ]
    "time" => 0.42
  ]
]
リレーション設定したデータ取得2(Eager Loading)
array:1 [ // app/Console/Commands/Example.php:75
  0 => array:3 [
    "query" => "select * from `users` where `name` LIKE ?"
    "bindings" => array:1 [
      0 => "%山田%"
    ]
    "time" => 0.29
  ]
]
リレーション設定したデータ取得3(Lazy Eager Loading)
array:1 [ // app/Console/Commands/Example.php:80
  0 => array:3 [
    "query" => "select * from `users` where `name` LIKE ?"
    "bindings" => array:1 [
      0 => "%山田%"
    ]
    "time" => 0.46
  ]
]
何かしらの処理
[] // app/Console/Commands/Example.php:84
リレーション先のpostsが存在するレコードのみ1
array:1 [ // app/Console/Commands/Example.php:93
  0 => array:3 [
    "query" => "select * from `users` where `name` LIKE ? and exists (select * from `posts` where `users`.`id` = `posts`.`user_id` and exists (select *))"
    "bindings" => array:1 [
      0 => "%山田%"
    ]
    "time" => 0.51
  ]
]
リレーション先のpostsが存在するレコードのみ2
array:1 [ // app/Console/Commands/Example.php:98
  0 => array:3 [
    "query" => "select * from `users` where `name` LIKE ? and `id` in (select `posts`.`user_id` from `posts`)"
    "bindings" => array:1 [
      0 => "%山田%"
    ]
    "time" => 0.25
  ]
]
リレーション先のpostsのリレーション先のcommentsが存在するレコードのみ
array:1 [ // app/Console/Commands/Example.php:108
  0 => array:3 [
    "query" => "select * from `users` where `name` LIKE ? and exists (select * from `posts` where `users`.`id` = `posts`.`user_id` and exists (select * from `comments` where `posts`.`id` = `comments`.`post_id` and exists (select *)))"
    "bindings" => array:1 [
      0 => "%山田%"
    ]
    "time" => 0.41
  ]
]
リレーション先を条件に設定1
array:1 [ // app/Console/Commands/Example.php:116
  0 => array:3 [
    "query" => "select * from `users` where `name` LIKE ? and exists (select * from `posts` where `users`.`id` = `posts`.`user_id` and `title` LIKE ?)"
    "bindings" => array:2 [
      0 => "%山田%"
      1 => "%会社%"
    ]
    "time" => 0.3
  ]
]
リレーション先を条件に設定2
array:1 [ // app/Console/Commands/Example.php:124
  0 => array:3 [
    "query" => "select * from `users` where `name` LIKE ? and `id` in (select `user_id` from `posts` where `posts`.`title` LIKE ?)"
    "bindings" => array:2 [
      0 => "%山田%"
      1 => "%会社%"
    ]
    "time" => 0.3
  ]
]
リレーション先を条件に設定3
array:1 [ // app/Console/Commands/Example.php:130
  0 => array:3 [
    "query" => "select * from `users` where `name` LIKE ? and `id` in (select `user_id` from `posts` where `posts`.`title` LIKE ?)"
    "bindings" => array:2 [
      0 => "%山田%"
      1 => "%会社%"
    ]
    "time" => 0.26
  ]
]

検索したユーザが1件存在するパターン

~/example-app$ php artisan app:example
start

 検索するユーザ名を入力してください。:
 > 小泉

単純に検索
array:1 [ // app/Console/Commands/Example.php:35
  0 => array:3 [
    "query" => "select * from `users` where `name` LIKE ?"
    "bindings" => array:1 [
      0 => "%小泉%"
    ]
    "time" => 3.01
  ]
]
検索条件をネスト
array:1 [ // app/Console/Commands/Example.php:42
  0 => array:3 [
    "query" => "select * from `users` where `name` LIKE ? and (`email` LIKE ? or `email` LIKE ?)"
    "bindings" => array:3 [
      0 => "%小泉%"
      1 => "%example.com%"
      2 => "%@google.org%"
    ]
    "time" => 0.57
  ]
]
途中まで条件が同じモデルを複製
array:2 [ // app/Console/Commands/Example.php:50
  0 => array:3 [
    "query" => "select * from `users` where `name` LIKE ? and date(`created_at`) < ?"
    "bindings" => array:2 [
      0 => "%小泉%"
      1 => "2023-05-19"
    ]
    "time" => 0.38
  ]
  1 => array:3 [
    "query" => "select * from `users` where `name` LIKE ? and date(`updated_at`) > ?"
    "bindings" => array:2 [
      0 => "%小泉%"
      1 => "2023-05-12"
    ]
    "time" => 0.3
  ]
]
inner join
array:1 [ // app/Console/Commands/Example.php:55
  0 => array:3 [
    "query" => "select * from `users` inner join `posts` on `users`.`id` = `posts`.`user_id` where `name` LIKE ?"
    "bindings" => array:1 [
      0 => "%小泉%"
    ]
    "time" => 0.94
  ]
]
left join
array:1 [ // app/Console/Commands/Example.php:60
  0 => array:3 [
    "query" => "select * from `users` left join `posts` on `users`.`id` = `posts`.`user_id` where `name` LIKE ?"
    "bindings" => array:1 [
      0 => "%小泉%"
    ]
    "time" => 0.5
  ]
]
リレーション設定したデータ取得1(動的)
array:9 [ // app/Console/Commands/Example.php:70
  0 => array:3 [
    "query" => "select * from `users` where `name` LIKE ?"
    "bindings" => array:1 [
      0 => "%小泉%"
    ]
    "time" => 0.32
  ]
  1 => array:3 [
    "query" => "select * from `posts` where `posts`.`user_id` = ? and `posts`.`user_id` is not null"
    "bindings" => array:1 [
      0 => 13
    ]
    "time" => 0.31
  ]
  2 => array:3 [
    "query" => "select * from `comments` where `comments`.`post_id` = ? and `comments`.`post_id` is not null"
    "bindings" => array:1 [
      0 => 12
    ]
    "time" => 0.35
  ]
  3 => array:3 [
    "query" => "select * from `comments` where `comments`.`post_id` = ? and `comments`.`post_id` is not null"
    "bindings" => array:1 [
      0 => 14
    ]
    "time" => 0.27
  ]
  4 => array:3 [
    "query" => "select * from `comments` where `comments`.`post_id` = ? and `comments`.`post_id` is not null"
    "bindings" => array:1 [
      0 => 34
    ]
    "time" => 0.3
  ]
  5 => array:3 [
    "query" => "select * from `comments` where `comments`.`post_id` = ? and `comments`.`post_id` is not null"
    "bindings" => array:1 [
      0 => 37
    ]
    "time" => 0.44
  ]
  6 => array:3 [
    "query" => "select * from `comments` where `comments`.`post_id` = ? and `comments`.`post_id` is not null"
    "bindings" => array:1 [
      0 => 47
    ]
    "time" => 0.33
  ]
  7 => array:3 [
    "query" => "select * from `comments` where `comments`.`post_id` = ? and `comments`.`post_id` is not null"
    "bindings" => array:1 [
      0 => 55
    ]
    "time" => 0.35
  ]
  8 => array:3 [
    "query" => "select * from `comments` where `comments`.`post_id` = ? and `comments`.`post_id` is not null"
    "bindings" => array:1 [
      0 => 60
    ]
    "time" => 0.26
  ]
]
リレーション設定したデータ取得2(Eager Loading)
array:3 [ // app/Console/Commands/Example.php:75
  0 => array:3 [
    "query" => "select * from `users` where `name` LIKE ?"
    "bindings" => array:1 [
      0 => "%小泉%"
    ]
    "time" => 0.44
  ]
  1 => array:3 [
    "query" => "select * from `posts` where `posts`.`user_id` in (13)"
    "bindings" => []
    "time" => 0.33
  ]
  2 => array:3 [
    "query" => "select * from `comments` where `comments`.`post_id` in (12, 14, 34, 37, 47, 55, 60)"
    "bindings" => []
    "time" => 0.41
  ]
]
リレーション設定したデータ取得3(Lazy Eager Loading)
array:1 [ // app/Console/Commands/Example.php:80
  0 => array:3 [
    "query" => "select * from `users` where `name` LIKE ?"
    "bindings" => array:1 [
      0 => "%小泉%"
    ]
    "time" => 0.48
  ]
]
何かしらの処理
array:2 [ // app/Console/Commands/Example.php:84
  0 => array:3 [
    "query" => "select * from `posts` where `posts`.`user_id` in (13)"
    "bindings" => []
    "time" => 0.26
  ]
  1 => array:3 [
    "query" => "select * from `comments` where `comments`.`post_id` in (12, 14, 34, 37, 47, 55, 60)"
    "bindings" => []
    "time" => 0.33
  ]
]
リレーション先のpostsが存在するレコードのみ1
array:1 [ // app/Console/Commands/Example.php:93
  0 => array:3 [
    "query" => "select * from `users` where `name` LIKE ? and exists (select * from `posts` where `users`.`id` = `posts`.`user_id` and exists (select *))"
    "bindings" => array:1 [
      0 => "%小泉%"
    ]
    "time" => 0.35
  ]
]
リレーション先のpostsが存在するレコードのみ2
array:1 [ // app/Console/Commands/Example.php:98
  0 => array:3 [
    "query" => "select * from `users` where `name` LIKE ? and `id` in (select `posts`.`user_id` from `posts`)"
    "bindings" => array:1 [
      0 => "%小泉%"
    ]
    "time" => 0.3
  ]
]
リレーション先のpostsのリレーション先のcommentsが存在するレコードのみ
array:1 [ // app/Console/Commands/Example.php:108
  0 => array:3 [
    "query" => "select * from `users` where `name` LIKE ? and exists (select * from `posts` where `users`.`id` = `posts`.`user_id` and exists (select * from `comments` where `posts`.`id` = `comments`.`post_id` and exists (select *)))"
    "bindings" => array:1 [
      0 => "%小泉%"
    ]
    "time" => 0.36
  ]
]
リレーション先を条件に設定1
array:1 [ // app/Console/Commands/Example.php:116
  0 => array:3 [
    "query" => "select * from `users` where `name` LIKE ? and exists (select * from `posts` where `users`.`id` = `posts`.`user_id` and `title` LIKE ?)"
    "bindings" => array:2 [
      0 => "%小泉%"
      1 => "%会社%"
    ]
    "time" => 0.28
  ]
]
リレーション先を条件に設定2
array:1 [ // app/Console/Commands/Example.php:124
  0 => array:3 [
    "query" => "select * from `users` where `name` LIKE ? and `id` in (select `user_id` from `posts` where `posts`.`title` LIKE ?)"
    "bindings" => array:2 [
      0 => "%小泉%"
      1 => "%会社%"
    ]
    "time" => 0.27
  ]
]
リレーション先を条件に設定3
array:1 [ // app/Console/Commands/Example.php:130
  0 => array:3 [
    "query" => "select * from `users` where `name` LIKE ? and `id` in (select `user_id` from `posts` where `posts`.`title` LIKE ?)"
    "bindings" => array:2 [
      0 => "%小泉%"
      1 => "%会社%"
    ]
    "time" => 0.29
  ]
]

動的なリレーション先データアクセスはクエリ発行数がやばい
Eager Loadingはとても便利

イメージした通りのSQLをすぐに発行できるよう精進したい


コメント