記事のテーブルにユーザーのIDを外部キーとして追加することで、記事の著者が表示されるようにします。
親記事
Laravel 5.7で基本的なCRUDを作る - Qiita
モデルにリレーションを追加する
readouble.com: リレーション
モデルにメソッドを追加します。
それぞれのメソッド名の単数形と複数形に注意してください。
なおUserモデルでは、記事を新しい順で取得するためにlatest()
を使っています。
readouble.com: latest/oldest
/**
* リレーション (1対多の関係)
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function posts() // 複数形
{
// 記事を新しい順で取得する
return $this->hasMany('App\Post')->latest();
}
/**
* リレーション (従属の関係)
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function user() // 単数形
{
return $this->belongsTo('App\User');
}
なお、戻り値の型はデバッグバーの\Debugbar::info()
で調べました。
postsのマイグレーションに外部キーを追加する
readouble.com: 外部キー制約
外部キー用のuser_id
カラムをpostsテーブルに追加します。
その際、usersテーブルでレコードが削除されたら、postsテーブルで関連しているレコードも削除するようにします。
つまり、会員が削除されたらその人の記事も削除するということです。
また、user_idのデフォルト値を1にしています。
こうすると、新たに作る記事の著者がID1番のユーザーに固定されてしまいます。
後で実装するログイン機能によって、著者のIDが正しく保存されるように変更します。
public function up()
{
Schema::create('posts', function (Blueprint $table) {
(中略)
+ $table->integer('user_id')->unsigned()->default(1);
+ $table->foreign('user_id')
+ ->references('id')->on('users')
+ ->onDelete('cascade');
});
}
postsのシーダーにもuser_id
を追加して、著者としてのユーザーIDをランダムに割り当てます。
usersのシーダーでは20人のユーザーを作っているので、数値の範囲は1~20とします。
Faker
DB::table('posts')->insert([
(中略)
+ 'user_id' => $faker->numberBetween(1, 20),
]);
テーブル構造が変わってしまったので、マイグレーションとシーディングをやり直すのを忘れないでください。
> php artisan migrate:refresh --seed
postsのビューを修正する
{{ $post->user->name }}
のようにして記事の投稿者の情報を表示できます。
なお、コントローラは何も修正しません。
<table class="table table-striped">
<thead>
<tr>
+ <th>{{ __('Author') }}</th>
<th>{{ __('Title') }}</th>
(中略)
@foreach ($posts as $post)
<tr>
+ <td>
+ <a href="{{ url('users/' . $post->user->id) }}">
+ {{ $post->user->name }}
+ </a>
+ </td>
<dl class="row">
+ <dt class="col-md-2">{{ __('Author') }}:</dt>
+ <dd class="col-md-10">
+ <a href="{{ url('users/' . $post->user->id) }}">
+ {{ $post->user->name }}
+ </a>
+ </dd>
usersのビューを修正する
{{ $post->title }}
のようにしてユーザーが投稿した記事を表示できます。
ユーザーのshowページで、その人が書いた記事を新しい順に表示することにします。
下記を丸ごと、Bootstrapの.container
の終了タグの直前に追加してください。
{{-- ユーザーの記事一覧 --}}
<h2>{{ __('Posts') }}</h2>
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th>{{ __('Title') }}</th>
<th>{{ __('Body') }}</th>
<th>{{ __('Created') }}</th>
<th>{{ __('Updated') }}</th>
{{-- 記事の編集・削除ボタンのカラム --}}
<th></th>
</tr>
</thead>
<tbody>
@foreach ($user->posts as $post)
<tr>
<td>
<a href="{{ url('posts/' . $post->id) }}">
{{ $post->title }}
</a>
</td>
<td>{{ $post->body }}</td>
<td>{{ $post->created_at }}</td>
<td>{{ $post->updated_at }}</td>
<td nowrap>
<a href="{{ url('posts/' . $post->id . '/edit') }}" class="btn btn-primary">
{{ __('Edit') }}
</a>
@component('components.btn-del')
@slot('table', 'posts')
@slot('id', $post->id)
@endcomponent
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
{{ $user->posts->links() }}
hasManyのページング
上のビューで、ページリンクの{{ $user->posts->links() }}
を動作させるため、Userコントローラに追記が必要です。
public function show(User $user)
{
+ // そのユーザーが投稿した記事のうち、最新5件を取得
+ $user->posts = $user->posts()->paginate(5);
return view('users.show', ['user' => $user]);
}
(補足) SQLiteで外部キー制約を有効にする
※ データベースにSQLiteを使わない場合は、この項目は読み飛ばしてください。
この項目はLaravel5.4以来手を入れておらず、古い情報です。
SQLiteで外部キー制約を使えるようにする - ..たれろぐ..
SQLiteで外部キー制約(Foreign Key)が効かない時 – ララ帳
SQLiteでは、カラムにonDelete('cascade')
を追加しただけではユーザーを削除してもそのユーザーが投稿した記事を自動で削除してはくれません。
DBに接続するたびにPRAGMA foreign_keys = ON;
を実行しなければなりません。
これをLaravelで行うには、app/Providers/AppServiceProvider.php
のboot()
に下記を追加します。
public function boot()
{
if (\DB::getDriverName() == 'sqlite') {
\DB::statement(\DB::raw('PRAGMA foreign_keys=1'));
}
}
ウェブ上の多くの情報では、DBがSQLiteかどうかを判断するために下記のIF条件を使っています。
// 私の環境では正常に分岐しない
if (\DB::connection() instanceof \Illuminate\Database\SQLiteConnection) {
これでもいいのですが、私の環境ではなぜか初めは正常に動作しませんでした。
php artisan tinker
やvar_dump()
で\DB::connection() instanceof \Illuminate\Database\SQLiteConnection
を確認してもtrue
が帰ってきます。
なのにIF文の中身が実行されません。
その後、いろいろいじっていると動くようになったのですが、なぜ解決したのかが分かりません。
私の他にも同じ悩みを抱える人がいました。
なので、最初に挙げたIF条件を使うことにしました。