Skip to content

Repository files navigation

Laravel Comments

Latest Version on Packagist GitHub Tests Action Status Total Downloads

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.

About

Something like spatie/laravel-comments but created for public domain

Resources

Stars

0 stars

Watchers

0 watching

Forks

Used by

Contributors

Languages