Laravel Eloquent ORM Polymorphism
In Laravel's Eloquent ORM, polymorphism refers to polymorphic relationships, which allow a model to belong to more than one type of model using a single association.
This is useful when you have a relationship that can apply to multiple different models without having to create separate foreign keys or pivot tables for each type. Examples would be Users and Products that each have one or more images.
Let’s take a look at different types of polymorphic relationships commonly used in Laravel Eloquent ORM.
One-to-One (Polymorphic)
Example: An Image model could belong to either a User or a Product.
// Image model
public function imageable()
{
return $this->morphTo();
}
// User model
public function image()
{
return $this->morphOne(Image::class, 'imageable');
}
// Product model
public function image()
{
return $this->morphOne(Image::class, 'imageable');
}
Table Definitions: User, Product models can both have an Image. Notice Image.imageable_id and Image.imageable_type. This can join on both User.id and Product.id tables.
CREATE TABLE users (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255),
email VARCHAR(255) UNIQUE
);
CREATE TABLE products (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255),
price DECIMAL(10,2)
);
CREATE TABLE images (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
url VARCHAR(255),
imageable_id BIGINT UNSIGNED, -- FK ID (user.id or product.id)
imageable_type VARCHAR(255), -- Model class (App\Models\User/App\Models\Product)
INDEX idx_imageable (imageable_id, imageable_type)
);
One-to-Many (Polymorphic)
Example: A Comment model could belong to both a Post and a Video.
// Comment model
public function commentable()
{
return $this->morphTo();
}
// Post model
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}
// Video model
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}
Table Definitions: Post, Video models can both have a Comment. Notice Comment.commentable_id and Comment.commentable_type. This can join on both Post.id and Video.id tables.
CREATE TABLE posts (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255),
body TEXT
);
CREATE TABLE videos (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255),
url VARCHAR(255)
);
CREATE TABLE comments (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
body TEXT,
commentable_id BIGINT UNSIGNED, -- FK ID (post.id|video.id)
commentable_type VARCHAR(255), -- Model class (App\Models\Post|App\Models\Video)
INDEX idx_commentable (commentable_id, commentable_type)
);
Many-to-Many (Polymorphic)
Example: A Tag model can be applied to both Post and Video.
// Tag model
public function posts()
{
return $this->morphedByMany(Post::class, 'taggable');
}
public function videos()
{
return $this->morphedByMany(Video::class, 'taggable');
}
// Post model
public function tags()
{
return $this->morphToMany(Tag::class, 'taggable');
}
// Video model
public function tags()
{
return $this->morphToMany(Tag::class, 'taggable');
}
Table Definitions: Both Post, Video models can have multiple Tags. Also, a Tag can belong to multiple Post, Video models. Notice the pivot table taggables…
CREATE TABLE tags (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) UNIQUE
);
CREATE TABLE posts (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255),
body TEXT
);
CREATE TABLE videos (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255),
url VARCHAR(255)
);
CREATE TABLE taggables (
tag_id BIGINT UNSIGNED, -- FK to tags.id
taggable_id BIGINT UNSIGNED, -- FK ID (post.id or video.id)
taggable_type VARCHAR(255), -- Model class (App\Models\Post / App\Models\Video)
PRIMARY KEY (tag_id, taggable_id, taggable_type),
INDEX idx_taggable (taggable_id, taggable_type)
);