Production-ready comments system for Laravel: nested threads, reactions, guest & user support, polymorphic models, moderation, rate limiting
fiachehr/laravel-comments-pro is a Laravel package for production-ready comments system for laravel: nested threads, reactions, guest & user support, polymorphic models, moderation, rate limiting.
It currently has 4 GitHub stars and 343 downloads on Packagist (latest version v0.1.6).
Install it with composer require fiachehr/laravel-comments-pro.
Discover more Laravel packages by fiachehr
or browse all Laravel packages to compare alternatives.
Last updated
A comprehensive, feature-rich comments system for Laravel applications with support for nested comments, reactions, guest users, and advanced moderation features.
Full guide with examples and IDE-style code blocks: Laravel Comments Pro โ Documentation
| PHP Version | Laravel 10.x | Laravel 11.x | Laravel 12.x | | ----------- | ------------ | ------------ | ------------ | | 8.1 | โ Supported | โ | โ | | 8.2 | โ Supported | โ Supported | โ Supported | | 8.3 | โ Supported | โ Supported | โ Supported | | 8.4 | โ Supported | โ Supported | โ Supported |
Laravel 11 and 12 require PHP 8.2 or higher. Use PHP 8.1 only with Laravel 10.
โ ๏ธ This package is Laravel-specific and requires:
Not compatible with other frameworks (Symfony, CodeIgniter, etc.)
Before installing, ensure you have:
composer require fiachehr/laravel-comments-pro
Note: This package requires Laravel framework and is not compatible with other PHP frameworks.
php artisan vendor:publish --provider="Fiachehr\Comments\CommentsServiceProvider" --tag=comments-migrations
php artisan migrate
php artisan vendor:publish --provider="Fiachehr\Comments\CommentsServiceProvider" --tag=comments-config
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Fiachehr\Comments\Traits\HasComments;
// Works with any model - Post, Article, Product, etc.
class Post extends Model
{
use HasComments;
// Your model code...
}
// Example with different models
class Article extends Model
{
use HasComments;
}
class Product extends Model
{
use HasComments;
}
use Fiachehr\Comments\Facades\Comments;
// Works with any model that uses HasComments trait
$post = Post::find(1);
$article = Article::find(1);
$product = Product::find(1);
// For authenticated users
$comment = Comments::create([
'body' => 'This is a great post!',
], $post);
// For guest users
$comment = Comments::create([
'body' => 'Nice article!',
'guest_name' => 'John Doe',
'guest_email' => '[email protected]',
], $article);
// Nested comments
$reply = Comments::create([
'body' => 'Thanks for the comment!',
'parent_id' => $comment->id,
], $product);
use Fiachehr\Comments\Facades\Comments;
$comment = Comments::approve($comment);
use Fiachehr\Comments\Facades\Reactions;
use Fiachehr\Comments\Enums\ReactionType;
// Like a comment
$reaction = Reactions::toggle($comment, ReactionType::LIKE);
// Dislike a comment
$reaction = Reactions::toggle($comment, ReactionType::DISLIKE);
use Fiachehr\Comments\Facades\Comments;
$comments = $post->comments()->approved()->get();
$tree = Comments::toTree($comments);
// config/comments.php (after publishing)
return [
'route_prefix' => 'api/comments',
'middleware' => ['api', 'throttle:60,1'],
'max_depth' => 5,
'auto_approve_authenticated' => true,
'reply_only_to_approved_parent' => true,
'guests' => [
'allowed' => true,
'require_email' => true,
'cookie_name' => 'guest_fingerprint',
],
];
Per-model settings are not defined on the trait; adjust behavior in config/comments.php (or override config per environment).
use Fiachehr\Comments\Services\CommentsService;
use Fiachehr\Comments\Services\ReactionService;
// Direct service usage
$commentsService = app(CommentsService::class);
$comment = $commentsService->createComment($data, $post);
$reactionService = app(ReactionService::class);
$reaction = $reactionService->toggleReaction($comment, ReactionType::LIKE);
use Fiachehr\Comments\Events\CommentCreated;
use Fiachehr\Comments\Events\CommentApproved;
use Fiachehr\Comments\Events\ReactionToggled;
// Listen to events
Event::listen(CommentCreated::class, function (CommentCreated $event) {
// Handle new comment
$comment = $event->comment;
// Send notification, log activity, etc.
});
// Get approved comments
$approvedComments = $post->comments()->approved()->get();
// Get comments with reactions
$commentsWithReactions = $post->comments()->withReactions()->get();
// Get popular comments (service)
$popularComments = app(ReactionService::class)->getPopularComments(10, '7 days');
// Or via facade
$popularComments = Reactions::getPopular(10, '7 days');
use Fiachehr\Comments\Facades\Reactions;
// Bulk reactions
$results = Reactions::bulkToggle([1, 2, 3], ReactionType::LIKE);
// Get reaction statistics
$stats = Reactions::getStats($comment);
// Returns: ['likes' => 5, 'dislikes' => 2, 'total' => 7]
<?php
namespace App\Http\Controllers;
use App\Http\Requests\StoreCommentRequest;
use Fiachehr\Comments\Services\CommentsService;
use Fiachehr\Comments\Services\ReactionService;
use Fiachehr\Comments\Enums\ReactionType;
use Illuminate\Database\Eloquent\Model;
class CommentController extends Controller
{
public function __construct(
private CommentsService $commentsService,
private ReactionService $reactionService
) {}
// Works with any model that uses HasComments trait
public function store(StoreCommentRequest $request, Model $commentable)
{
$comment = $this->commentsService->createComment($request->validated(), $commentable);
return response()->json([
'success' => true,
'comment' => $comment,
]);
}
public function approve(Comment $comment)
{
$approvedComment = $this->commentsService->approveComment($comment);
return response()->json([
'success' => true,
'comment' => $approvedComment,
]);
}
public function react(Comment $comment, string $type)
{
$reactionType = ReactionType::from($type);
$reaction = $this->reactionService->toggleReaction($comment, $reactionType);
return response()->json([
'success' => true,
'reaction' => $reaction,
]);
}
// Works with any commentable model
public function tree(Model $commentable)
{
$comments = $commentable->comments()->approved()->get();
$tree = $this->commentsService->toTree($comments);
return response()->json($tree);
}
}
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Fiachehr\Comments\Rules\GuestFingerprint;
class StoreCommentRequest extends FormRequest
{
public function rules()
{
return [
'body' => 'required|string|max:1000',
'parent_id' => 'nullable|exists:comments,id',
'guest_name' => 'required_if:user_id,null|string|max:255',
'guest_email' => 'required_if:user_id,null|email|max:255',
'guest_fingerprint' => ['nullable', new GuestFingerprint()],
];
}
}
# Run all tests
./vendor/bin/phpunit tests/Unit/
If you want to use php artisan test in your Laravel project, add this to your phpunit.xml:
<testsuite name="Comments">
<directory suffix="Test.php">./vendor/fiachehr/laravel-comments-pro/tests/Unit</directory>
</testsuite>
Then run: php artisan test --testsuite=Comments
// CommentComponent.vue
<template>
<div class="comments">
<div v-for="comment in comments" :key="comment.id" class="comment">
<div class="comment-body">{{ comment.body }}</div>
<div class="comment-actions">
<button @click="toggleReaction(comment, 'like')">
๐ {{ comment.likes }}
</button>
<button @click="toggleReaction(comment, 'dislike')">
๐ {{ comment.dislikes }}
</button>
</div>
<div v-if="comment.children" class="replies">
<CommentComponent :comments="comment.children" />
</div>
</div>
</div>
</template>
<script>
export default {
props: ['comments'],
methods: {
async toggleReaction(comment, type) {
try {
const response = await axios.post(`/comments/${comment.id}/react`, {
type: type
});
// Update UI
} catch (error) {
console.error('Failed to toggle reaction:', error);
}
}
}
}
</script>
// CommentComponent.jsx
import React, { useState } from 'react';
import axios from 'axios';
const CommentComponent = ({ comments }) => {
const [reactions, setReactions] = useState({});
const toggleReaction = async (commentId, type) => {
try {
const response = await axios.post(`/comments/${commentId}/react`, {
type: type
});
setReactions(prev => ({
...prev,
[commentId]: response.data.reaction
}));
} catch (error) {
console.error('Failed to toggle reaction:', error);
}
};
return (
<div className="comments">
{comments.map(comment => (
<div key={comment.id} className="comment">
<div className="comment-body">{comment.body}</div>
<div className="comment-actions">
<button onClick={() => toggleReaction(comment.id, 'like')}>
๐ {comment.likes}
</button>
<button onClick={() => toggleReaction(comment.id, 'dislike')}>
๐ {comment.dislikes}
</button>
</div>
{comment.children && (
<CommentComponent comments={comment.children} />
)}
</div>
))}
</div>
);
};
export default CommentComponent;
// Add custom indexes for better performance
Schema::table('comments', function (Blueprint $table) {
$table->index(['commentable_type', 'commentable_id', 'status']);
$table->index(['parent_id', 'depth']);
$table->index(['created_at', 'status']);
});
use Illuminate\Support\Facades\Cache;
// Cache popular comments
$popularComments = Cache::remember('popular_comments', 3600, function () {
return app(ReactionService::class)->getPopularComments(10);
});
// Cache comment trees
$commentTree = Cache::remember("comments_tree_{$post->id}", 1800, function () use ($post) {
return app(CommentsService::class)->toTree($post->comments()->approved()->get());
});
// Load comments with relationships
$comments = $post->comments()
->with(['user', 'reactions', 'children'])
->approved()
->get();
Compatibility Issues
# Check PHP version
php --version
# Check Laravel version
php artisan --version
# Ensure minimum requirements are met
# PHP >= 8.1 (8.2+ for Laravel 11/12), Laravel 10โ12
Migration Errors
# Clear cache and re-run migrations
php artisan cache:clear
php artisan config:clear
php artisan migrate:fresh
Factory Not Found
// Make sure to publish factories
php artisan vendor:publish --provider="Fiachehr\Comments\CommentsServiceProvider" --tag=comments-factories
Event Not Firing
// Check if events are registered in EventServiceProvider
protected $listen = [
CommentCreated::class => [
// Your listeners
],
];
Framework Compatibility
// This package only works with Laravel
// Not compatible with: Symfony, CodeIgniter, etc.
// Requires: Laravel 10โ12 (PHP 8.2+ for Laravel 11/12)
// Enable debug logging
Log::info('Comment created', ['comment' => $comment->toArray()]);
Log::info('Reaction toggled', ['reaction' => $reaction->toArray()]);
// Track comment metrics
$stats = [
'total_comments' => Comment::count(),
'approved_comments' => Comment::approved()->count(),
'pending_comments' => Comment::where('status', 'pending')->count(),
'total_reactions' => Reaction::count(),
'popular_posts' => app(ReactionService::class)->getPopularComments(5),
];
// Check system health
$health = [
'database_connection' => DB::connection()->getPdo() !== null,
'migrations_up_to_date' => !Artisan::call('migrate:status'),
'config_loaded' => config('comments') !== null,
'services_registered' => app()->bound(CommentsService::class),
];
git checkout -b feature/amazing-feature)git commit -m 'Add some amazing feature')git push origin feature/amazing-feature)This package is open-sourced software licensed under the MIT license.
Made with โค๏ธ by Fiachehr