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!

LIVE MENTORSHIP ONLY 5 SPOTS

Laravel Mastery
Coaching Class Program

KritiMyantra

Transform from beginner to Laravel expert with our personalized Coaching Class starting June 21, 2025. Limited enrollment ensures focused attention.

Daily Sessions

1-hour personalized coaching

Real Projects

Build portfolio applications

Best Practices

Industry-standard techniques

Career Support

Interview prep & job guidance

Total Investment
$200
Duration
30 hours
1h/day

Enrollment Closes In

Days
Hours
Minutes
Seconds
Spots Available 5 of 10 remaining
Next cohort starts:
June 21, 2025

Join the Program

Complete your application to secure your spot

Application Submitted!

Thank you for your interest in our Laravel mentorship program. We'll contact you within 24 hours with next steps.

What happens next?

  • Confirmation email with program details
  • WhatsApp message from our team
  • Onboarding call to discuss your goals

Tags

Comments

No comments yet. Be the first to comment!

Please log in to post a comment:

Sign in with Google

Related Posts