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対1 | 1つのデータに対して 必ず 1つだけのレコードがぶら下がる User has Computer | 1つのレコードは 必ず1つのレコードに属する Computer belongs to User | 1つのリレーションにおいて 登場するモデルは固定の2種類のみ | 学校の生徒1人あたり1つのコンピュータを支給する、そのユーザのコンピュータ利用状況を紐づける、という場合を考える ユーザが持つコンピュータは必ず1台である 一方、コンピュータが属するユーザも必ず1人のみである(1対1) Computer そのもののマスタと User の紐付けならば これは適切ではなく、最も基本形でありながら このパターンを実装する機会は少ない |
1対n | 1つのデータに対して 複数(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対n | 1つのレコードに対して 複数(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 | 型 | 入る値 | 具体例 | 備考 |
---|---|---|---|---|
id | integer | increments により自動採番される値 | 1 | UUID を採用する場合は integer ではなく string |
url | string | 画像のパス | https://example.com/75438295.jpg | |
imageable_type | string | モデル | App\Models\User or App\Models\Post | |
imageable_id | integer | モデルid | 12 | UUID を採用する場合は 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 で管理