作る機能
テーブルの編集
postsテーブルにuser_id
というカラムを追加します。
database/migrations/XXXX_XX_XX_XXXXXX_create_posts_table.php
Schema::create('posts', function (Blueprint $table) { $table->id(); $table->string('title'); $table->text('body')->nullable(); $table->boolean('is_public')->default(true)->comment('公開・非公開'); $table->dateTime('published_at')->default(DB::raw('CURRENT_TIMESTAMP'))->comment('公開日'); $table->foreignId('user_id')->constrained(); $table->timestamps(); });
追加したのは8行目です。
$table->id()
で設定している場合はforeignId
を使用します。Laravelの規約通りなら、constrained
に引数は必要ありませんが、関連するテーブルがusersではない場合引数に指定します。
ファクトリーファイルにもuser_idを追加しましょう。
database/factories/PostFactory.php
return [ 'title' => $this->faker->realText(rand(20,50)), 'body' => $this->faker->realText(rand(100,200)), 'is_public' => $this->faker->boolean(90), 'published_at' => $random_date, 'user_id' => $this->faker->numberBetween(1,3), 'created_at' => $random_date, 'updated_at' => $random_date ];
次のコマンドでDBを再構築します。
$ artisan migrate:refresh --seed
モデルの設定
リレーションの設定はモデルで行います。
user_idがあるPostモデルではbelongsTo
を設定します。
app/Models/Post.php
class Post extends Model { // ... public function user() { return $this->belongsTo(User::class); } }
カラム名がリレーション先の「モデル_id」なら上記の通りで問題ありませんが、もしそれ以外のカラム名を関連付ける場合はbelongsTo
の第二引数に指定します。
今回は表示しませんが、Userモデルで設定する場合は複数のPostを持つのでhasMany
になります。
app/Models/User.php
class User extends Model { // ... public function posts() { return $this->hasMany(Post::class); } }
ビューの設定
PostからUserにアクセスできるようになりました。
ビューで表示してみましょう。
ユーザー名を表示したい場合は$post->user->name
のように指定します。
resources/views/back/posts/index.blade.php
<thead> <tr> // ... <th scope="col">編集者</th> </tr> </thead> <tbody> @foreach($posts as $post) <tr> // ... <td>{{ $post->user->name }}</td> </tr> @endforeach
編集画面にも表示してみましょう。
resources/views/back/posts/edit.blade.php
<table class="table"> <tr> <th>編集者</th> <td>{{ $post->user->name }}</td> </tr> <tr> <th>登録日時</th> <td>{{ $post->created_at }}</td> </tr> <tr> <th>編集日時</th> <td>{{ $post->updated_at }}</td> </tr> </table>
N+1問題
モデルを関連付けすることは簡単にできることがわかったと思います。
ただこのままだと問題があります。
一覧ページのクエリを確認するとレコードの数だけクエリが発行されているのがわかると思います。
クエリは必要になったときに発行される仕組みなので、このような問題が起こります。
この問題を解消するにはEager Loading
という機能を使います。
コントローラーを次のように編集してください。
app/Http/Controllers/Back/PostController.php
$posts = Post::with('user')->latest('id')->paginate(20);
再度一覧ページを表示するとクエリの数が少なくなっているのがわかると思います。
savingイベントでuser_idを保存する
表示することはできたので、今度は保存時にログインユーザーのidをuser_idに保存するという処理をします。
この処理はコントローラーに書いてもいいのですが、Laravelにはさまざまなイベントの間に処理を挟むことができます。
今回はデータの登録と更新時に同じ処理を行いたいのでsaving
というイベントに処理を追加します。
このイベントの書き方もいろいろとあるのですが、今回は単純な処理なのでモデルに直接記述します。
app/Models/Post.php
protected static function boot() { parent::boot(); // 保存時user_idをログインユーザーに設定 self::saving(function($post) { $post->user_id = \Auth::id(); }); }
データの更新をしてuser_idにログインユーザーのidが登録されるか確認してみてください。
シーダーの修正
先ほどのsavingイベントでログイン情報を読み込むような処理をすると、シーダーのコマンド実行時にもイベントが実行されてしまいエラーになります。(コマンド実行時はログイン情報が取れない為)
Event::fakeFor
でイベントを実行しないように修正しましょう。
database/seeders/PostSeeder.php
\Event::fakeFor(function () { Post::factory()->count(50)->create(); });
これで多対一(HasMany)で投稿(Post)とユーザー(User)の関連付けをすることができました。
次回は多対多(ManyToMany)のリレーションでタギング機能を作っていきます。
ソースコードはGitHubに置いてます。
LaravelMiniCMS2020