Browse documentationOpen

byrcsc/laravel-comments · 1.x

Troubleshooting.

The exceptions the package throws, what each one means, and the symptoms that are not exceptions at all.

Every exception the package throws extends CommentsException, so one catch covers all of them:

use ByRcsc\LaravelComments\Exceptions\CommentsException;

try {
    $post->comment($request->input('body'), by: $request->user());
} catch (CommentsException $e) {
    return back()->withErrors(['body' => $e->getMessage()]);
}

Configuration

InvalidConfigurationException on boot. Something in config/comments.php is missing or malformed: a blank or missing table name, an unknown actor_key_type, a negative max_depth, a non-integer max_length, an unknown status, a malformed allowed_reactions, a non-string attachment disk, or an empty notification channel list.

The message names the key and what it received. Fix the config; these are checked at boot precisely so they do not surface as a confusing query error long afterwards.

"allowed_reactions is missing." Absent is not the same as null. A published config predating the key would otherwise turn the allowlist off silently, so the package asks you to add it — set it to null if you meant "accept anything".

Writing

CommentableNotPersistedException. You called comment() on a model that has not been saved. Save it first.

ThreadTooDeepException. The reply would sit below comments.max_depth. Nothing was written. Raise the limit, set it to null, or reply higher up the thread.

BodyTooLongException. The body exceeds comments.max_length, counted in characters. Nothing was written and nothing was truncated.

LogicException: Cannot reply to an unsaved comment. Persist the parent first.

Editing

CommentTrashedException on edit. A soft-deleted comment cannot be rewritten. Restore it first if the edit is legitimate.

LogicException: Cannot edit the body of a comment loaded without it. The comment was selected without its body column, so there is nothing truthful to file as a revision. Select the column, or reload the comment.

RevisionIsAppendOnlyException. Revisions refuse updates. Nothing in the package writes to an existing revision, and neither should your application.

Reactions

InvalidReactionException. The reaction is blank, longer than 64 characters, or outside comments.allowed_reactions. Nothing was stored.

If a reaction that looks right is refused, check the exact string: emoji that can be written more than one way — a base character plus a variation selector, say — are different reactions to the allowlist and to the unique index alike.

CommentTrashedException on react or unreact. A tombstone keeps the reactions it had and takes no changes, in either direction.

LogicException: Cannot change reactions on an unsaved comment. Persist it first.

Attachments

InvalidAttachmentException. A blank path, a blank name, a negative size, or an attachment that belongs to a different comment.

ImageSupportMissingException. attachImage() needs the framework's Image facade — Laravel 13 and up — and intervention/image:

composer require intervention/image

Nothing else in the package needs it; attach() works without it.

AttachmentStorageFailedException. The framework's image pipeline could not write to the disk. Check the disk name, the directory, and its permissions.

CommentTrashedException on attach or detach. A tombstone takes no attachment changes.

Counts

CommentsCountNotEnabledException. recountComments() was called on a model that keeps no count. Return a column name from commentsCountColumn() to opt it in, and add the column. See comment counts.

The count is wrong and no exception was thrown. Something wrote comments around Eloquent's model events — the query builder, an upsert, raw SQL, a restored dump. Run the repair:

php artisan comments:recount --dry-run
php artisan comments:recount

comments:recount reports nothing for a model you expected. A full sweep visits the types the comments table mentions plus your morph map. A model whose comments were all hard deleted, in an application with no morph map, is invisible to it — name it with --model.

Testing

NotFakeableException. Comments::fake() records comments, replies, and reactions, and refuses everything else. Moderating, editing, pinning, attaching, and deleting need a real database. See testing.

A relation or scope returns nothing under the fake. Reads are not faked. Ask the recorder — commentsOn(), repliesTo(), reactionsOn() — rather than the database.

Symptoms that are not exceptions

Pending or rejected comments appear on a public page. The query is missing approved(). Status is not visibility; the scope is. See rendering and safety.

A transition returned false and nothing happened. The comment was already in that status. Re-entering a status writes nothing and fires nothing, by design.

No revision was recorded for an edit. The write skipped Eloquent's model events — saveQuietly(), the query builder, raw SQL. So did the count maintenance and the events. See edits and revisions.

No reply notification arrived. Check, in order: is comments.notifications.reply.enabled true; is the reply approved; is the parent comment authored by a model rather than a guest; is that model Notifiable; and is the replier somebody other than the parent's author. reply_notified_at on the reply tells you whether a send was already attempted — it is written once and never cleared. See reply notifications.

A moderation listener never fires. You listened to CommentModerated. Laravel's dispatcher resolves listeners by interface but never by parent class: name the concrete events and type-hint the base. See events and listeners.

Replies vanished after a delete. forceDelete() takes the whole reply subtree through the database's cascade. delete() does not — it leaves replies readable under a tombstone. See deleting comments.

A listener never heard about a force-deleted reply. The cascade fires no model events. AttachmentRemoved is swept across the whole subtree by the package, and CommentForceDeleted carries $countableRemoved for counts; anything else you keep per comment has to walk the subtree before deleting.

Pinned comments sort differently on another database. They should not — pinnedFirst() spells the null test out rather than leaving it to the driver. If ordering differs, check that the query is using the scope rather than a hand-written orderBy('pinned_at').