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

記事のテーブルにユーザーのIDを外部キーとして追加することで、記事の著者が表示されるようにします。
![001.png](https://qiita-image-store.s3.amazonaws.com/0/27816/d32b19be-60af-2b3e-5f7f-5bbbcb0b29e8.png)

# 親記事
[Laravel 5.7で基本的なCRUDを作る - Qiita](https://qiita.com/sutara79/items/ef30fcdfb7afcb2188ea)


# モデルにリレーションを追加する
:link: readouble.com: [リレーション](https://readouble.com/laravel/5.7/ja/eloquent-relationships.html)

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

なおUserモデルでは、記事を新しい順で取得するために`latest()`を使っています。
:link: readouble.com: [latest／oldest](https://readouble.com/laravel/5.7/ja/queries.html#ordering-grouping-limit-and-offset)

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

```php:app/Post.php
    /**
     * リレーション (従属の関係)
     *
     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
     */
    public function user() // 単数形
    {
        return $this->belongsTo('App\User');
    }
```

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


# postsのマイグレーションに外部キーを追加する
:link: readouble.com: [外部キー制約](https://readouble.com/laravel/5.7/ja/migrations.html#foreign-key-constraints)

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

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

```diff: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](https://github.com/fzaninotto/Faker#fakerproviderbase)

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

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

```text:PowerShell
> php artisan migrate:refresh --seed
```

# postsのビューを修正する
![001.png](https://qiita-image-store.s3.amazonaws.com/0/27816/d32b19be-60af-2b3e-5f7f-5bbbcb0b29e8.png)

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

```diff: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>
```

```diff: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](https://qiita-image-store.s3.amazonaws.com/0/27816/29dcce79-b95e-0c54-8b8b-322906f4b38c.png)

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

```html: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コントローラに追記が必要です。

```diff: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で外部キー制約を使えるようにする - ..たれろぐ..](http://d.hatena.ne.jp/naga_sawa/20101219/1292774083) [![hatena bookmark](http://b.hatena.ne.jp/entry/image/http://d.hatena.ne.jp/naga_sawa/20101219/1292774083)](http://b.hatena.ne.jp/entry/http://d.hatena.ne.jp/naga_sawa/20101219/1292774083)
:link: [SQLiteで外部キー制約(Foreign Key)が効かない時 &#8211; ララ帳](https://laravel10.wordpress.com/2015/02/24/sqlite-%E3%81%A7%E5%A4%96%E9%83%A8%E3%82%AD%E3%83%BC%E5%88%B6%E7%B4%84foreign-key%E3%81%8C%E5%8A%B9%E3%81%8B%E3%81%AA%E3%81%84%E6%99%82/) [![hatena bookmark](http://b.hatena.ne.jp/entry/image/https://laravel10.wordpress.com/2015/02/24/sqlite-%E3%81%A7%E5%A4%96%E9%83%A8%E3%82%AD%E3%83%BC%E5%88%B6%E7%B4%84foreign-key%E3%81%8C%E5%8A%B9%E3%81%8B%E3%81%AA%E3%81%84%E6%99%82/)](http://b.hatena.ne.jp/entry/https://laravel10.wordpress.com/2015/02/24/sqlite-%E3%81%A7%E5%A4%96%E9%83%A8%E3%82%AD%E3%83%BC%E5%88%B6%E7%B4%84foreign-key%E3%81%8C%E5%8A%B9%E3%81%8B%E3%81%AA%E3%81%84%E6%99%82/)

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

```php:app/Providers/AppServiceProvider.php
public function boot()
{
    if (\DB::getDriverName() == 'sqlite') {
        \DB::statement(\DB::raw('PRAGMA foreign_keys=1'));
    }
}
```

ウェブ上の多くの情報では、DBがSQLiteかどうかを判断するために下記のIF条件を使っています。

```php
// 私の環境では正常に分岐しない
if (\DB::connection() instanceof \Illuminate\Database\SQLiteConnection) {
```

これでもいいのですが、私の環境ではなぜか初めは正常に動作しませんでした。
`php artisan tinker`や`var_dump()`で`\DB::connection() instanceof \Illuminate\Database\SQLiteConnection`を確認しても`true`が帰ってきます。
なのにIF文の中身が実行されません。
その後、いろいろいじっていると動くようになったのですが、なぜ解決したのかが分かりません。
私の他にも[同じ悩みを抱える人](https://laracasts.com/discuss/channels/eloquent/laravel-relationship-foreign-key-delete/replies/313077)がいました。
なので、最初に挙げたIF条件を使うことにしました。