Creating Polymorphic Relations in Laravel 5

  • January 21, 2015

Welcome to the incredibly popular Easy Laravel 5 companion blog. To celebrate the new edition's release (updated for Laravel 5.5!) use the discount code easteregg to receive 20% off the book or book/video package! » Buy the book

When considering an interface for commenting on different types of application data (products and blog posts, for example), one might presume it is necessary to manage each type of comment separately. This approach would however be repetitive because each comment model would presumably consist of the same data structure. You can eliminate this repetition using a polymorphic relation, resulting in all comments being managed via a single model.

Incidentally, although the models found in this post using Laravel 5-specific syntax, Laravel 4 also supports polymorphic relations.

Let's work through an example that would use polymorphic relations to add anonymous commenting capabilities to the Product and Todolist models. Begin by creating a new model named Comment, done by creating a file named Comment.php and placing it in your project's app directory:

<?php namespace todoparrot;

use Illuminate\Database\Eloquent\Model;

class Comment extends Model {

  protected $fillable = [];

}

Next, generate the associated table:

$ php artisan make:migration --create=comments create_comments_table
Created Migration: 2015_01_20_223902_create_comments_table

Next, open up the newly generated migration file and modify the up() method to look like this:

Schema::create('comments', function(Blueprint $table)
{
  $table->increments('id');
  $table->text('body');
  $table->integer('commentable_id');
  $table->string('commentable_type');
  $table->timestamps();
});

Finally, save the changes and run the migration:

$ php artisan migrate
Migrated: 2015_01_20_223902_create_comments_table

Because the Comment model serves as a central repository for comments associated with multiple different models, we require a means for knowing both which model and which record ID is associated with a particular comment. The commentable_type and commentable_id fields serve this purpose. For instance, if a comment is associated with a list, and the list record associated with the comment has a primary key of 453, then the comment's commentable_type field will be set to Todolist and the commentable_id to 453.

Logically you'll want to attach other fields to the comments table if you plan on for instance assigning ownership to comments via the User model, or would like to include a title for each comment.

Next, open the Comment model and add the following method:

class Comment extends Model {

    public function commentable()
    {
      return $this->morphTo();
    }

}

The morphTo method defines a polymorphic relationship. Personally I find the name to be a poor choice; when you read it just think "belongs To" but for polymorphic relationships, since the record will belong to whatever model is defined in the commentable_type field. This defines just one side of the relationship; you'll also want to define the inverse relation within any model that will be commentable, creating a method that determines which model is used to maintain the comments, and referencing the name of the method used in the polymorphic model:

class Todolist extends Model {

  public function comments()
  {
    return $this->morphMany('\todoparrot\Comment', 'commentable');
  }

}

With these two methods in place, it's time to begin using the polymorphic relation! The syntax for adding, removing and retrieving comments is straightforward; in the following example we'll attach a new comment to a list:

$list = Todolist::find(1);

$c = new Comment();

$c->body = 'Great work!';

$list->comments()->save($c);

After saving the comment, review the database and you'll see a record that looks like the following:

mysql> select * from comments;
+----+-------------+----------------+---------------------+------------+------------+
| id | body        | commentable_id | commentable_type    | created_at | updated_at |
+----+-------------+----------------+---------------------+------------+------------+
|  1 | Great work! |              1 | todoparrot\Todolist | 2015-...   | 2015-...   |
+----+-------------+----------------+---------------------+------------+------------+

The list's comments are just a collection, so you can easily iterate over it. You'll retrieve the list within the controller per usual:

public function index()      
{
  $list = Todolist::find(1);
  return view('lists.show')->with('list', $list);
}

In the corresponding view you'll iterate over the comments collection:

  @foreach ($list->comments as $comment)
    <p>
      {{ $comment->body }}
    </p>
  @endforeach

Polymorphic relations can no doubt save a great deal of time and effort when you'd like models to share a particular feature such as comments or reviews. If you have other experience or insights using polymorphic relations be sure to share your thoughts in the comments!

Chapter 7 offers a comprehensive introduction to all Laravel relationship types, including polymorphic relations.

Buy the book