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.
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:
<?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:
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:
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:
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:
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:
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:
$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.
Step 5: Retrieve Records
Records from a polymorphic relationship can be accessed as a property, like $post->comments
or $video->comments
, as shown below:
// 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:
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:
public static function boot()
{
static::deleting(function ($post) {
// Delete related comments
$post->comments()->delete();
});
}
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:
<?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:
<?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:
<?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 1 | Parent Model 2 | Child Model | Polymorphic Relationship |
Team | Project | Task | taskable |
Company | Employee | Review | reviewable |
Event | Article | Tag | taggeable |
Image | Document | Preview | previewable |
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
This entry is part 6 of 9 in the series Laravel Eloquent Relationships
- How to Use a One-to-One Relationship in Laravel
- How to Use a One-to-Many Relationship in Laravel
- How to Use a Many-to-Many Relationship in Laravel
- Using Extra Fields in Pivot Table With Laravel belongsToMany
- How to Use Has-Many-Through Relationships in Laravel
- How to use One-to-Many Polymorphic Relationship in Laravel
- How to use Many-to-Many Polymorphic Relationship in Laravel
- How to Paginate a Model with Relationship in Laravel
- How to Use `with()` to Eager Load Relationship in Laravel
Amazing guide Johan! It was simple to follow and understand and it’s great to see some automated advice as well!