前提
- 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のドキュメントのいいところは、こういった実務的な使い方までちゃんとドキュメントに記載されている点は非常にありがたいなという風に思いました。(非公式ではありますが、、、)