Introduction
Imagine you want to store images for users, products, posts, etc. In such cases, a polymorphic relationship can be useful. It allows a model to be associated with different types of models through a single relationship.
Other common examples include descriptions and tags, where the same model can be associated with different types of parent models.
For example, an Image could belong to either a Product, a User, or a Post. Instead of having separate foreign keys such as product_id, user_id, or post_id, an image can use imageable_id and imageable_type:
This means that the image belongs to the User with an ID of 14.
In short polymorphic relationships allow a single model to be associated with different types of parent models through the same relationship. This can reduce duplication and simplify your database structure when the same type of relationship is needed across different models.
How does it work in Laravel?
In Laravel polymorphic relationships are built directly into Eloquent. Laravel lets you define the relationship once and then reuse it across different models.
Creating the table:
In this case the morphs method creates the imageable_id and imageable_type.
Relationship types
There are different types of polymorphic relationships. Using the database example above as a starting point, we will explore the different types of polymorphic relationships.
One to One
Similar to a typical one-to-one relationship a model can have one related model, however since it's polymorphic the child model can belong to different types of parent models.
For this to work the Image model must be given the imageable functionality using MorphTo.
The parent models then use MorphOne to define the one-to-one relationship.
One to Many
Using our image example, in this case the parent models can have many images, however an image can only belong to one parent model. Compared to the one-to-one polymorphic relationship we only need to replace morphOne by morphMany.
One of Many
This allows you to retrieve one specific record from a one-to-many relationship. For example the latest or oldest record.
The ofMany can be set to your requirements, in this case we used size to get the largest image. Laravel also provides the methods latestOfMany() and oldestOfMany() for the common cases of retrieving the latest or oldest related record.
Many to Many
This polymorphic relationship is a bit more complicated because it combines a many-to-many relationship with a polymorphic relationship. Since both sides can have multiple related records, a pivot table is required to keep track of the relationships.
Using the image example, besides the images table, a separate pivot table is used to store the relationship between the images and the polymorphic models. The pivot table contains the image_id, imageable_type and imageable_id. So in this case the pivot table holds the polymorphic association.
Following our example the creation of our database migration would look as follows:
Making the primary key of the pivot table a combination of all three columns.
For this to work, the parent models use morphToMany() method to define that they can have many related models.
The image model then uses the morphedByMany() method to define which types of parent models it can be related to.
An important detail here is that even though the morphedByMany() method is used, the return type remains MorphToMany.
Custom types
Normally Laravel stores the full class name in the type column. Using enforceMorphMap() you can define shorter custom types. This especially can be useful if you later change the class name of a parent model, because the value stored in the database does not need to change.
enforceMorphMap() can be set in the boot method of the AppServiceProvider.
In this case the database would then store for example:
imageable_type = "user"
Instead of the full class name.
Accessing the Relationships
Once the relationship has been defined, you can access them through Eloquent the same way as regular relationships.
For example, if a User has a polymorphic one-to-many relationship with Image, you can retrieve all of the user's images using the images property:
On the other side of the relationship, you can use the imageable relationship to retrieve the parent model.
Pros
- Less duplication: No more need to use separate image tables for users, posts, etc.
- Flexibility: One model can be associated with multiple types of models.
- Easy to extend: Adding a parent model usually only need to define the relationship, instead of changing the database schema.
Cons
- More complex: Can be harder to understand and query than regular foreign-key relationships.
- Potential performance penalty: Since multiple model types can share the same table, the tables can become larger and queries can take longer. Proper indexing can become important.
- No foreign-key constraint: The database can no longer control the integrity of the relationship, since there is no foreign-key. In this case the application is responsible for maintaining the relationship's integrity.
When to use?
Use polymorphic relationships only when the same model genuinely needs to belong to different types of parent models. In all other circumstances regular relationships are usually simpler and safer to maintain.