Browse documentationOpen

byrcsc/laravel-comments · 1.x

Commentable models.

The HasComments trait, writing comments as a model or as a guest, and the columns a comment carries.

Any Eloquent model becomes commentable by adding one trait. This page covers what that trait gives you and what a comment row holds.

use ByRcsc\LaravelComments\Concerns\HasComments;
use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    use HasComments;
}

There is no registration step and no interface to implement. The trait adds a polymorphic relation and two write methods.

Write a comment

comment() takes a body and the model writing it:

$comment = $post->comment('Great write-up!', by: $user);

$by is any Eloquent model. The package stores commentator_type and commentator_id as a morph and never assumes a user class.

commentAsGuest() takes a name and an email instead:

$comment = $post->commentAsGuest(
    'Where can I download the slides?',
    name: 'Jane',
    email: 'jane@example.com',
);

A comment carries exactly one of the two identities. A guest comment has a null commentator_type, which is how the package tells them apart — and why guest comments have their own default status, cannot react, and are never notified.

Both methods throw a CommentableNotPersistedException when the commentable has not been saved yet. There is nothing for the comment to point at.

Read comments

$post->comments;              // every comment, any status, any depth
$post->comments()->count();

The relation is a plain MorphMany, so everything you know about Eloquent applies. Narrow it with the scopes:

$post->comments()->approved()->topLevel()->with('replies')->get();
ScopeReturns
topLevel()Thread starters — comments with no parent
pending()Comments waiting on a moderator
approved()Comments in the approved set
rejected()Comments a moderator turned down
spam()Comments marked as spam, kept apart from rejected
pinned()Comments held at the top of their thread
pinnedFirst()Ordering: pinned first, most recently pinned among them

Soft-deleted comments are excluded by Eloquent's own global scope; reach them with withTrashed(). See deleting comments.

What a comment holds

ColumnMeaning
commentable_type, commentable_idThe record being commented on
commentator_type, commentator_idWho wrote it, or null for a guest
guest_name, guest_emailThe guest identity, or null for a model-authored comment
parent_idThe comment being replied to, or null at the top
bodyStored verbatim, untrusted input
statuspending, approved, rejected, or spam
edited_atWhen the body last changed, or null
pinned_atWhen it was pinned, or null
reply_notified_atWhen its author was told about it, or null
deleted_atSoft-delete tombstone marker

status casts to a CommentStatus enum; the four timestamps cast to Carbon.

Relations on a comment

$comment->commentable;      // the record it is on
$comment->commentator;      // the model that wrote it, or null for a guest
$comment->parent;           // the comment it replies to, or null
$comment->replies;          // direct replies only
$comment->reactions;        // every reaction row
$comment->reactionCounts;   // grouped counts, for rendering
$comment->revisions;        // prior bodies, oldest first
$comment->attachments;      // attachment metadata, oldest first

Ask who wrote it

$comment->isBy($user);  // bool

Both halves of the morph have to match: a User and an Admin that happen to share a primary key are not the same author. A guest-authored comment matches nobody, which is what makes ownership checks deny for one.

Optional: keep a count

A commentable can keep a denormalized count of its approved, non-deleted comments on its own table. It is off until you override one method and add the column yourself:

public function commentsCountColumn(): ?string
{
    return 'comments_count';
}

See comment counts for the migration, the maintenance guarantees, and the repair command.

Optional: decide the initial status

A commentable can implement DecidesCommentStatus to say what status its new comments start in, overriding both configured defaults. See initial status.