Laravel Eloquent リレーションは怖くない

# はじめに

[以前に書いた記事](https://qiita.com/YuK1Game/items/406791b699774d15d11d)がいくつかLGMTいただいていたので、
こちらに現在自分の知識での最善のリレーションを記述しておきます。

# 親子が1対1の関係

## テーブル構成

### [親] `users` テーブル

色々な子テーブルを持っています。
もちろん、次に登場する `user_ages` テーブルもです。

| id |  name   |
|---:|:--------|
|   1| 田中太郎 |
|   2| 山田花子 |
|   3| 佐藤一郎 |

### [子] `user_ages` テーブル

ユーザーの年齢を管理するテーブル。
`users` テーブルとのレコードは1対1の関係。

| id | user_id | age |
|---:|--------:|----:|
|   1|       1 |  23 |
|   2|       2 |  34 |
|   3|       3 |  55 |

## Model側の実装

### 親(idを持つ)から子(user_idを持つ)を参照するには、 `hasOne` を使います。

``` App\Models\User.php
use App\Models\UserAge;

public function age() {
    return $this->hasOne(UserAge::class);
}
```

### 子(user_idを持つ)から親(idを持つ)を参照するには、 `belongsTo` を使います。

``` App\Models\UserAge.php
use App\Models\User;

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

準備しておけば下記のように記述できます。

```.php
use App\Models\{ User, UserAge };

$user = User::find(1);
echo $user->age->age; // 23

$age = UserAge::find(3);
echo $age->user->name; // 佐藤一郎
```

# 親子が1対多の関係

## テーブル構成

### [親] `users` テーブル

再登場した `users` テーブル。
次は1ユーザーが複数の `本` を持つことができる、 `user_books` テーブルを参照します。

| id |  name   |
|---:|:--------|
|   1| 田中太郎 |
|   2| 山田花子 |
|   3| 佐藤一郎 |

### [子] `user_books` テーブル

ユーザーの持っている本のタイトルを管理するテーブルです。
`エルマーのぼうけん シリーズ` が人気のようです。
同じ `user_id` のレコードが複数行できる可能性があります。


| id | user_id | name                   |
|---:|--------:|:-----------------------|
|   1|       1 |  エルマーのぼうけん      |
|   2|       1 |  週刊少年ジャンプ+       |
|   3|       3 |  エルマーのぼうけん      |
|   4|       3 |  エルマーとりゅう        |
|   5|       3 |  エルマーと16ぴきのりゅう |

## Model側の実装

### 親(idを持つ)から複数の子(user_idを持つ)を参照するには、 `hasMany` を使います。

``` App\Models\User.php
use App\Models\UserBook;

public function books() {
    return $this->hasMany(UserBook::class);
}
```

### 子(user_idを持つ)から親(idを持つ)を参照するには、先ほどと同じく `belongsTo` を使います。

``` App\Models\UserBook.php
use App\Models\User;

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

準備しておけば下記のように記述できます。

```.php
use App\Models\{ User, UserBook };

// 田中太郎(ID:1)さんの持つ本の一覧を表示してみましょう。
$user = User::find(1);

foreach ($user->books as $book) {
    echo $book->name . PHP_EOL;
}
// エルマーのぼうけん
// 週刊少年ジャンプ+

// エルマーとりゅう(ID:4)は誰の持ち物か表示してみましょう。
$book = UserBook::find(4);
echo $book->user->name; // 佐藤一郎
```

# 多対多の関係

## テーブル構成

### `users` テーブル

先程は「親子」と言っていましたが、多対多の関係では親子の縁は切れました。
なぜなら、どちらも親になる可能性があるからです。

| id |  name   |
|---:|:--------|
|   1| 田中太郎 |
|   2| 山田花子 |
|   3| 佐藤一郎 |

### `books` テーブル

本のタイトルをそのまま持つと、本のタイトルが変わったときに一気にレコードを更新しないといけません。
毎回、複数のレコードを更新するのはめんどくさいですよね？
なら、`本` は `books` テーブルで管理しましょう。

| id | name                   |
|---:|:-----------------------|
|   1| 週刊少年ジャンプ+       |
|   2| おそ松さん             |
|   3| エルマーのぼうけん      |
|   4| エルマーとりゅう        |
|   5| エルマーと16ぴきのりゅう |

### `book_user` テーブル

`ユーザー` と `本` を結びつける中間テーブルです。
なぜ `book_user[s]` じゃないのかって？
Laravelでは中間テーブルの名前は[複数形にしないルール](### 基本的なLaravelにおけるテーブルの命名規則)になっています。

ちなみに、 `users` テーブルと `books` テーブルの中間テーブル名は、
アルファベットの早い方が先頭になります。
したがって、 `book_user` テーブルとなります。


| id | user_id | book_id |
|---:|--------:|--------:|
|   1|       1 |       3 |
|   2|       1 |       1 |
|   3|       3 |       3 |
|   4|       3 |       4 |
|   5|       3 |       5 |

## Model側の実装

### `users` テーブルから `book_user` という中間テーブルを経由して `books` データを取得する場合は、 `belongsToMany` を使用します。

``` App\Models\User.php
use App\Models\Book;

public function books() {
    return $this->belongsToMany(Book::class);
}
```

### `books` テーブルから `book_user` という中間テーブルを経由して `users` データを取得する場合も、 `belongsToMany` を使用します。

``` App\Models\UserBook.php
use App\Models\User;

public function users() {
    return $this->belongsToMany(User::class);
}
```

準備しておけば下記のように記述できます。

```.php
use App\Models\{ User, Book };

// 佐藤一郎(3)さんの持つ本の一覧を表示してみましょう。
$user = User::find(3);

foreach ($user->books as $book) {
    echo $book->name . PHP_EOL;
}
// エルマーのぼうけん
// エルマーとりゅう
// エルマーと16ぴきのりゅう

// エルマーのぼうけん(3)を持っている人一覧を表示してみましょう。
$book = Book::find(3);
foreach ($book->users as $user) {
    echo $user->name . PHP_EOL;
}
// 田中太郎
// 佐藤一郎
```

また、 `山田花子` さんに `おそ松さん` を持たせたい場合は、
下記のように `attach` メソッドを使えば追加できます。

```.php
$user = User::find(2); // 山田花子
$user->books()->attach(2); // おそ松さん(2)を追加します。
```

# その他、覚えておくと良いこと

### 基本的なLaravelにおけるテーブルの命名規則

LaravelでDBを使用する場合のテーブル名は、マスタ・データ問わず基本的に `複数形` になります。
また、中間テーブルのみ `単数形` となります。

### `$user->age` と `$user->age()` の違い

`$user->age` はSQLを実行して、その結果を `App\Models\UserAge` で返します。

`$user->age()` はSQLが実行されず、`Illuminate\Database\Eloquent\Relations\HasOne` のようなリレーションオブジェクトを返します。
下記のように、続けて `HasOne` オブジェクトのメソッドが利用できます。

```.php
if ($user->age()->exists()) {
    // \App\Models\UserAge モデルが存在する
} else {
    // \App\Models\UserAge モデルが存在しない
}
```

### `$user->books()`

`$user->books()`は、
[1対多の関係](#親子が1対多の関係)の場合は `Illuminate\Database\Eloquent\Relations\HasMany`
[多対多の関係](#多対多の関係)の場合は `Illuminate\Database\Eloquent\Relations\BelongsToMany` を返します。

### `Illuminate\Database\Eloquent\Relations\HasMany`

新しくレコードを追加したい場合は、下記のように実装します。

```.php
$user->books()->create(['name' => '週刊少年ジャンプ+']);
```

### `Illuminate\Database\Eloquent\Relations\BelongsToMany`

新しく本を紐付けしたい場合は下記のように実装します。

```.php
$user->books()->attach([1, 2, 3]); // books テーブルのID

// 新しく books.id が 1, 2, 3 のものが追加されます。
```

また、本の紐付けを同期したい場合は下記のように実装します。

```.php
$user->books()->sync([1, 2, 3]); // books テーブルのID

// books.id が 1, 2, 3 のものが追加され、それ以外のidは削除されます。
```

# 最後に

社内用です。
ソースを書きながら記事を書いたわけではないので、
ところどころ動かしてみると間違ってる可能性があるので、
その場合は都度修正します…。