Browse documentationOpen

byrcsc/laravel-comments · 1.x

Events and listeners.

Every event the package fires, what each carries, and the two rules that decide how you listen to them.

Every event carries the comment it happened to, so a listener that only cares which record moved can type-hint the base class CommentEvent and be done. Everything else — the thread, the commentable, the commentator — is derivable from the comment.

Events fire whether or not any of the package's own features are switched on. Your listeners are never coupled to its config.

The full list

Lifecycle

EventFires when
CommentCreatedA comment or reply is written, guest or not
CommentUpdatedAny saved change: a body edit, a transition, a pin, a restore
CommentDeletedA soft delete, and also a force delete
CommentRestoredA tombstone is restored
CommentForceDeletedA comment is removed for good

CommentForceDeleted additionally carries $countableRemoved: how many comments in the removed subtree were approved and not soft deleted. See deleting comments.

Moderation — base CommentModerated

EventFires when
CommentApprovedapprove() moved it
CommentRejectedreject() moved it
CommentMarkedAsSpammarkAsSpam() moved it

Each carries $previousStatus and $actor (null when nobody was named).

Reactions — base CommentReacted

EventFires when
ReactionAddedA reaction row actually appeared
ReactionRemovedOne actually went away

Each carries $reactor and $reaction.

Attachments — base CommentAttachmentChanged

EventFires when
AttachmentAddedA row is recorded
AttachmentRemoveddetach() removes one, or a force delete takes one

Each carries $attachment.

Pinning — base CommentPinChanged

EventFires when
CommentPinnedA pin actually went up
CommentUnpinnedOne actually came down

Each carries $actor.

Rule one: listen by concrete class

Laravel's dispatcher resolves listeners by interface but never by parent class. Listening to CommentModerated alone would never fire — name the concrete events and type-hint the base:

use ByRcsc\LaravelComments\Events\CommentApproved;
use ByRcsc\LaravelComments\Events\CommentMarkedAsSpam;
use ByRcsc\LaravelComments\Events\CommentModerated;
use ByRcsc\LaravelComments\Events\CommentRejected;
use Illuminate\Support\Facades\Event;

Event::listen(
    [CommentApproved::class, CommentRejected::class, CommentMarkedAsSpam::class],
    fn (CommentModerated $event) => $this->reindex($event->comment),
);

The same applies to CommentReacted, CommentAttachmentChanged, CommentPinChanged, and CommentEvent: useful as type hints, never as subscriptions.

Rule two: one event means one real change

Re-entering a status writes nothing and fires nothing. Pinning a pinned comment does the same. Reacting twice with the same reaction hands back the existing row and fires nothing. Detaching an attachment that is already gone fires nothing.

That is a guarantee you can build on: a listener counting approvals counts real ones, and a notification cannot double up under a double click.

The events also fire only once the write is known to have landed. A host saving listener that halts the save leaves nothing counting a state change the table never took.

Queue anything slow

Events fire inside whatever transaction caused them, synchronously. A listener that does anything slow or external should be queued, and a queued listener should dispatch after commit — the comment it was handed may still be rolled back:

use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Contracts\Events\ShouldDispatchAfterCommit;

class IndexComment implements ShouldQueue, ShouldDispatchAfterCommit
{
    public function handle(CommentCreated $event): void
    {
        // ...
    }
}

What fires and what does not

The lifecycle events ride Eloquent's own model events, so anything that skips those skips these: saveQuietly(), the query builder, raw SQL. The moderation, reaction, attachment, and pin events are dispatched by the package itself, immediately after the write.

CommentUpdated is the one to watch for edits, because it fires for every kind of update. Ask the comment which kind it was:

Event::listen(CommentUpdated::class, function (CommentUpdated $event): void {
    if ($event->comment->wasChanged('body')) {
        // an edit — the revision for it is already filed
    }
});

The package's own listeners

Two subscribers are registered unconditionally, and both ask their question per comment rather than at boot — so a config change takes effect without a reboot:

  • MaintainsCommentCounts keeps an opted-in commentable's count honest. A model that never opted in costs an array lookup. See comment counts.
  • SendsReplyNotifications tells a comment's author that somebody replied. Nothing is sent unless the config says so. See reply notifications.