Editor’s Note: This post was updated in September 2021 for accuracy and to include information on multiple types of polymorphic relationships in Laravel, including one-to-one, one-of-many, and many-to-many relationships.
It’s not uncommon in software development to have models that can belong to more than one entity. This type of model maintains the same structure that doesn’t change regardless of the other model it’s connected to.
A common example of this kind of scenario is comments. In a blog, for example, comments can go on a post or a page but maintain the same structure regardless if it’s a post or a page. This kind of behavior is described as polymorphism.
In this article, we will go over polymorphic relationships in Laravel, how they work and the various use cases in which they are best used.
Taking the example mentioned above into consideration, we have two entities: Post
and Page
. To allow for comments on each of these, we can decide to set up our database like this:
posts: id title content posts_comments: id post_id comment date pages: id body pages_comments: id page_id comment date
This approach above has us creating multiple comment tables — posts_comments
and pages_comments
— that do the same thing except that they are pointing to different entities.
With polymorphic relationships, we can follow a cleaner and simpler approach for the same situation.
posts: id title content pages: id body comments: id commentable_id commentable_type date body
By definition, polymorphism is the condition of occurring in several different forms, and this is the approach we are trying to follow above. We have two important new columns to take note of: commentable_id
and commentable_type
.
In the example above, we have merged page_comments
and post_comments
together by replacing both post_id
and page_id
in each table with commentable_id
and commentable_type
to derive the comments
table.
The commentable_id
column would contain the ID of the post or page, and commentable_type
would contain the class name of the model that owns the record. The commentable_type
would store something like App\Post
, which is how the ORM will determine which model it belongs to and return when trying to access the value.
Here, we have three entities: Post
, Page
, and Comments
.
Post
can have Comments
.
Page
can have Comments
.
And Comments
can belong to either Post
or Page
.
Let’s create our migrations:
Schema::create('posts', function (Blueprint $table) { $table->increments('id'); $table->string('title'); $table->text('content'); }); Schema::create('pages', function (Blueprint $table) { $table->increments('id'); $table->text('body'); }); Schema::create('comments', function (Blueprint $table) { $table->increments('id'); $table->morphs(‘commentable’); $table->text('body'); $table->date('date'); });
$table→morphs('commentable')
would automatically create two columns for the id
and type
using the text passed to it, so it will result in commentable_id
and commentable_type
.
Next, we create models for our entities:
//file: app/Post.php <?php namespace App; use Illuminate\Database\Eloquent\Model; class Post extends Model { /** * Get all of the post's comments. */ public function comments() { return $this->morphMany('App\Comment', 'commentable'); } } //file: app/Page.php <?php namespace App; use Illuminate\Database\Eloquent\Model; class Page extends Model { /** * Get all of the page's comments. */ public function comments() { return $this->morphMany('App\Comment', 'commentable'); } } //file: app/Comment.php <?php namespace App; use Illuminate\Database\Eloquent\Model; class Comment extends Model { /** * Get all of the models that own comments. */ public function commentable() { return $this->morphTo(); } }
In the code above, we have our models declared and also make use of two methods, morphMany()
and morphTo
, that help us define a one-to-many polymorphic relationship.
Both the Page
and Post
models have a comments()
function that returns a morphMany()
to the Comment
model. This indicates that both of them are expected to have a relationship with many comments.
The Comment
model has a commentable()
function that returns a morphTo()
function, indicating that this class is related to other models.
Once these are set up, it becomes easy to access data and work with this relationship through our models.
Here are some examples:
To access all of the comments for a page, we can use the comments
dynamic property declared in the model.
// getting comments for a sample page... $page = Page::find(3); foreach($page->comment as $comment) { // working with comment here... }
For retrieving comments on a post:
// getting comments for a sample post... $post = Post::find(13); foreach($post->comment as $comment) { // working with comment here... }
You can also reverse this retrieval. In a situation where you have a comment ID and would like to find out which entity it belongs to using the commentable
method on the Comment
model, you can use:
$comment = Comment::find(23); // getting the model... var_dump($comment->commentable);
With this set up, you should know that the number of models that use the Comment
relationship are not limited to two. You can have as many as possible added to this without any major changes or breaking code. For example, let’s create a new Product
model added to your site that can also have comments.
First, we create the migration for the new model:
Schema::create('products', function (Blueprint $table) { $table->increments('id'); $table->string('name'); });
Then we create the model class:
//file: app/Product.php <?php namespace App; use Illuminate\Database\Eloquent\Model; class Product extends Model { /** * Get all of the product's comments. */ public function comments() { return $this->morphMany('App\Comment', 'commentable'); } }
And that’s it. Your comment system now supports your products and can be retrieved the same way as other entities.
// getting comments for a sample product... $product = Product::find(3); foreach($product->comment as $comment) { // working with comment here... }
The example above has covered one-to-many polymorphic relationships where one model, e.g., a page, can have many models, e.g., comments, related to it. Similar to how relationships in relational databases work, there are other types of polymorphic relationships.
A one-to-one polymorphic relationship is a situation where one model can belong to more than one type of model but on only one association. A typical example of this is featured images on a post and an avatar for a user. The only thing that changes however is how we get the associated model by using morphOne
instead.
class Post extends Model { /** * Get the post's only image. */ public function image() { return $this->morphOne(Image::class, 'imageable'); } } class User extends Model { /** * Get the user's only image. */ public function image() { return $this->morphOne(Image::class, 'imageable'); } }
A one-of-many relationship is a situation where one model can have multiple associations with more than one model, but you only want to retrieve one at a time. This takes advantage of Laravel’s ofMany
helper methods along with morphOne
to retrieve the desired single association.
public function latestImage() { return $this->morphOne(Image::class, 'imageable')->latestOfMany(); }
Check out Laravel’s documentation of the various ofMany
methods that can be used to construct such relationships.
Many-to-many polymorphic relationships handle slightly more complicated situations than one-to-many and one-to-one relationships. An easy example is when we have posts, pages, and tags. Multiple posts can have multiple tags. Multiple tags can belong to multiple pages.
In our post model, we can retrieve associated tags using morphToMany
.
class Post extends Model { /** * Get all of the tags for the post. */ public function tags() { return $this->morphToMany(Tag::class, 'taggable'); } } And from the Tag model, return all connected associations from the desired model class Tag extends Model { /** * Get all of the posts that are assigned this tag. */ public function posts() { return $this->morphedByMany(Post::class, 'taggable'); } }
A common use case where polymorphic relationships also come into play is when there is a need for multiple user types. These user types usually have some similar fields and then other fields unique to them. This could be a User
and Admin
type, a Driver
or Rider
type in the case of ride-sharing apps, or even applications where there are numerous kinds of users or professionals.
Every user may have a name, email, avatar phone, etc., before having additional details added to them. Here’s an example schema for a hiring platform that allows you to hire different kinds of artisans:
user: id name email avatar address phone experience userable_id userable_type drivers: id region car_type //manual or automatic long_distance_drive cleaners: id use_chemicals //uses chemicals in cleaning preferred_size //size of cleaning ...
In this scenario, we can get our users’ basic data without worrying about whether they are cleaners or not, andm at the same time, we can get their type from the userable_type
and the ID from that table in the userable_id
column when it is needed. This will be a one-to-one polymorphic relationship.
In a similar scenario like the comment example provided above, posts and pages — and even messages — can require attachments or any other form of media. This works better than creating a table for every kind of attachment.
messages: id user_id recipient_id content attachment: id url attachable_id attachable_type
In the above example, attachable_type
can then be the model for messages, posts, or pages.
The general concept behind using polymorphic relationships revolves around identifying similarities between what two or more models might need and building on top of it instead of duplicating and creating numerous tables and code.
We have discussed the basic application of polymorphic relationships and its possible use cases. We should also note that polymorphic relationships are not a complete solution to everything and should only be used when convenient or feels like the right way to go. Is there an exciting use case of polymorphic relationships in your application? Share below!
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowCompare Prisma and Drizzle ORMs to learn their differences, strengths, and weaknesses for data access and migrations.
It’s easy for devs to default to JavaScript to fix every problem. Let’s use the RoLP to find simpler alternatives with HTML and CSS.
Learn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.
22 Replies to "Polymorphic relationships in Laravel and their use cases"
Great article, thanks!
Great !
Yes good article!
Thanks for the great explanation. It was very easy to learn this content here.
I have 2 questions:
1. Are you missing S on $page->comment(s)? And in other loops too?
foreach($page->comment as $comment)
{
// working with comment here…
}
2. In which column the comments are stored in comments table? Because we have only: Id, commendable_id, commendable_type and date.
2. Its an error in this article – in comments migrations we saw $table->date(‘body’); .. then must by $table->string(‘body’); or $table->text(‘body’); – body column is for the coment content 🙂
Great article. I have one question: how would you return the inverse? Eg all comments of class Page?
great article
You guys have done a far better than job than the official Laravel docs. Cheers!
Yes, I agreed.
This was exactly what I was looking for. All my scenarios were discussed here. This is fantastic. Thank you very much.
Thank You soo much for this great article
Hi, greate article!!!
Just one small mistake: it should be $table→morphs(‘commentable’) not $table→morphs(‘comment’) which would automatically create two columns using the text passed to (it won’t add able, atleast not in L8). So it will result in commentable_id and commentable_type.
Thanks for the catch
Thanks for showing us the relationship.
Loved your article and will help me solve many issues.
Thanks!
Great Article.
Loved it.
this is useless if you not going to teach actionable events like attaching comment to post or sync without detaching!! stop supporting half baked articles
Is it possible to eager load relations with this approach?
the most complete article, congratz!
Very nice article, finally I know what polymorph relation is..
Great i learn a lot from this article. Thanks
Very Very Informative.ThankYou
The way you’ve explained this complex concept is truly impressive. Laravel’s flexibility never ceases to amaze me, and this article really highlights the power of polymorphic relationships in making our code cleaner and more efficient.