Published on 11 April 2014

Using Polymorphic Relations in Laravel

Let's say we're building a web application that enables users to post comments about movies; a very simple movie review system. The simplified UML class diagram to define the relations between the classes would look something like:

UML diagram

Very simple. The simplified SQL structure would be:

Table USERS:

Table MOVIES:

Table COMMENTS:

There's nothing wrong with this but if we think of expanding our application in the future we may encounter a few issues. What if we besides movies we want to have books? We should add a new class BOOK and the necessary relationships to enable users to post comments to books. A couple of ways to do that.

The first way would be to modify our existing COMMENTS table and to add a new BOOKS table:

BOOKS table:

Add a new foreign key to the COMMENTS table:

Not very pretty right? Each time we want to expand our application by adding a new class we need to add a new foreign key to the COMMENTS table. Ugly.

Another way would be to add a new BOOKS table and a new COMMENTS table where only book comments would be stored:

BOOKS table:

COMMENTS table:

Again, this is not a very pretty solution as each time we add a new class we also add a new table to store comments for this new class of objects. Movies have their own comments table, books have their own comments table and so on.. It's a pity because all these comments table have the same structure and it would be nice if they could be stored in the same table.

What if we could find a solution where we could avoid adding new tables or new foreign keys columns each time we expand our application? Polymorphic relations are the answer!

Table USERS:

Table MOVIES:

Table BOOKS table:

Table COMMENTS:

The table COMMENTS has a commentable_id pointing to Movies, Books or any other table we want. The commentable_type column stores the name of the table the commentable_id is pointing to. Sexy right? Each time we add a new class to our application we don't have to change the existing tables, just add the new table, in this case BOOKS.

If we are using Laravel migrations, the schema for comments is:

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;

class CreateCommentsTable extends Migration {

	/**
	 * Run the migrations.
	 *
	 * @return void
	 */
	public function up()
	{
		Schema::create('comments', function(Blueprint $table)
		{
			$table->increments('id');
			$table->integer('user_id')->unsigned();
			$table->foreign('user_id')->references('id')->on('users');
			$table->integer('commentable_id')->unsigned();
			$table->string('commentable_type');
			$table->text('body');
			$table->timestamps();
		});
	}


	/**
	 * Reverse the migrations.
	 *
	 * @return void
	 */
	public function down()
	{
		Schema::drop('comments');
	}

}

The comment model class, Comment.php:

<?php

class Comment extends \Eloquent {

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

    public function author()
    {
        return $this->belongsTo('User');
    }
}

The movie model class, Movie.php:

<?php

class Movie extends \Eloquent {

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

}

The book model class, Book.php:

<?php

class Book extends \Eloquent {

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

}

The cool thing is to navigate through the relationship with Laravel. Getting the comments from a given book:

$book = Book::find(1);

foreach ($book->comments as $comments)
{
    //do what you want with each comment
}

Or getting the commentable object of a given comment (book, movie, whatever..):

$comment = Comment::find(1);

$commentable = $comment->commentable;

Laravel's ORM Eloquent is smart enough to return a Book or a Movie, depending on what the comment is related to. Very cool right? Now, each time we add a new class we don't need to do anything besides coding the new class. The disadvantage is we loose the foreign key constraint from the COMMENTS table to the other tables but our application logic is in charge of managing this now.This is a great feature that can save you time and work while developing. You can check the official documentation to learn more about polymorphic relationships. Enjoy!