LoginSignup
0
1

More than 1 year has passed since last update.

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

Posted at

前提

  • 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へのリレーション先のデータが取得できない。。。」
  • デバッグ等でソースコードを見直して原因を突き止める

対応策

結論

  • 公式ドキュメントにこんな書き方ができるとの記述を発見

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

なるほど。

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

  • 第2引数:外部キー(=Foreign Key)のカラム名を指定
  • 第3引数:自身のPrimary Keyのカラム名を指定

ということをしている。

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

Orderモデル
  • 第2引数:User/GuestUserはポリモーフィックであるため、外部キーはuser_idではなくorderable_id
  • 第3引数:リレーションを構築するための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のドキュメントのいいところは、こういった実務的な使い方までちゃんとドキュメントに記載されている点は非常にありがたいなという風に思いました。(非公式ではありますが、、、)
0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1