Browse documentationOpen

byrcsc/laravel-comments · 1.x

Initial status.

Which status a new comment starts in — the configured defaults, why guests are separate, and the DecidesCommentStatus hook that beats both.

Every comment lands in some status the moment it is written. Which one is resolved in a fixed, short order:

  1. A status the caller set explicitly.
  2. The commentable's initialCommentStatus() hook, when it implements DecidesCommentStatus.
  3. comments.default_status for a model-authored comment, or comments.guest_status for a guest one.

The configured defaults

'default_status' => 'approved',
'guest_status' => 'pending',

Guests are read from their own key on purpose. Raising default_status never drags anonymous content along with it — approving content nobody can be held to is a decision the package will not make for you. Set guest_status yourself when you mean it.

Both accept pending, approved, rejected, or spam. Anything else throws an InvalidConfigurationException when the package boots.

Hold everything for review by lowering the default:

'default_status' => 'pending',

The commentable hook

For a rule the config cannot express — hold the first comment from anyone new, auto-approve on a closed internal board, hold everything on a locked thread — implement the contract on the commentable:

use ByRcsc\LaravelComments\Contracts\DecidesCommentStatus;
use ByRcsc\LaravelComments\Enums\CommentStatus;
use ByRcsc\LaravelComments\Models\Comment;

class Post extends Model implements DecidesCommentStatus
{
    use HasComments;

    public function initialCommentStatus(Comment $comment): ?CommentStatus
    {
        if ($this->comments_locked) {
            return CommentStatus::Pending;
        }

        return null;
    }
}

The comment handed to the hook is unsaved and fully populated: its commentator, guest identity, parent, and body are all readable, and nothing it says has been trusted yet.

Returning null hands the decision back to the configured defaults, so a hook only has to answer the cases it cares about. The hook is asked about replies as well as thread starters.

Two things the hook is deliberately not:

It is not an authorization check. Returning pending holds a comment; it does not refuse one. Refusing a write belongs in the policy or your controller.

It is not consulted when a status was named. A factory, a seeder, or an import restoring an export already knows what it meant:

$post->comments()->create(['body' => 'Imported', 'status' => CommentStatus::Approved]);

Resolution is only for the writes that said nothing.

Cost

The commentable is loaded for the hook only when its class implements the contract, so a model that does not pays nothing. On the ordinary write path the commentable is already in hand — $post->comment(...) hands the model over rather than re-reading the row — so implementing the hook costs no extra query either. Writing through the Comment model directly, without the commentable loaded, costs one read.

Factories resolve the same way

Comment::factory() sets no status, so factory-built comments resolve theirs exactly as written ones do — a guest lands pending, which is what a test of a moderation queue should be seeing. Name one with status() or its shortcuts when the test is about a particular status:

Comment::factory()->forCommentable($post)->pending()->create();

See testing.