Help us understand the problem. What is going on with this article?

Laravel5.7: postsとusersを関連させる

More than 1 year has passed since last update.

記事のテーブルにユーザーのIDを外部キーとして追加することで、記事の著者が表示されるようにします。
001.png

親記事

Laravel 5.7で基本的なCRUDを作る - Qiita

モデルにリレーションを追加する

:link: readouble.com: リレーション

モデルにメソッドを追加します。
それぞれのメソッド名の単数形と複数形に注意してください。

なおUserモデルでは、記事を新しい順で取得するためにlatest()を使っています。
:link: readouble.com: latest/oldest

app/User.php
    /**
     * リレーション (1対多の関係)
     *
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
     */
    public function posts() // 複数形
    {
        // 記事を新しい順で取得する
        return $this->hasMany('App\Post')->latest();
    }
app/Post.php
    /**
     * リレーション (従属の関係)
     *
     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
     */
    public function user() // 単数形
    {
        return $this->belongsTo('App\User');
    }

なお、戻り値の型はデバッグバーの\Debugbar::info()で調べました。

postsのマイグレーションに外部キーを追加する

:link: readouble.com: 外部キー制約

外部キー用のuser_idカラムをpostsテーブルに追加します。
その際、usersテーブルでレコードが削除されたら、postsテーブルで関連しているレコードも削除するようにします。
つまり、会員が削除されたらその人の記事も削除するということです。

また、user_idのデフォルト値を1にしています。
こうすると、新たに作る記事の著者がID1番のユーザーに固定されてしまいます。
後で実装するログイン機能によって、著者のIDが正しく保存されるように変更します。

database/migrations/2017_05_22_041557_create_posts_table.php
     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とします。
:link: Faker

database/seeds/PostsTableSeeder.php
     DB::table('posts')->insert([
         (中略)
+        'user_id' => $faker->numberBetween(1, 20),
     ]);

テーブル構造が変わってしまったので、マイグレーションとシーディングをやり直すのを忘れないでください。

PowerShell
> php artisan migrate:refresh --seed

postsのビューを修正する

001.png

{{ $post->user->name }}のようにして記事の投稿者の情報を表示できます。
なお、コントローラは何も修正しません。

resources/views/posts/index.blade.php
         <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>
resources/views/posts/show.blade.php
 <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のビューを修正する

002.png

{{ $post->title }}のようにしてユーザーが投稿した記事を表示できます。
ユーザーのshowページで、その人が書いた記事を新しい順に表示することにします。
下記を丸ごと、Bootstrapの.containerの終了タグの直前に追加してください。

resources/views/users/show.blade.php
    {{-- ユーザーの記事一覧 --}}
    <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コントローラに追記が必要です。

app/Http/Controllers/UserController.php
     public function show(User $user)
     {
+        // そのユーザーが投稿した記事のうち、最新5件を取得
+        $user->posts = $user->posts()->paginate(5);
         return view('users.show', ['user' => $user]);
     }

(補足) SQLiteで外部キー制約を有効にする

※ データベースにSQLiteを使わない場合は、この項目は読み飛ばしてください。
この項目はLaravel5.4以来手を入れておらず、古い情報です。

:link: SQLiteで外部キー制約を使えるようにする - ..たれろぐ.. hatena bookmark
:link: SQLiteで外部キー制約(Foreign Key)が効かない時 – ララ帳 hatena bookmark

SQLiteでは、カラムにonDelete('cascade')を追加しただけではユーザーを削除してもそのユーザーが投稿した記事を自動で削除してはくれません。
DBに接続するたびにPRAGMA foreign_keys = ON;を実行しなければなりません。
これをLaravelで行うには、app/Providers/AppServiceProvider.phpboot()に下記を追加します。

app/Providers/AppServiceProvider.php
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 tinkervar_dump()\DB::connection() instanceof \Illuminate\Database\SQLiteConnectionを確認してもtrueが帰ってきます。
なのにIF文の中身が実行されません。
その後、いろいろいじっていると動くようになったのですが、なぜ解決したのかが分かりません。
私の他にも同じ悩みを抱える人がいました。
なので、最初に挙げたIF条件を使うことにしました。

sutara79
宮崎 雄策。1979年生まれ。PHP(Laravel)、 JavaScript(jQuery)、英語を勉強中の文系Webプログラマ。
https://ja.gravatar.com/sutara79
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした