NewWe open-sourced 50+ Laravel packages
Custom AI apps, agents and automation — Roundly ConsultingRoundly
All packages
Comments for Laravel

DI and actions

The facade is the recommended default, not the only way in. Three entry points run the same code:

  • The Comments facade — the shortest form.
  • The manager, injected through the constructor — the same API with an explicit dependency and no static calls. RoundlyConsulting\Comments\CommentsManager is the facade root: a singleton, non-final because the fake extends it.
  • Actions — RoundlyConsulting\Comments\Actions\*Action, final readonly classes with one execute(), for queued jobs or your own actions.

Injecting the manager

Comments::fake() swaps the manager in the container too, so constructor-injected managers get the fake:

use RoundlyConsulting\Comments\CommentsManager;
use RoundlyConsulting\Comments\Models\Comment;

final class PublishFeedback
{
    public function __construct(private CommentsManager $comments) {}

    public function __invoke(Post $post, User $user, string $body): Comment
    {
        return $this->comments->on($post)->as($user)->body($body)->post();
    }
}

// Everything on the facade is on the manager:
$this->comments->query()->pending()->mostReported()->paginate();
$this->comments->lockThread($comment);

Running an action

Each manager method resolves one action from the container, so a host binding over an action applies to the facade, the builder, bulk moderation and the model traits alike. Calling an action directly skips the manager, so Comments::fake() does not record it:

use RoundlyConsulting\Comments\Actions\LockThreadAction;
use RoundlyConsulting\Comments\Actions\WriteCommentAction;
use RoundlyConsulting\Comments\DataTransferObjects\WriteCommentData;

$comment = app(WriteCommentAction::class)->execute(new WriteCommentData(
    commentable: $post,
    body: 'From a job',
    author: $user,        // optional
    parent: $rootComment, // optional reply — must belong to $post
));

app(LockThreadAction::class)->execute($comment);

Facade method → action

Facade methodActionDoes
on()->post(), write()WriteCommentAction::execute(WriteCommentData)Refuses a foreign parent, authorizes, validates the body, checks depth, locks and blocklist, persists, syncs mentions.
update()UpdateCommentAction::execute(UpdateCommentData)Edits the body, re-runs the blocklist, re-syncs mentions; fires CommentUpdated.
delete(), deleteAll()DeleteCommentAction::execute(Comment)Soft-deletes; fires CommentDeleted.
restore()RestoreCommentAction::execute(Comment)Restores a soft-deleted comment.
approve(), approveAll()ApproveCommentAction::execute(Comment)Status approved; fires CommentApproved, then CommentMentioned for mentions held back until now.
hide(), hideAll()HideCommentAction::execute(Comment)Status hidden; fires CommentHidden.
lock()LockSubjectAction::execute(Model)Stores the subject’s CommentLock.
unlock()UnlockSubjectAction::execute(Model)Removes the subject lock.
lockThread()LockThreadAction::execute(Comment)Locks the thread; fires CommentThreadLocked.
unlockThread()UnlockThreadAction::execute(Comment)Lifts the thread lock; fires CommentThreadUnlocked.

query(), for(), byAuthor(), isLocked() and isThreadLocked() are reads with no action behind them. SyncCommentMentionsAction and NotifyCommentMentionsAction are @internal — building blocks the write, update and approve actions run, not host-facing operations. The actions take two DTOs from RoundlyConsulting\Comments\DataTransferObjects: WriteCommentData(commentable, body, author, visible, parent) and UpdateCommentData(comment, body).

Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

More ways to support, including crypto

By donating, you agree to our donation terms.

Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.