Browse documentationOpen

byrcsc/laravel-comments · 1.x

Threads and replies.

Build threads with reply(), read them with the relations, and understand the depth limit and what it does and does not reshape.

A thread is a comment and everything hanging off its parent_id. Replies are ordinary comments with a parent, on the same commentable, carrying their own status, reactions, revisions, and attachments.

Reply to a comment

$reply = $comment->reply('Agreed, especially the last section.', by: $teammate);

$guestReply = $comment->replyAsGuest(
    'Same question here.',
    name: 'Jane',
    email: 'jane@example.com',
);

Both mirror the write methods on the commentable, and both take the parent's commentable_type and commentable_id automatically — a reply is always on the same record as the comment it answers.

Replying to an unsaved comment throws a LogicException. Persist the parent first.

Read a thread

Load thread starters with their replies, nesting to the depth your interface renders:

$threads = $post->comments()
    ->approved()
    ->topLevel()
    ->with('replies.replies')
    ->get();

replies is direct replies only. There is no recursive relation and no materialized path column: the package stores the parent key and leaves the shape of the query to you, because a two-level interface and a fully nested one want different things.

For a flat list — a moderation queue, an activity feed — skip topLevel() entirely and read every comment at once.

Ask how deep a comment sits

$comment->depth();  // 0 at the top, one more per level above

The walk follows parent up the chain, so it costs a query per level unless the parents are already loaded. It reads the chain rather than a stored column, which means it cannot disagree with the table.

The depth limit

comments.max_depth caps how deep replies nest. It defaults to 3, so a top-level comment sits at depth 0 and replies are allowed at depths 1, 2, and 3.

'max_depth' => 3,   // null for unlimited

Creating a reply below the limit throws a ThreadTooDeepException and stores nothing:

use ByRcsc\LaravelComments\Exceptions\ThreadTooDeepException;

try {
    $deep->reply('One level too far', by: $user);
} catch (ThreadTooDeepException $e) {
    // Nothing was written.
}

Two things about the limit are worth knowing before you change it.

It applies at creation only. Tightening the limit never reshapes existing threads: comments already deeper than the new value stay exactly where they are, and stay readable. Only new replies are refused.

It applies to every write path. The check runs on Eloquent's creating event, so factories and Comments::fake() are held to it as well as reply(). A test cannot build a thread the engine would refuse.

The body length limit

comments.max_length is the other creation-time gate, in characters:

'max_length' => 2_000,   // null for no limit beyond the column

Exceeding it throws a BodyTooLongException and stores nothing. Multibyte characters count as one, and nothing is ever silently truncated. The same check runs again on an edit, so an edit cannot do what a write was refused — see edits and revisions.

Threads and deletion

Soft deleting a comment leaves its replies readable under a tombstone: the parent is hidden by Eloquent's global scope, the children are not. Force deleting takes the whole subtree with it through the database's cascade on parent_id.

That asymmetry is deliberate, and it has consequences for events and counts. See deleting comments.