Polymorphic Relations in Eloquent: Explained with Examples

Updated: January 16, 2024 By: Guest Contributor Post a comment

Introduction

Polymorphic relations in Laravel’s Eloquent ORM provide a way to set up a single association to different models. This guide walks you through the concept of polymorphic relationships, showing code examples and explaining how they can be used in your Laravel applications.

Understanding Polymorphic Relations

In Eloquent, relationships allowing a model to belong to more than one other model on a single association is known as a polymorphic relationship. Typical examples include things like comments or files that could be associated with posts, videos, or any other type of model. To understand this, let’s begin with a basic example: setting up a polymorphic relationship between a Comment model and models Post and Video.

class Comment extends Model
{
    public function commentable()
    {
        return $this->morphTo();
    }
}

class Post extends Model
{
    public function comments()
    {
        return $this->morphMany('App\Comment', 'commentable');
    }
}

class Video extends Model
{
    public function comments()
    {
        return $this->morphMany('App\Comment', 'commentable');
    }
}

In the database, your comments table might have a schema like this:

Schema::create('comments', function (Blueprint $table) {
    $table->id();
    $table->text('body');
    $table->morphs('commentable');
    $table->timestamps();
});

This will create two additional columns: commentable_type and commentable_id. The commentable_type column will store the class name of the related model and commentable_id stores the id of the related record. Using this setup, you can now post comments on any model that you set up a polymorphic relation with.

Retrieving And Using Polymorphic Relations

Let’s now see how to retrieve and work with this relationship. For example, to fetch comments related to a post:

$post = Post::find(1);
foreach ($post->comments as $comment) {
  echo $comment->body;
}

To do the same for videos:

$video = Video::find(1);
foreach ($video->comments as $comment) {
  echo $comment->body;
}

Advanced Relations: Morph Maps and Custom Keys

Laravel allows you to use custom names for the type field by using morph maps. This can help avoid fully-qualified class names being stored in your database. Let’s look at how to define and utilize a morph map:

use Illuminate\Database\Eloquent\Relations\Relation;

Relation::morphMap([
  'posts' => 'App\Post',
  'videos' => 'App\Video',
]);

Now, instead of Laravel storing the full model class name in the commentable_type, it will store the custom key from the morph map. Custom keys can also be set for IDs:

return $this->morphMany('App\Comment', 'commentable', 'model_type', 'model_id');

This changes the name of the columns Laravel looks for, from the default commentable_type and commentable_id to model_type and model_id.

Many-to-Many Polymorphic Relations

Eloquent also supports a ‘morphed by many’ relationship, which allows you to have a many-to-many polymorphic relation. This is typically used for scenarios like tags, which can belong to any number of different models. The setup looks like this:

class Tag extends Model
{
    public function posts()
    {
        return $this->morphedByMany('App\Post', 'taggable');
    }
    public function videos()
    {
        return $this->morphedByMany('App\Video', 'taggable');
    }
}

class Post extends Model
{
    public function tags()
    {
        return $this->morphToMany('App\Tag', 'taggable');
    }
}

class Video extends Model
{
    public function tags()
    {
        return $this->morphToMany('App\Tag', 'taggable');
    }
}

For storing data, you’ll need an intermediate table that might be named taggables, with a schema like this:

Schema::create('taggables', function (Blueprint $table) {
    $table->id();
    $table->morphs('taggable');
    $table->unsignedBigInteger('tag_id');
    $table->timestamps();
});

To retrieve all the tags associated with a post, you can simply use:

$post = Post::find(1);
foreach ($post->tags as $tag) {
  echo $tag->name;
}

Many-to-Many Polymorphic Relations: Custom Pivot Attributes

Custom pivot attributes can be accessed or assigned on a many-to-many polymorphic relation much in the same way as a standard many-to-many relation. This means you can have additional data about the relation in the pivot table and interact with it conveniently:

// When attaching a tag
$post->tags()->attach($tagId, ['custom_attribute' => 'value']);

// When retrieving pivot data
foreach ($post->tags as $tag) {
  echo $tag->pivot->custom_attribute;
}

Conclusion

Polymorphic relations in Eloquent are a powerful and flexible feature allowing for complex data structures and relationships to be elegantly managed within a Laravel application. They allow for more reusable and abstracted code and can significantly simplify your database design.