Eloquent のリレーションの使い方

## 大前提

テーブル名やカラム名の命名規則は遵守すること。

* プライマリは `id`
* `他テーブル名_id` は必ず外部キー。

論理名として「なんとかID」があったとしても、`nantoka_key` や `nantoka_code` にし、存在しないテーブルの `naktoka_id` は絶対に作ってはならない。

なお、ここで「外部キー」は必ずしも外部キー制約（FORREIGN KEY）を振るものではない。Eloquet のリレーションを結びつける論理キーを指している。
詳しくは、[Eloquent 前提の MySQL データベース設計](https://qiita.com/mikomey/items/50cceaf1f5e22d8b2b72) を参照のこと。

## リレーション定義の指針

- モデル定義と同時にすべての定義を書く必要はなく、コーディングの都度追加で十分。
- すなわち、hasMany に対応する belongsTo を必ずしもペアで定義する必要性はない。
- 慣れれば定義すべきリレーションは想像つくので、真っ先に定義することを否定はしない。
- 関係を決めるときに、**「１対１か、１対多か」の呪縛** から抜け出すこと、これ大事。
- 最初に考えるのは外部キーを誰が持っているか。相手が持つときはじめて１か多か決める。

| 自分 | | 他テーブル | リレーション定義 |
|-------|-|------|-------------------|
| 👱🔑 | | 🙋 　🙅    | 相手のキーを自分が持っているなら **belongsTo** |
| 👱    | | 🙋🔑🙅    | 自分のキーを一人にだけわたすなら **hasOne** |
| 👱    | | 🙋🔑🙋🔑 | 自分のキーを複数人にわたすなら **hasMany** |
| 🙋   |🔑🔑|🙋     | 中間テーブルでキーを持ち合うなら **belongsToMany** |

　※「人」と書いているが他テーブルは人とは限らない。

- リレーション名は接頭語や接尾語を（あるなら）省き、シンプルな論理名にする。
- 例えば、Project から ProjectGroup へのリレーション名なら、group にする。
- hasOne と belongsTo は単数形、hasMay や belongsToMany は複数形とする。

### 第２引数と第３引数の覚え方

テーブル名やカラム名の命名規則を遵守していれば、基本的なリレーション定義に第２引数以降は不要。

```php
return $this->blongsTo(Company::class);
```

外部キーが **相手のテーブル名(単数形)_id** でないとき、第２引数を使う。よくあるのは、階層や親子関係のため自分自身にリレーションするとき、テーブル名ではなく parent_id を使うことがある。

```php
return $this->belongsTo(self::class, 'parent_id');
```

プライマリキーを id 以外にしてしまうと第３引数の登場となる。第２引数は **相手テーブル名(単数形)_id** で規定通りなのだが、第３引数があるので省略できない。こうなると、どちらが何だかわからないので命名規則は重要なのだった。

```php
return $this->belongsTo(Company::class, 'company_id', 'company_id');
```


## 多対応のリレーションには pluck と flatten

例えば、ユーザーが所属会社を横断したプロジェクトに属していて、**ユーザーとプロジェクトが多対多対応** となる場合で、ログインユーザーと関わりのあるプロジェクトの会社名をすべて取得することを考えてみる。
会社とプロジェクトには直接的な結びつきはなく、間にユーザーを介した関係となっているものとする。

![sample.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/905317/1194e4e1-987e-8c98-863c-2e8fcbd47870.png)

練習用のプロジェクトを実際に作成するには [前記事](https://qiita.com/mikomey/items/ea3f4126962839581a4f) を参照。

各モデルに定義するリレーションは次のようなものになる。

*App\Models\User*
```php
public function company()
{
    return $this->belongsTo(Company::class);
}

public function projects()
{
    return $this->belongsToMany(Project::class, 'user_project');
}
```
*App\Models\Project*
```php
public function users()
{
    return $this->belongsToMany(User::class, 'user_project');
}
```



欲しいのは、**ユーザー ⇒ プロジェクト ⇒ ユーザー ⇒ 会社（⇒ 会社名）** というリレーションだ。

直接リレーションしているモデル同士なら、関係を記述することは難しくない。
実際にこのようなデータベース定義とモデル定義を作成できたなら、tinker で結果を確認してみよう。

*ユーザー ⇒ プロジェクト、 および ユーザー ⇒ 会社*
```php
$user = User::find(1);
$user->projects;           // Project のコレクション
$user->company;            // Company オブジェクト
```

*プロジェクト ⇒ ユーザー*
```php
$project = Project::find(1);
$project->users;           // User のコレクション
```

*会社 ⇒ 会社名*
```php
$company = Company::find(1);
$company->name;            // 文字列
```

では、これらをつなげるにはどうしたらよいだろうか。
**ユーザー ⇒ プロジェクト ⇒ ユーザ** を単純につなげるとエラーになる。

```php
$user = User::find(1);
$user->projects->users;  // エラー
```
これは $user->projects がコレクションだからで、コレクション内の１つずつを取り出さなければ次につながらないからだ。
コレクションから次につなげるには **pluck** を用いる。
```php
$user = User::find(1);
$user->projects->pluck('users');  // User のコレクション？
```
では、そこから会社につながるだろうか？
```php
$user = User::find(1);
$user->projects->pluck('users')->pluck('company'); // ？？？
```
エラーにはならないが、結果は空となる。
tinkerで確認すると pluck('users') は **コレクションのコレクション** となっていたことがわかる。
```php
>>> $user->projects->pluck('users');
=> Illuminate\Support\Collection {#4324
     all: [
       Illuminate\Database\Eloquent\Collection {#4440
         all: [
           App\Models\User {#4386
             id: 2,
```

**flatten** で平坦なコレクションに変換すれば pluck がつながるようになる。

```php
$user = User::find(1);
$user->projects->pluck('users')
    ->flatten()->pluck('company'); // Company のコレクション
```

ということで、会社名文字列のコレクションを得るのは次で正解。
リレーション名の命名規則（複数形と単数形）がとっても大事だった。

```php
$user = User::find(1);
$names = $user->projects->pluck('users')
    ->flatten()->pluck('company')
    ->pluck('name')->unique();
```

だが、ここまでの知識でリレーションを駆使すると **「Ｎ＋１問題」** が発生し、大量の SQL クエリを発行してしまい、「Eloquent は遅い！ ビルダーで生クエリを書くほうが良いのだ」という主張が出てきてしまう。

「Ｎ＋１問題」とは、ループの中で SQL クエリ発行をくり返すこと。長くなるので<a href="https://qiita.com/mikomey/items/9f06fb3c5558acf6827b">次の記事</a>に。