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

Multi-moderator moderation

By default one call to Reports::resolve() or reject() settles a report. To require several moderators to agree first, open a moderation request: the report becomes an approvals-for-laravel subject and the engine’s rule decides when the bar is met. Publish the approvals migrations first (see Installation).

Moderators

Moderators are saved Eloquent models — typically your User with the approvals GivesApprovals trait, which adds its givenApprovals() relation. It combines freely with GivesReports:

use Illuminate\Database\Eloquent\Model;
use RoundlyConsulting\Approvals\Interfaces\GivesApprovalsInterface;
use RoundlyConsulting\Approvals\Traits\GivesApprovals;
use RoundlyConsulting\Reports\Contracts\Reporter;
use RoundlyConsulting\Reports\Traits\GivesReports;

class User extends Model implements GivesApprovalsInterface, Reporter
{
    use GivesApprovals;
    use GivesReports;
}

Opening a request

Open a moderation request, then route decisions through the same resolve() and reject():

use RoundlyConsulting\Approvals\Enums\ApprovalRule;
use RoundlyConsulting\Reports\Facades\Reports;

// Require 2 of the named moderators to agree.
Reports::moderate($report)
    ->requiring([$alice, $bob, $carol])
    ->rule(ApprovalRule::Quorum)
    ->quorum(2)
    ->open();

Reports::resolve($report, by: $alice);             // 1 of 2 — report stays open
Reports::resolve($report, by: $bob, note: 'Spam'); // quorum reached → Resolved
  • requiring([...]) — the moderators whose sign-off is required. Opening without any throws MissingModeratorsException.
  • rule(ApprovalRule) — Unanimous, Quorum, Any or Weighted; defaults to moderation.default_rule.
  • quorum(int) — the approval count for Quorum, or the weight threshold for Weighted; defaults to moderation.default_quorum.
  • open() — opens the request and returns the underlying ApprovalRequest.

The named list is stored on the request, so open() refuses one it could never settle — nothing is opened: MissingModeratorsException for an empty list, and the approvals engine’s InvalidApprovalRequestException for a moderator that isn’t saved yet or a quorum above the moderator count (quorum(4) of three).

How a report settles

When the rule’s threshold is reached, the SyncReportStatusFromApproval listener updates the report automatically: an approved request resolves it, a rejected one rejects it, and cancelled or expired requests leave it untouched. The deciding moderator and their note are stamped onto the report, and ReportResolved, ReportRejected and ReportStatusChanged are re-emitted — so your listeners see the same events whether a report was settled directly or through moderation. Under the Unanimous rule a single rejection rejects the report.

The listener respects strict_transitions: if the report has meanwhile moved somewhere the transition table forbids, it does nothing.

Only the named moderators decide

While the request is open ($report->isUnderModeration()), resolve() and reject() accept a moderator from the requiring([...]) list, or an approvals delegate of one (recorded for the moderator). Anyone else is refused with ModeratorRequiredException and nothing is recorded — an outsider (the approvals engine’s UnauthorizedApprovalException is its getPrevious()), a call without an actor, and a raw changeStatus() or close() out of the open statuses alike:

use RoundlyConsulting\Reports\Exceptions\ModeratorRequiredException;
use RoundlyConsulting\Reports\Facades\Reports;

try {
    Reports::resolve($report, by: $mallory); // not in requiring([...])
} catch (ModeratorRequiredException $e) {
    $e->report; $e->actor;                   // the report, and who was refused (null: no actor)
}

review() still works. Once the request is decided, the report settles directly again.

Tracking progress

Report uses the approvals RequiresApproval trait, so the latest request’s progress is readable straight from the report:

$progress = $report->approvalProgress();  // ?ApprovalProgress — null before a request is opened

$progress?->approved;      // approvals so far
$progress?->required;      // moderators on the request
$progress?->percentage();  // 0–100

$report->approvalRequests; // every moderation request opened on the report

Weighted rules

With ApprovalRule::Weighted, the quorum is a weight threshold rather than a headcount. Moderators declare their weight by implementing the approvals ProvidesApprovalWeight interface — see the Approvals for Laravel docs.

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.