Polymorphic relationships in Laravel and their use cases
In software development, it’s common to encounter models that belong to multiple entities. A great example is comments. Take an application like Facebook, for instance: comments can be associated with posts, stories, or videos while maintaining the same structure. This behavior is an excellent illustration of polymorphism in action.
In this article, we will explore polymorphic relationships in Laravel, delve into how they work, and discuss the various use cases where they shine the most.
Polymorphic Relationships
A polymorphic relationship lets one model be connected to multiple other models using a single setup. For example, if you’re building an app where users can share blog posts and videos, a Comment model can be linked to both Post and Video models. This means users can comment on either posts or videos without needing separate comment systems.
One to One (Polymorphic)
A one-to-one polymorphic relationship is like a regular one-to-one relationship, but with a twist: the child model can be linked to more than one type of parent model using just one connection. For example, a Profile could belong to either a User or an Admin, all through the same relationship setup.
let’s examine the table structure:
admin
id - integer
name - string
users
id - integer
name - string
profile
id - integer
avatar- string
profileable_id - integer
profileable_type - string
The profileable_id column will contain the id values of admin or users. While the profileable_type column will contain the class name of the parent users , admin models. it’s either App\Models\Users or App\Models\Admin
Now, let check the models
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphTo;
class Profile extends Model
{
/**
* Get the parent profileable model (user or admin).
*/
public function profileable(): MorphTo
{
return $this->morphTo();
}
}
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphOne;
class Users extends Model
{
/**
* Get the users profile.
*/
public function profile(): MorphOne
{
return $this->morphOne(Profile::class, 'profileable');
}
}
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphOne;
class Admin extends Model
{
/**
* Get the admin profile.
*/
public function profile(): MorphOne
{
return $this->morphOne(Profile::class, 'profileable');
}
}
One to Many (Polymorphic)
A one-to-many polymorphic relationship is like a regular one-to-many relationship, but with a difference: the child model can be linked to multiple types of parent models using one setup. For example, a Comment model can belong to either a Post or a Video, allowing users to leave comments on both without needing separate comment systems
let’s examine the table structure required to build this relationship:
posts
id - integer
title - string
body - text
videos
id - integer
title - string
url - string
comments
id - integer
body - text
commentable_id - integer
commentable_type - string
Next, Model structure
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphTo;
class Comments extends Model
{
/**
* Get the parent commentable model (posts or vidoes).
*/
public function commentable(): MorphTo
{
return $this->morphTo();
}
}
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphOne;
class Videos extends Model
{
/**
* Get the video comments.
*/
public function comments(): MorphMany
{
return $this->morphMany(Comments::class, 'commentable');
}
}
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphOne;
class Posts extends Model
{
/**
* Get the post comments.
*/
public function comments(): MorphMany
{
return $this->morphMany(Comments::class, 'commentable');
}
}
Many to Many (Polymorphic)
Many-to-many polymorphic relationships let multiple models share a common relationship with another model. For example, both a Post and a Video can have tags, and those tags are stored in a single table. This setup allows you to manage unique tags that can be linked to either posts or videos, keeping things organized and flexible.
let’s examine the table structure required to build this relationship:
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
Now, let check models
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphToMany;
class Post extends Model
{
/**
* Get all of the tags for the post.
*/
public function tags(): MorphToMany
{
return $this->morphToMany(Tag::class, 'taggable');
}
}
class Tag extends Model
{
/**
* Get all of the posts that are assigned this tag.
*/
public function posts(): MorphToMany
{
return $this->morphedByMany(Post::class, 'taggable');
}
/**
* Get all of the videos that are assigned this tag.
*/
public function videos(): MorphToMany
{
return $this->morphedByMany(Video::class, 'taggable');
}
}
Custom Polymorphic Types
By default, Laravel saves the full class name of the related model (like App\Models\Post
or App\Models\Video
) in the commentable_type
column to identify the type of model a comment belongs to.
However, if you want to simplify this and avoid saving full class names in the database — for example, just save post
or video
instead—you can customize this behavior. This makes the database cleaner and less tied to the internal structure of your application.
You can do this by defining a morph map, which tells Laravel to use shorter, custom names instead of full class names.
use Illuminate\Database\Eloquent\Relations\Relation;
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
Relation::enforceMorphMap([
'post' => App\Models\Post::class,
'video' => App\Models\Video::class,
]);
}
}
In this example:
- The
post
key maps to theApp\Models\Post
class. - The
video
key maps to theApp\Models\Video
class.
This will ensure that the commentable_type
column in your database stores post
or video
instead of the fully qualified class names.
common usage,
use App\Models\Post;
use App\Models\Video;
use App\Models\Comment;
// Attach a comment to a post
$post = Post::find(1);
$post->comments()->create([
'body' => 'This is a comment on a post',
]);
// Attach a comment to a video
$video = Video::find(1);
$video->comments()->create([
'body' => 'This is a comment on a video',
]);
// fetching comments
// Get all comments for a post
$postComments = Post::find(1)->comments;
// Get all comments for a video
$videoComments = Video::find(1)->comments;
// accessing the parents model
$comment = Comment::find(1);
$parent = $comment->commentable; // Returns the related Post or Video model
Thank you !