AQ Tech Blog

Laravelで実装するポリモーフィックリレーション(Morph)

作成者: naoto.momose|2024年09月05日

はじめに

本記事では、Laravel上でポリモーフィック関連となるデータ構造をEloquentのリレーションでどう実装するか について紹介を行います。

ポリモーフィック関連とは?

1つの子テーブルから複数の親テーブルを参照する関連のことを指します。

具体例

例えば、ブログや商品にコメントを添付する機能を実装する場合、ポリモーフィック関連を利用するのが良いとされています。

テーブル構造は以下のような形になります。

posts
- id : 主キー
- tilte : ブログタイトル
- content : 内容

products
- id : 主キー
- name : 商品名
- discription : 商品説明

comments
- id : 主キー
- content : コメント内容
- commentable_type : 関連レコードのモデル名
- commentable_id : 関連レコードの主キー

この設計では、commentsテーブルcommentable_idcommentable_typeがポリモーフィック関連における外部キーとなり、postsテーブルproductsテーブルのレコードと関連付けられます。

commentable_typeによって、コメントがどのテーブルに属するのかを特定できます。

LaravelでのMigrationの記述方法

Laravelではポリモーフィック関連のサポートが行われているため、簡単にMigrationを記述できます。

例で示したcommentsテーブルのMigration記述例は以下になります。

Schema::create('comments', function (Blueprint $table) {
$table->text('comment')->comment('コメント内容');
$table->morphs('commentable');
});
$table->morphs('〇〇');

こちらの記述により、 ポリモーフィック関連における外部キー用のカラム(〇〇_type,〇〇_id)の作成が一括で行えます。

Laravelでのポリモーフィックのリレーション

Laravelのモデルでリレーションの記述を行うことによって、 子テーブルのモデルから、親テーブルのモデルの取得が行えるようになります。

リレーションの記述、利用方法について紹介を行っていきます。

1対1の場合

親テーブルと子テーブルが1対1である場合のリレーションの記述、利用方式について紹介していきます。

テーブルの構造は以下とします

posts
- id : 主キー
- tilte : ブログタイトル
- content : 内容

products
- id : 主キー
- name : 商品名
- discription : 商品説明

comments
- id : 主キー
- content : コメント内容
- commentable_type : 関連レコードのモデル名
- commentable_id : 関連レコードの主キー

リレーションの記述方法

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
public function comment()
{
return $this->morphOne(Comment::class, 'commentable');
}
}
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Product extends Model
{
public function comment()
{
return $this->morphOne(Comment::class, 'commentable');
}
}
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Comment extends Model
{
public function commentable()
{
return $this->morphTo(__FUNCTION__, 'commentable_type', 'commentable_id');
}
}

リレーションの利用方法

親モデルから子モデルを取得する方法は

use App\Models\Post;

$post = Post::find(1);

$comment = $post->comment;

となります。

こちらにより、親モデル(Postモデル)から、子モデル(commentモデル)を取得することができます。

逆に、子モデルから親モデルを取得する方法は

use App\Models\Comment;

$comment = Comment::find(1);

$value = $comment->commentable;

となります。

こちらにより、子モデル(commentモデル)から親モデル(Postモデル, Productモデル)を取得することができます。

取得されるモデルはcomment.commentable_typeに記述されているモデル名に応じて変化します。

1対多の場合

親テーブルと子テーブルが1対多である場合のリレーションの記述、利用方式について紹介していきます。

1対多の場合でも、1対1と同じようなテーブル構造・リレーションの記述になります。

テーブルの構造は以下とします。

posts
- id : 主キー
- tilte : ブログタイトル
- content : 内容

products
- id : 主キー
- name : 商品名
- discription : 商品説明

comments
- id : 主キー
- content : コメント内容
- commentable_type : 関連レコードのモデル名
- commentable_id : 関連レコードの主キー

リレーションの記述方法

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}
}
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Product extends Model
{
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}
}
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Comment extends Model
{
public function commentable()
{
return $this->morphTo(__FUNCTION__, 'commentable_type', 'commentable_id');
}
}

リレーションの利用方法

親モデルから子モデルを取得する方法は

use App\Models\Post;

$post = Post::find(1);

$comments = $post->comments;

となります。

こちらにより、親モデル(Postモデル)から、紐づくすべての子モデル(commentモデル)を取得することができます。

逆に、子モデルから親モデルを取得する方法は

use App\Models\Comment;

$comment = Comment::find(1);

$value = $comment->commentable;

となります。

こちらにより、子モデル(commentモデル)から親モデル(Postモデル, Productモデル)を取得することができます。

取得されるモデルはcomment.commentable_typeに記述されているモデル名に応じて変化します。

多対多の場合

親テーブルと子テーブルが多対多である場合のリレーションの記述、利用方式について紹介していきます。

テーブルの構造は以下とします

posts
- id : 主キー
- tilte : ブログタイトル
- content : 内容

products
- id : 主キー
- name : 商品名
- discription : 商品説明

comments
- id : 主キー
- content : コメント内容

commentables
- comment_id : コメントID
- commentable_type : 関連レコードのモデル名
- commentable_id : 関連レコードの主キー

リレーションの記述方法

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
public function comments()
{
return $this->morphToMany(Comment::class, 'commentable');
}
}
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Product extends Model
{
public function comments()
{
return $this->morphToMany(Comment::class, 'commentable');
}
}
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Comment extends Model
{
public function Posts()
{
return $this->morphedByMany(Post::class, 'commentable');
}

public function Products()
{
return $this->morphedByMany(Product::class, 'commentable');
}
}

リレーションの利用方法

親モデルから子モデルを取得する方法は

use App\Models\Post;

$post = Post::find(1);

$comments = $post->comments;

となります。

こちらにより、親モデル(Postモデル)から、紐づくすべての子モデル(commentモデル)を取得することができます。

逆に、子モデルから親モデルを取得する方法は

use App\Models\Comment;

$comment = Comment::find(1);

$posts = $comment->posts;
$products = $comment->products;

となります。

こちらにより、子モデル(commentモデル)からそれぞれの親モデル(Postモデル, Productモデル)を取得することができます。

まとめ

今回紹介したリレーションによって、 Laravelではポリモーフィック関連のテーブルの場合でも、簡単に関連テーブルの取得が行えるようになります。

実務で行っている案件上で、ポリモーフィック関連のテーブルを扱う際に「Laravel上でどのように行えばよいのか」が理解しづらかったので、今回紹介させていただきました。

Laravelを利用した開発の手助けの一因になれれば幸いです。

ここまで読んでいただきありがとうございました。

参考

Laravel 11.x Eloquent:リレーション Laravel 11.x マイグレーション