Laravel における「ポリモーフィック」を理解する

本記事は https://readouble.com/laravel/10.x/ja/eloquent-relationships.html#one-to-many-polymorphic-relations に登場する 「Laravel における ポリモーフィック」を理解することを目的として、その内容をまとめたものである。つまり、言語やフレームワークを問わず使用される用語である「ポリモーフィズム」については触れない。

ただし「厳格な定義や表現で正しくまとめること」よりも「自分が理解すること」「あとから自分が見たときに概要をササっと思い出せること」に重点を置いているため、本記事の鵜呑みは厳禁。(書いてみて思ったが、正確に言語化するのが結構難しい内容である)

また、あくまでポリモーフィックとは何か、を「知識として理解する」ことが目的のため、個別具体的な実装方法( php artisan make コマンドの使い方やコードの書き方など)はスコープ外とする。

TL;DR

基本形

  • 通常のリレーションは、登場するモデルの関係が固定
  • ポリモーフィックのリレーションは、1つの共通モデルを複数のモデルから参照する
keyモデル同士の関係
(主から見た場合)
モデル同士の関係
(従から見た場合)
参照関係具体例を交えた説明
1対11つのデータに対して
必ず 1つだけのレコードがぶら下がる
User has Computer
1つのレコードは
必ず1つのレコードに属する
Computer belongs to User
1つのリレーションにおいて
登場するモデルは固定の2種類のみ
学校の生徒1人あたり1つのコンピュータを支給する、そのユーザのコンピュータ利用状況を紐づける、という場合を考える
ユーザが持つコンピュータは必ず1台である
一方、コンピュータが属するユーザも必ず1人のみである(1対1)
Computer そのもののマスタと User の紐付けならば これは適切ではなく、最も基本形でありながら このパターンを実装する機会は少ない
1対n1つのデータに対して
複数(0も含む)のレコードがぶら下がる
Blog has Posts
1つのレコードは
必ず1つのレコードに属する
Posts belong to Blog
1つのリレーションにおいて
登場するモデルは固定の2種類のみ
1つのブログには複数の記事が投稿される可能性がある
しかし、1つの記事は必ず1つのブログに属する(1対n)
1つの記事を複数のブログを投稿する、ということはスパム的な作戦としてはあり得ない話ではないが、データベース上で1つの Posts table のレコードを 複数の Blogs table のレコードから参照するということはまずないだろう
(それをやる場合は n対n の関係になる)
n対n1つのレコードに対して
複数(0も含む)のレコードがぶら下がる
User has Roles
1つのレコードは
複数のレコードに属する可能性がある
Role belongs Users
1つのリレーションにおいて
登場するモデルは固定の2種類のみ
とある管理システムにおけるユーザは、「管理者」「承認者」「編集者」と複数のロールを兼ねることがある
一方で「編集者」というロールは「田中さん」「山田さん」「鈴木さん」と複数のユーザに紐づけられる可能性がある
つまり主従のどちらから見ても、相手のレコードは複数となる可能性がある(n対n)
ポリモーフィック
1対1
1つのデータに対して
必ず 1つだけのレコードがぶら下がる
User has Computer
1つのレコードは
必ず1つのレコードに属する
Computer belongs to User
1つのリレーションにおいて
1つの共通モデルが
複数のモデルから参照される可能性がある
1つの画像テーブルがあり、これがユーザプロフィールのサムネイル画像、記事のサムネイル画像の両方で使われる可能性がある(ポリモーフィック)
ポリモーフィック
1対n
1つのデータに対して
複数(0も含む)のレコードがぶら下がる
Blog has Posts
1つのレコードは
必ず1つのレコードに属する
Posts belong to Blog
1つのリレーションにおいて
1つの共通モデルが
複数のモデルから参照される可能性がある
1つのコメントテーブルがあり、そのコメントは記事に対するものである場合もあれば、動画に対するものである場合もある(ポリモーフィック)
1つのPost や Video には複数のコメントがぶら下がる可能性があるが、1つのコメントが紐づくのは必ず 1つの Post または Video のみである(1:n)
(1つのコメントを作成し、それを複数の記事やビデオにまとめて紐づける、ということができるブログや動画配信サイトは世の中を探せばあるのかもしれないが、相当レアだろう)
ポリモーフィック
n対n
1つのレコードに対して
複数(0も含む)のレコードがぶら下がる
User has Roles
1つのレコードは
複数のレコードに属する可能性がある
Role belongs Users
1つのリレーションにおいて
1つの共通モデルが
複数のモデルから参照される可能性がある
また、記事や動画には複数のコメントが投稿される
1つのタグテーブルがあり、そのタグは記事にも動画にも紐付けられる可能性がある(ポリモーフィック)
1つの記事や動画には複数のタグを紐づけることが可能である一方で、1つのタグも複数の記事や動画に登録される可能性がある(n対n)

