How to use One-to-Many Polymorphic Relationship in Laravel

Laravel comes with built-in support for defining and querying One-to-Many Polymorphic Relationships. This blog post will explain what this relationship means exactly and which steps are needed to use it.

What is a One-to-Many Polymorphic Relationship?

A One-to-Many polymorphic relationship allows a single table to be associated with multiple models. For example, a Comment might be associated with either a Video or a Post, where Video and Post are the parent classes and Comment would be the child class.

Diagram of Tables Involved in One-to-Many Polymorphic Relationship : postscomments and videos

In the following step-by-step guide, we will learn how to define a Comment model and its related models Post and Video that can be commented on. As a bonus step, we will learn how to move common code into a Trait class so that we don’t need to duplicate it.

Let’s dive in and discover how to code this!

Step 1: Create and Run the Migrations

Using the make:migration Artisan command, create the migration files for each entity:

php artisan make:migration create_posts_table --create=posts
php artisan make:migration create_videos_table --create=videos
php artisan make:migration create_comments_table --create=comments

Now, modify the migration files with the following code samples:

For the posts table migration:

database/migrations/2023_07_23_161224_create_posts_table.php
<?php

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

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->id();
            $table->string('title');
            $table->string('content')->nullable();
            // Add other columns as needed
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('posts');
    }
};

For the videos table migration:

database/migrations/2023_07_23_161324_create_videos_table.php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('videos', function (Blueprint $table) {
            $table->id();
            $table->string('title');
            // Add other columns as needed
            $table->timestamps();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('videos');
    }
};

For the comments table migration:

database/migrations/2023_07_23_161424_create_comments_table.php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('comments', function (Blueprint $table) {
            $table->id();
            $table->text('content');
            $table->unsignedBigInteger('commentable_id');
            $table->string('commentable_type');
            $table->timestamps();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('comments');
    }
};

Step 2: Create the Models

Now, create the models for Post, Video, and Comment using the make:model Artisan command:

php artisan make:model Post
php artisan make:model Video
php artisan make:model Comment

Step 3: Define the Polymorphic Relationship

A One-to-Many Polymorphic Relationship is defined by adding morphMany() to the Comment model and addding morphTo() to the related Models: Post and Video for the inverse of the relation.

Let’s add the necessary code.

For the Post model, use:

app/Models/Post.php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'title', 'content'
    ];

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

For the Video model, use:

app/Models/Video.php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Video extends Model
{
    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'title'
    ];

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

For the Comment model, use:

app/Models/Comment.php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Comment extends Model
{
    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'content'
    ];

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

Step 4: Create Records

With the relationship defined, you can now create comments for both Post and Video models. For example:

PHP
$post = Post::create([
    'title' => 'Test Post'
]);

$video = Video::create([
    'title' => 'Test Video'
]);

// Creating a comment for a post
$post->comments()->create(['content' => 'This is a comment on a post.']);

// Creating a comment for a video
$video->comments()->create(['content' => 'This is a comment on a video.']);

// Alternative: Using ->save instead of ->create
$anotherComment = new Comment();
$anotherComment->content = "Lorem ipsum";
$post->comments()->save($anotherComment);

Viewing the database confirms it is all saved correctly.

Screenshot of MySQL Command Line Client Showing the Contents of videos, posts and comments

Step 5: Retrieve Records

Records from a polymorphic relationship can be accessed as a property, like $post->comments or $video->comments, as shown below:

PHP
// Retrieving comments for a post
$post = Post::first();
$postComments = $post->comments;
foreach ($post->comments as $postComment) {
    dump($postComment->attributesToArray());
}

// Retrieving comments for a video
$video = Video::first();
$videoComments = $video->comments;
foreach ($videoComments as $videoComments) {
    dump($videoComments->attributesToArray());
}

If we run this code we’d see:

Screenshot of the Browser Running our Example Code

Step 6: Handle Polymorphic Deletion

Whenever we use a Polymorphic Relationship we can’t use the database’s cascade delete of related records. For this reason when you delete a Post or Video, you should first delete the associated comments, like for example:

$post = Post::find(1); 
$post->comments()->delete();
$post->delete();

To do this automatically whenever you delete a Post or a Video you can also add to the Models:

app/Models/Post.php
public static function boot()
{
    static::deleting(function ($post) {
        // Delete related comments
        $post->comments()->delete();
    });
}
app/Models/Video.php
public static function boot()
{
    static::deleting(function ($video) {
        // Delete related comments
        $video->comments()->delete();
    });
}

Step 7: Improve the Code by Using a Trait (Optional)

Since both Models Video and Post use the same code for defining the morphMany relationship to the Comment model and the same code for running the static.deleting(..) method, we should clean up our code by moving the common code into a trait called “Commandable” instead of duplicating it in both Models.

To do this, create a file Commentable.php inside the folder app/Traits/. By default, this folder isn’t created by Laravel so you should create it yourself. Inside use the following code:

app/Traits/Commentable.php
<?php

namespace App\Traits;

use App\Models\Comment;

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

    public static function bootCommentable()
    {
        static::deleting(function ($model) {
            $model->comments()->delete();
        });
    }
}

Now edit the Model Video by using:

app/Models/Video.php
<?php

namespace App\Models;

use App\Traits\Commentable;
use Illuminate\Database\Eloquent\Model;

class Video extends Model
{
    use Commentable;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'title'
    ];
}

And lastly edit the Model Post by using:

app/Models/Post.php
<?php

namespace App\Models;

use App\Traits\Commentable;
use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    use Commentable;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'title', 'content'
    ];
}

Further Examples of One-to-Many Polymorphic Relationships

In addition to the comment on posts and videos example, we have used in this tutorial, there are various other scenarios where One-to-Many Polymorphic Relationships can be applied. Here are a few more examples:

Parent Model 1Parent Model 2Child ModelPolymorphic Relationship
TeamProjectTasktaskable
CompanyEmployeeReviewreviewable
EventArticleTagtaggeable
ImageDocumentPreviewpreviewable
Table Showing Example Models Which Can Have a One-to-Many Relationship

Conclusion

We’ve learned how to use One-to-Many Polymorphic Relationships in Laravel to define flexible associations between models and a common parent. By defining a single comments table that can be linked to multiple models, such as Video or Post, we achieved a seamless commenting feature.

Additionally, we explored handling polymorphic deletion and optimizing code organization using Traits. With this knowledge, you can improve the code of your Laravel applications by avoiding repetition and applying a feature to several entities at the same time. Happy coding!

References

Johan van den Broek

Johan is the creator of laracoding.com. As a child, he began tinkering with various programming languages, many of which have been long forgotten today. Currently, he works exclusively with PHP and Laravel, and his passion for programming remains to this day.

One thought on “How to use One-to-Many Polymorphic Relationship in Laravel

  1. Amazing guide Johan! It was simple to follow and understand and it’s great to see some automated advice as well!

Leave a Reply

Your email address will not be published. Required fields are marked *

Recent Posts