【Laravel】ポリモーフィック（morphTo）を使った際のリレーション構築

## 前提
* windows10 home
* PHP 8.0.7
* Laravel 8.50.0

## 背景
* プロジェクト内で初めてポリモーフィックを使ったリレーションを使用したあ時の話
* 「リレーションは既に何度か使って問題なく扱えているから余裕だ！」と思っていた
* しかし、つまづいた。。。

## 内容
### DBの構成
#### usersテーブル

| カラム名     | 型         | 用途                   | 
| ------------ | ---------- | ---------------------- | 
| id           | int, A_I,  | PK       | 
| name         | string     |                        | 
| 略           | ←         | ←                     | 

#### guest_usersテーブル

| カラム名     | 型         | 用途                   | 
| ------------ | ---------- | ---------------------- | 
| id           | int, A_I,  | PK       | 
| name         | string     |                        | 
| 略           | ←         | ←                     | 

#### ordersテーブル

| カラム名     | 型         | 用途                   | 
| ------------ | ---------- | ---------------------- | 
| id           | int, A_I,  | PK       | 
| orderable_id | int        | FK | 
| name         | string     |                        | 
| 略           | ←         | ←                     | 

#### user_attributesテーブル

| カラム名     | 型         | 用途                   | 
| ------------ | ---------- | ---------------------- | 
| id           | int, A_I,  | PK       | 
| user_id         | string     | FK     | 
| age         | int     |      | 
| 略           | ←         | ←                     | 

### 各DBの関係性
|  親1  |   子1 / 親2  | 子2     | 
| ------------------- | ----- | ------------- | 
| User | Order | UserAttribute | 
| GuestUser           |     |             | 

* OrderとUser/GuestUserの関係は多対1
* User/GuestUserとUserAttributeの関係は1対1関係

### やりたいこと

`User`モデルから`Order`モデルを経由し`UserAttribute`モデルの`age`を取得する

※DBの構造上、Orderを経由する必要あった

### ソースコード
#### モデル

#### トレイト

* ポリモーフィック(User/GuestUser)→Orderへのアクセスを有効にするためにリレーション構築

```app/Models/Concerns/Authenticatable.php

use App\Models\GuestUser;
use App\Models\Order;
use App\Models\User;

trait Authenticatable
{
    // User/GuestUserからOrderへアクセスし、全ての注文を取得. 
    // orderaテーブルとの間にordarableという中間テーブルが生成
    public function orders()
    {
        return $this->morphMany(Order::class, 'orderable');
    }
}
```

##### Userモデル

* Userモデルでトレイトを使用し、ポリモーフィックを構築

```App/Models/User.php
use app/Models/Concerns/Authenticatable;

class User extends Model
{
    // トレイトの使用
    use Authenticatable;
    
    ...
} 
```

##### GuestUserモデル

* GuestUserモデルでもトレイトを使用し、ポリモーフィックを構築

```App/Models/GuestUser.php
use app/Models/Concerns/Authenticatable;

class GuestUser extends Model
{
    // トレイトの使用
    use Authenticatable;
    
    ...
} 
```

##### Orderモデル

* ココでUserAttributeへのアクセスを有効にするためにリレーション構築

```App\Models\Order.php
class Order extends Model
{
    // OrderからUser/GuestUserへアクセスし、ユーザー情報を取得
    public function userAttribute()
    {
        return $this->belongsTo(UserAttribute::class);
    }
```

#### コントローラ

```app\Http\Controllers\OrderController.php
class SoftDeleteController extends Controller
{
    public function index()
    {
        $users = Order::orderable()->get();

        return view('hello.orders', ['users' => $users]);
    }
}
```

#### ビュー

$users をforeachし、`$user->name`でユーザーの名前一覧表示

※割愛

---

そして

> 「Order->User/GuestUser->UserAttributeへのリレーション構築したし、カンペキ！」

と思い、動作確認してみる

### 結果
* 「おや、Order->UserAttributeへのリレーション先のデータが取得できない。。。」
* デバッグ等でソースコードを見直して原因を突き止める

## 対応策
### 結論
* 公式ドキュメントにこんな書き方ができるとの記述を発見

https://readouble.com/laravel/8.x/ja/eloquent-relationships.html#one-to-one-defining-the-inverse-of-the-relationship

```app/Models/Phone.php
public function user()
{
    return $this->belongsTo(User::class, 'foreign_key', 'owner_key');
}
```

なるほど。

各々何しているかというと

* 第２引数：外部キー（=Foreign Key）のカラム名を指定
* 第３引数：自身のPrimary Keyのカラム名を指定

ということをしている。

なので、自分も以下のように`belongsTo`メソッドに**引数を追加**

##### Orderモデル
* 第２引数：`User/GuestUser`はポリモーフィックであるため、外部キーは`user_id`ではなく`orderable_id`
* 第３引数：リレーションを構築するための`Order`モデル内にあるキー（PK）となるのは`user_id`

```App\Models\Order.php
class Order extends Model
{
    // OrderからUser/GuestUserへアクセスし、ユーザー情報を取得
    public function userAttribute()
    {
        return $this->belongsTo(UserAttribute::class, 'orderable_id', 'user_id');
    }
```

無事、Order->UserAttributeのデータ取得ができるようになりました！

### まとめ

ドキュメントを読み解くと今回のエラー発生までの流れはこんな感じ

*  `belongsTo`メソッドのデフォルトの仕様は、リレーション関係先のモデルのカラムから`<リレーション先のテーブル名>_id`を**自動で探しに行き、そのカラムが各モデルに存在**すれば、リレーションが構築される仕様

👇

* 今回はリレーションのキーにしたいカラム名は、User/GuestUserがポリモーフィックを構築していることもあり、Orderモデルにある`orderable_id`になる

👇

* `belongsTo`はデフォルトでは`user_id`をキーに探しに行くが、Orderモデルには存在しないカラムになるので、Order->UserAttributeが構築できなかった

という流れ

つまづいた原因は
* belongsToなどのリレーション構築時にLaravelで自動で何をしているのか知らなかった

要は知識不足。

### 感想

* Laravelのドキュメントのいいところは、こういった実務的な使い方までちゃんとドキュメントに記載されている点は非常にありがたいなという風に思いました。（非公式ではありますが、、、）