Implementing a Polymorphic Comment System in Laravel 12: A Step-by-Step Guide

Author

Kritim Yantra

Apr 06, 2025

Implementing a Polymorphic Comment System in Laravel 12: A Step-by-Step Guide

Introduction

Polymorphic comments allow users to comment on different types of content (Posts, Videos, Products) using a single comments table. This is a clean, efficient approach compared to creating separate comment tables for each content type.

In this guide, we'll build a complete polymorphic comment system with:

  • Database structure for polymorphic relationships
  • Commentable models (Post, Video)
  • Comment creation and display
  • Frontend interface with Blade
  • Best practices for implementation

Step 1: Database Setup

1.1 Create the Comments Migration

php artisan make:migration create_comments_table
Schema::create('comments', function (Blueprint $table) {
    $table->id();
    $table->text('body');
    $table->unsignedBigInteger('user_id'); // Comment author
    $table->unsignedBigInteger('commentable_id'); // Polymorphic ID
    $table->string('commentable_type'); // Polymorphic class
    $table->timestamps();
    
    $table->foreign('user_id')->references('id')->on('users');
});

1.2 Create Sample Content Tables

php artisan make:migration create_posts_table
php artisan make:migration create_videos_table
// Posts table
Schema::create('posts', function (Blueprint $table) {
    $table->id();
    $table->string('title');
    $table->text('content');
    $table->timestamps();
});

// Videos table
Schema::create('videos', function (Blueprint $table) {
    $table->id();
    $table->string('title');
    $table->string('url');
    $table->timestamps();
});

Run migrations:

php artisan migrate

Step 2: Model Setup

2.1 Comment Model

php artisan make:model Comment
class Comment extends Model
{
    protected $fillable = ['body', 'user_id'];
    
    public function commentable()
    {
        return $this->morphTo();
    }
    
    public function user()
    {
        return $this->belongsTo(User::class);
    }
}

2.2 Make Models Commentable

// Post.php
class Post extends Model
{
    public function comments()
    {
        return $this->morphMany(Comment::class, 'commentable');
    }
}

// Video.php
class Video extends Model
{
    public function comments()
    {
        return $this->morphMany(Comment::class, 'commentable');
    }
}

Step 3: Create Comment Controller

php artisan make:controller CommentController
class CommentController extends Controller
{
    public function store(Request $request)
    {
        $request->validate([
            'body' => 'required',
            'commentable_id' => 'required',
            'commentable_type' => 'required|in:App\Models\Post,App\Models\Video'
        ]);
        
        $commentable = $request->commentable_type::find($request->commentable_id);
        
        $comment = $commentable->comments()->create([
            'body' => $request->body,
            'user_id' => auth()->id()
        ]);
        
        return back()->with('success', 'Comment added!');
    }
    
    public function destroy(Comment $comment)
    {
        $this->authorize('delete', $comment);
        $comment->delete();
        return back()->with('success', 'Comment deleted!');
    }
}

Step 4: Set Up Routes

// web.php
Route::post('/comments', [CommentController::class, 'store'])->name('comments.store');
Route::delete('/comments/{comment}', [CommentController::class, 'destroy'])->name('comments.destroy');

Step 5: Create Views

5.1 Comment Form Partial

<!-- resources/views/comments/_form.blade.php -->
@auth
<form action="{{ route('comments.store') }}" method="POST">
    @csrf
    <input type="hidden" name="commentable_id" value="{{ $model->id }}">
    <input type="hidden" name="commentable_type" value="{{ get_class($model) }}">
    
    <div class="mb-3">
        <textarea name="body" class="form-control" placeholder="Add a comment..."></textarea>
    </div>
    <button type="submit" class="btn btn-primary">Post Comment</button>
</form>
@endauth

5.2 Comments List Partial

<!-- resources/views/comments/_list.blade.php -->
<div class="comments mt-4">
    @foreach($model->comments()->with('user')->latest()->get() as $comment)
    <div class="card mb-3">
        <div class="card-body">
            <div class="d-flex justify-content-between">
                <h6>{{ $comment->user->name }}</h6>
                <small>{{ $comment->created_at->diffForHumans() }}</small>
            </div>
            <p>{{ $comment->body }}</p>
            
            @can('delete', $comment)
            <form action="{{ route('comments.destroy', $comment) }}" method="POST">
                @csrf @method('DELETE')
                <button type="submit" class="btn btn-sm btn-danger">Delete</button>
            </form>
            @endcan
        </div>
    </div>
    @endforeach
</div>

5.3 Using in Post/Video Views

<!-- resources/views/posts/show.blade.php -->
@extends('layouts.app')

@section('content')
<article>
    <h1>{{ $post->title }}</h1>
    <p>{{ $post->content }}</p>
    
    <!-- Include comments -->
    @include('comments._list', ['model' => $post])
    @include('comments._form', ['model' => $post])
</article>
@endsection

Step 6: Add Authorization

Create a CommentPolicy:

php artisan make:policy CommentPolicy --model=Comment
class CommentPolicy
{
    public function delete(User $user, Comment $comment)
    {
        return $user->id === $comment->user_id;
    }
}

Register in AuthServiceProvider:

protected $policies = [
    Comment::class => CommentPolicy::class,
];

Step 7: Testing the System

  1. Create test data in Tinker:
$post = Post::create(['title' => 'First Post', 'content' => 'Post content']);
$video = Video::create(['title' => 'Demo Video', 'url' => 'video1.mp4']);

// Add comments
$post->comments()->create(['body' => 'Great post!', 'user_id' => 1]);
$video->comments()->create(['body' => 'Nice video!', 'user_id' => 1]);
  1. Visit /posts/1 and /videos/1 to see comments working for both types.

Advanced Enhancements

1. Add Comment Replies

// Add to comments table
$table->unsignedBigInteger('parent_id')->nullable();

// In Comment model
public function replies()
{
    return $this->hasMany(Comment::class, 'parent_id');
}

2. Add Comment Notifications

// In CommentController
$commentable->user->notify(new NewCommentNotification($comment));

3. Implement Live Updates

Use Laravel Echo with WebSockets to show new comments in real-time.

Conclusion

You've now built a fully functional polymorphic comment system that can:
✅ Attach to any model (Posts, Videos, etc.)
✅ Handle user authorization
✅ Display threaded comments
✅ Scale easily with new content types

🚀 Your application now has a professional, flexible commenting system!

Tags

Comments

No comments yet. Be the first to comment!

Please log in to post a comment:

Sign in with Google

Related Posts