lenorix / laravel-comments
Associate comments with Eloquent models
Fund package maintenance!
Requires
- php: ^8.3
- illuminate/contracts: ^11.0||^12.0||^13.0
- league/commonmark: ^2.4
- spatie/laravel-package-tools: ^1.16
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/pint: ^1.14
- nunomaduro/collision: ^8.8
- orchestra/testbench: ^11.0.0||^10.0.0||^9.0.0
- pestphp/pest: ^4.0
- pestphp/pest-plugin-arch: ^4.0
- pestphp/pest-plugin-laravel: ^4.0
- phpstan/extension-installer: ^1.4
- phpstan/phpstan-deprecation-rules: ^2.0
- phpstan/phpstan-phpunit: ^2.0
README
Associate comments with any Eloquent model. Comments can be nested (replies), reacted to with emoji, rendered from Markdown to sanitized HTML, held for moderation, mention other users, and notify people who subscribed to a model's comments.
Installation
Install the package via composer:
composer require lenorix/laravel-comments
Publish and run the migrations:
php artisan vendor:publish --tag="comments-migrations"
php artisan migrate
Publish the config file:
php artisan vendor:publish --tag="comments-config"
Setup
Add the HasComments trait to any model that should receive comments, and implement
commentableName() / commentUrl() — notifications use them to describe where a
comment was posted:
use Illuminate\Database\Eloquent\Model; use Lenorix\LaravelComments\Models\Concerns\HasComments; use Lenorix\LaravelComments\Models\Concerns\Interfaces\Commentable; class Post extends Model implements Commentable { use HasComments; public function commentableName(): string { return $this->title; } public function commentUrl(): string { return route('posts.show', $this); } }
If comments have an author (most apps do), prepare your user model and point the config at it:
use Illuminate\Foundation\Auth\User as Authenticatable; use Lenorix\LaravelComments\Models\Concerns\InteractsWithComments; use Lenorix\LaravelComments\Models\Concerns\Interfaces\CanComment; class User extends Authenticatable implements CanComment { use InteractsWithComments; }
// config/comments.php 'models' => [ 'commentator' => App\Models\User::class, ],
If you want to allow comments from guests (no logged-in user), set:
// config/comments.php 'allow_anonymous_comments' => true,
Usage
Creating and reading comments
$post->comment('Great read!'); // as the current authenticated user $post->comment('Great read!', $anotherUser); // on behalf of a specific user $post->comments; // all comments, replies included $post->comments()->topLevel()->get(); // only root comments, no replies
Replies
A Comment can itself receive comments, which makes it a reply:
$comment = $post->comment('Great read!'); $reply = $comment->comment('I agree!'); $comment->comments; // replies to this specific comment
Deleting a comment leaves its replies in place by default. Set
delete_replies_along_comments to true in the config to delete them too, at every
depth of nesting.
Reactions
$comment->react('👍'); $comment->react('👍', $anotherUser); $comment->deleteReaction('👍'); $comment->reactions->summary(); // [['reaction' => '👍', 'count' => 3, 'commentator_reacted' => true], ...]
Reacting always requires an identified commentator, even if allow_anonymous_comments
is true — react() throws AnonymousReactionsNotAllowed when no commentator can be
resolved. Guests can comment, but never react.
Only emoji listed in allowed_reactions are accepted — react() throws
DisallowedReaction otherwise. Set allowed_reactions to an empty array to allow any
reaction.
Markdown rendering and sanitizing
By default, original_text is rendered from Markdown into HTML and stored in text.
Any HTML tag or attribute not on the sanitizer's whitelist is stripped. Extend that
whitelist per tag in the config:
// config/comments.php 'allowed_attributes' => [ 'p' => ['data-test'], ],
Use text to display a comment, and original_text when editing it.
Syntax highlighting for code blocks is not bundled. Add it by writing a
CommentTransformer that runs after MarkdownToHtmlTransformer in
comment_transformers and rewrites $comment->text with whichever highlighter your
app already depends on.
Moderation
// config/comments.php 'automatically_approve_all_comments' => false,
$comment->isApproved(); $comment->isPending(); $comment->approve(); $comment->reject(); // deletes the comment Comment::approved()->get(); Comment::pending()->get();
Register who can approve pending comments, and expose the signed approve/reject routes:
// in a service provider PendingCommentNotification::sendTo(fn (Comment $comment) => User::where('is_admin', true)->get());
// in a routes file Route::comments();
Override shouldBeAutomaticallyApproved() on a custom Comment subclass for
fine-grained approval rules instead of the global config flag.
Notifications and subscriptions
use Lenorix\LaravelComments\Enums\NotificationSubscriptionType; $user->subscribeToCommentNotifications($post, NotificationSubscriptionType::All); $user->subscribeToCommentNotifications($post, NotificationSubscriptionType::Participating); $user->unsubscribeFromCommentNotifications($post); $user->unsubscribeFromAllCommentNotifications();
All subscribers hear about every new approved comment on the model. Participating
subscribers only hear about it if they have commented on that model themselves. The
author of a comment is never notified about their own comment.
Customize the sender and the mail's content:
// config/comments.php 'notifications' => [ 'mail' => [ 'from' => [ 'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'), 'name' => env('MAIL_FROM_NAME', 'Example'), ], ], ],
php artisan vendor:publish --tag="comments-views"
This publishes editable Blade templates to resources/views/vendor/comments/mail/.
The pending-comment mail includes working approve/reject buttons.
Mentions
Mentions are represented as <span data-mention="{id}">{name}</span> inside the
rendered text. Enable them and pick a raw-input convention (the default recognizes
@[Name](id)):
// config/comments.php 'mentions' => [ 'enabled' => true, ],
$comment->mentionedCommentators(); // commentator models mentioned in this comment
Mentioned commentators are notified once the comment is approved, not while pending.
Authorization
The package ships a CommentPolicy, automatically bound to your configured Comment
model. Extend it to customize create, update, delete, react, see, approve
and reject rules:
// config/comments.php 'policies' => [ 'comment' => App\Policies\CustomCommentPolicy::class, ],
Events
CommentCreated and CommentDeleted are dispatched from the Comment model's own
created/deleted lifecycle, so they fire no matter how the row came to exist or
disappear — a top-level comment, a reply, direct model calls, or the
delete_replies_along_comments cascade all dispatch them:
use Lenorix\LaravelComments\Events\CommentCreated; use Lenorix\LaravelComments\Events\CommentDeleted; Event::listen(function (CommentCreated $event) { // $event->comment });
CommentCreated fires even for a pending comment — check $event->comment->isApproved()
if you only care about visible ones.
Testing
composer test
Changelog
Please see CHANGELOG for more information on what has changed recently.
Security Vulnerabilities
Please review our security policy on how to report security vulnerabilities.
Credits
License
Released into the public domain under The Unlicense.