Key Takeaways
- Polymorphic relationships in Laravel allow a model to belong to more than one other model on a single association. This simplifies the database structure, makes code more maintainable and allows for more dynamic and flexible data relationships.
- Setting up a polymorphic relationship in Laravel involves defining the relationship in your Eloquent models. The morphTo method is used on the model that will be receiving the polymorphic relation, while the morphMany or morphOne method is used on the model that will be relating to the other models.
- Laravel’s MorphMap method can be used to instruct Eloquent to use a custom name for each model instead of the class name. This helps in cases where the namespace of a model changes or in cases of long namespaces.
- Laravel supports many-to-many polymorphic relationships, which allow a model to belong to multiple models on a many-to-many basis. This can be particularly useful in complex applications.
This article was peer reviewed by Younes Rafie. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be!
You’ve probably used different types of relationships between models or database tables, like those commonly seen in Laravel: one-to-one, one-to-many, many-to-many, and has-many-through. But there’s another type of relationship that’s not so common: polymorphic. So what is a polymorphic relationship?
A polymorphic relationship is where a model can belong to more than one other model on a single association.
To clarify this, let’s create an imaginary situation where we have a Topic
and a Post
model. Users can leave comments on both topics and posts. Using polymorphic relationships, we can use a single comments
table for both of these scenarios. Surprising, yeah? This seems a bit impractical since, ideally, we’d have to create a post_comments
table and a topic_comments
table to differentiate the comments. With polymorphic relationships, we don’t need two tables. Let’s look into polymorphic relationships through a practical example.
What We’ll Be Building
We’ll be creating a demo music app which has songs and albums. In this app, we’ll have the option to upvote both songs and albums. Using polymorphic relationships, we’ll use a single upvotes table for both of these scenarios. First, let’s examine the table structure required to build this relationship:
albums
id - integer
name - string
songs
id - integer
title - string
album_id - integer
upvotes
id - integer
upvoteable_id - integer
upvoteable_type - string
Let’s talk about the upvoteable_id
and upvoteable_type
columns which may seem a bit foreign to those who’ve not used polymorphic relationships before. The upvoteable_id
column will contain the ID value of the album or song, while the upvoteable_type
column will contain the class name of the owning model. The upvoteable_type
column is how the ORM determines which “type” of owning model to return when accessing the upvoteable
relation.
Generating the Models Alongside Migrations
I am assuming you already have a Laravel app that’s up and running. If not, this premium quick start course might help. Let’s start by creating the three models and migrations, then edit the migrations to suit our needs.
php artisan make:model Album -m
php artisan make:model Song -m
php artisan make:model Upvote -m
Note, passing the -m
flag when creating models will generate migrations associated with those models as well. Let’s tweak the up
method in these migrations to get the desired table structure:
{some_timestamp}_create_albums_table.php
public function up()
{
Schema::create('albums', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->timestamps();
});
}
{some_timestamp}_create_songs_table.php
public function up()
{
Schema::create('songs', function (Blueprint $table) {
$table->increments('id');
$table->string('title');
$table->integer('album_id')->unsigned()->index();
$table->timestamps();
$table->foreign('album_id')->references('id')->on('album')->onDelete('cascade');
});
}
{some_timestamp}_create_upvotes_table.php
public function up()
{
Schema::create('upvotes', function (Blueprint $table) {
$table->increments('id');
$table->morphs('upvoteable'); // Adds unsigned INTEGER upvoteable_id and STRING upvoteable_type
$table->timestamps();
});
}
We can now run the artisan migrate
command to create the three tables:
php artisan migrate
Let’s now configure our models to take note of the polymorphic relationship between albums, songs, and upvotes:
app/Upvote.php
[...]
class Upvote extends Model
{
/**
* Get all of the owning models.
*/
public function upvoteable()
{
return $this->morphTo();
}
}
app/Album.php
class Album extends Model
{
protected $fillable = ['name'];
public function songs()
{
return $this->hasMany(Song::class);
}
public function upvotes()
{
return $this->morphMany(Upvote::class, 'upvoteable');
}
}
app/Song.php
class Song extends Model
{
protected $fillable = ['title', 'album_id'];
public function album()
{
return $this->belongsTo(Album::class);
}
public function upvotes()
{
return $this->morphMany(Upvote::class, 'upvoteable');
}
}
The upvotes
method in both the Album
and Song
models defines a polymorphic one-to-many relationship between these models and the Upvote
model and will help us get all of the upvotes for an instance of that particular model.
With the relationships defined, we can now play around with the app so as to get a better understanding of how polymorphic relationships work. We won’t create any views for this app, we’ll just tinker around with our application from the console.
In case you are thinking in terms of controllers and where we should place the upvote
method, I suggest creating an AlbumUpvoteController
and a SongUpvoteController
. By this, we kind of keep things strictly tied down to the thing we are acting on when working with polymorphic relationships. In our case, we can upvote both albums and songs. The upvote is not part of an album nor is it part of a song. Also, it’s not a general upvote thing, as opposed to how we’d have an UpvotesController
in most one-to-many relationships. Hopefully this makes sense.
Let’s fire up the console:
php artisan tinker
>>> $album = App\Album::create(['name' => 'More Life']);
>>> $song = App\Song::create(['title' => 'Free smoke', 'album_id' => 1]);
>>> $upvote1 = new App\Upvote;
>>> $upvote2 = new App\Upvote;
>>> $upvote3 = new App\Upvote;
>>> $album->upvotes()->save($upvote1)
>>> $song->upvotes()->save($upvote2)
>>> $album->upvotes()->save($upvote3)
Retrieving the Relationships
Now that we have some data in place, we can access our relationships via our models. Below is a screenshot of the data inside the upvotes table:
To access all of the upvotes for an album, we can use the upvotes dynamic property:
$album = App\Album::find(1);
$upvotes = $album->upvotes;
$upvotescount = $album->upvotes->count();
It’s also possible to retrieve the owner of a polymorphic relation from the polymorphic model by accessing the name of the method that performs the call to morphTo
. In our case, that is the upvoteable
method on the Upvote model. So, we will access that method as a dynamic property:
$upvote = App\Upvote::find(1);
$model = $upvote->upvoteable;
The upvoteable
relation on the Upvote model will return an Album
instance since this upvote is owned by an instance of the Album
instance.
As it is possible to get the number of upvotes for a song or an album, we can sort the songs or albums based on the upvotes on a view. That is what happens in music charts.
In the case of a song we’d get the upvotes like so:
$song = App\Song::find(1);
$upvotes = $song->upvotes;
$upvotescount = $song->upvotes->count();
Custom Polymorphic Types
By default, Laravel will use the fully qualified class name to store the type of the related model. For instance, given the example above where an Upvote
may belong to an Album
or a Song
, the default upvoteable_type
would be either App\Album
or App\Song
, respectively.
However, there is one big flaw with this. What if the namespace of the Album
model changes? We will have to make some sort of migration to rename all occurrences in the upvotes
table. And that’s a bit crafty! Also what happens in the case of long namespaces (such as App\Models\Data\Topics\Something\SomethingElse
)? That means we have to set a long max length on the column. And that is where the MorphMap
method comes to our rescue.
The “morphMap” method will instruct Eloquent to use a custom name for each model instead of the class name:
use Illuminate\Database\Eloquent\Relations\Relation;
Relation::morphMap([
'album' => \App\Album::class,
'song' => \App\Song::class,
]);
We can register the morphMap
in the boot function of our AppServiceProvider
or create a separate service provider. For the new changes to take effect, we have to run the composer dump-autoload
command. So now, we can add this new upvote record:
[
"id" => 4,
"upvoteable_type" => "album",
"upvoteable_id" => 1
]
and it would behave in the exact same manner as the previous example does.
Conclusion
Even though you’ve probably never run into a situation which required you to use polymorphic relationships, that day will likely eventually come. The good thing when working with Laravel is that it’s really easy to deal with this situation without having to do any kind of model association trickery to get things working. Laravel even supports many-to-many polymorphic relations. You can read more about that here.
I hope you’ve now understood polymorphic relationships and situations which may call for these types of relationships. Another, slightly more advanced example on Polymorphic relationships is available here. If you found this helpful, please share with your friends and don’t forget to hit the like button. Feel free to leave your thoughts in the comments section below!
Frequently Asked Questions on Eloquent’s Polymorphic Relationships
What are the benefits of using polymorphic relationships in Laravel?
Polymorphic relationships in Laravel provide a flexible and efficient way to handle related data across different models. They allow a model to belong to more than one other type of model on a single association. This means you can have a single list of unique identifiers for all related data, regardless of their type. This can greatly simplify your database structure and make your code more maintainable. It also allows for more dynamic and flexible data relationships, which can be particularly useful in complex applications.
How do I set up a polymorphic relationship in Laravel?
Setting up a polymorphic relationship in Laravel involves defining the relationship in your Eloquent models. First, you need to define the relationship on the model that will be receiving the polymorphic relation. This is done using the morphTo
method. Then, on the model that will be relating to the other models, you use the morphMany
or morphOne
method, depending on whether the relationship is one-to-many or one-to-one.
Can you provide an example of a polymorphic relationship in Laravel?
Sure, let’s consider a blogging platform where both posts and users can have comments. In this case, the Comment
model would have a polymorphic relationship with both the Post
and User
models. Here’s how you might define this relationship:class Comment extends Model
{
public function commentable()
{
return $this->morphTo();
}
}
class Post extends Model
{
public function comments()
{
return $this->morphMany('App\Comment', 'commentable');
}
}
class User extends Model
{
public function comments()
{
return $this->morphMany('App\Comment', 'commentable');
}
}
How do I retrieve related records using a polymorphic relationship?
You can retrieve related records in a polymorphic relationship just like you would with any other Eloquent relationship. For example, if you wanted to retrieve all comments for a post, you could do so like this:$post = App\Post::find(1);
$comments = $post->comments;
How do I save related records using a polymorphic relationship?
Saving related records in a polymorphic relationship is also similar to other Eloquent relationships. You can use the associate
method to associate the models, and then save the model. Here’s an example:$comment = new App\Comment(['body' => 'A new comment.']);
$post = App\Post::find(1);
$post->comments()->save($comment);
What are some common use cases for polymorphic relationships?
Polymorphic relationships are particularly useful in situations where a model can belong to more than one other type of model. Some common use cases include comments that can belong to both posts and users, tags that can be applied to multiple types of content, and images or files that can be attached to various types of entities.
Are there any limitations or drawbacks to using polymorphic relationships?
While polymorphic relationships offer a lot of flexibility, they can also be more complex to set up and manage than standard Eloquent relationships. They also don’t support all the features of standard relationships, such as eager loading constraints.
How do I delete related records in a polymorphic relationship?
You can delete related records in a polymorphic relationship using the delete
method on the relationship. For example, to delete all comments for a post, you could do so like this:$post = App\Post::find(1);
$post->comments()->delete();
Can I use polymorphic relationships with many-to-many relationships?
Yes, Laravel supports many-to-many polymorphic relationships through the morphToMany
and morphedByMany
methods. This allows a model to belong to multiple models on a many-to-many basis.
How do I handle polymorphic relationships in database migrations?
In your database migrations, you would typically add two columns to the table that will be receiving the polymorphic relation: one for the related model ID, and one for the related model type. Laravel provides a convenient morphs
method to add these columns:$table->morphs('commentable');
This will add commentable_id
and commentable_type
columns to the table.
Chris is a software developer at Andela. He has worked with both Rails and Laravel and blogs to share a few tips. Chris also loves traveling.