ポリモーフィック

Laravel における ポリモーフィックとは「1つの共通モデルが複数のモデルから参照される」というケース、およびそのケースにおいて適切なコード上の実装のことを指す。(ようである) 例えば「Images が User とも Post とも Book とも relational な関係」となる場合、言い換えると「User has Image」「Post has Image」「Book has Image」というケースが同時に成り立つ場合に適切な実装をポリモーフィックと呼ぶ。(ようである)

また ポリモーフィックかどうかは「モデル同士の関係が 1:1 か 1:n か n:n か」つまり「User has Many Roles」「Role Belongs to many Users」かどうかとは全く関係がない。これは切り分けて考えるべきものである。(だからこそ、上記の TL;DR の表でも でも「モデル同士の関係」と「参照関係」とカラムを分けて書いた。このラベルが適切かは自分でも怪しいと思っているが・・・)

非ポリモーフィックである場合は、モデル自体の関係はは必ず Image 対 Post のように 1対1の関係となる。

ポリモーフィックな「モデル同士の関係」は、大きく以下の3つに分けることができる。

  • 1対1の場合
    • User と Post 1レコードが参照する Image は必ず1つ(ユーザのプロフィール画像、Postのサムネイル画像は必ず1枚で複数は設定できない、と考えればわかりやすいだろう)
    • Image 1レコードが参照する User または Post も必ず1つ(画像1つが紐づくことが可能なのは1つのプロフィールかサムネイルだけである、とルール付けすればこれもそうなるだろう)
  • 1対nの場合
    • Post と Video 1レコードが参照する Comments は複数(1つの記事や動画に対して、複数のユーザーがコメントするので、1対nになると考えると良い)
    • Comment 1レコードが参照する Post または Video は必ず1つ(1つのコメントを作成し、それを複数の記事やビデオにまとめて紐づける、ということができるブログや動画配信サイトは一般的ではないだろう)
  • n対nの場合
    • Post や Video 1レコードが参照する Tags は 複数(1つの記事や動画に対して、複数のタグが付与できる)
    • Tags 1レコードが参照する Post や Videp も複数(同じタグを、複数の記事や動画に対して紐づけることができる)

カラム構成の違い

Laravel におけるポリモーフィックの実装に乗っかる場合のテーブル構成を確認していく。

1対1 または 1対n

1対1 の場合「Image を User と Post が取り合う」という関係に立つことから、 取り合いされる側である Images table が若干特殊なカラム構成となる。

posts
    id - integer
    name - string

users
    id - integer
    name - string

images
    id - integer
    url - string
    imageable_id - integer
    imageable_type - string

Images table の中身

key入る値具体例備考
idintegerincrements により自動採番される値1UUID を採用する場合は integer ではなく string
urlstring画像のパスhttps://example.com/75438295.jpg
imageable_typestringモデルApp\Models\User or App\Models\Post
imageable_idintegerモデルid12UUID を採用する場合は integer ではなく string

これは m対n の場合も同様である。

n対n

一方で、 n対n の場合は 上記とは異なるカラム構成を取る必要がある。

posts
    id - integer
    name - string

videos
    id - integer
    name - string

tags
    id - integer
    name - string

taggables
    tag_id - integer
    taggable_id - integer
    taggable_type - string

1対1 および 1対n の場合は参照される側の テーブルは1つでよかったが、n対n の場合は tags と taggables の二つが必要となる。これは以下のように役割を二分するものである。

  • tag そのものの具体的な情報(タグ名など)は tags で管理
  • 「どのタグ」が「どのmodel」の「どのレコード」から参照されるか、という情報は taggables で管理
執筆日: