›
byrcsc/laravel-assignment · 1.x
Every transition dispatches an event, and each event waits for the surrounding transaction to commit.
Every transition dispatches an event. None of them ship with an application listener, so nothing happens until you add one. The package leaves out manager alerts, dashboards, and escalations on purpose. Build each of them as a listener on one of these events.
use ByRcsc\LaravelAssignment\Events\AssignableQueued;
use ByRcsc\LaravelAssignment\Events\Assigned;
use ByRcsc\LaravelAssignment\Events\AssignmentAccepted;
use ByRcsc\LaravelAssignment\Events\AssignmentDeclined;
use ByRcsc\LaravelAssignment\Events\AssignmentOffered;
use ByRcsc\LaravelAssignment\Events\OfferExpired;
use ByRcsc\LaravelAssignment\Events\OffersExhausted;
use ByRcsc\LaravelAssignment\Events\Reassigned;
use ByRcsc\LaravelAssignment\Events\Unassigned;| Event | Dispatched when | Payload |
|---|---|---|
Assigned | An assignment becomes active, by assign() or acceptance | $assignment |
AssignmentOffered | An offer is created | $assignment |
AssignmentAccepted | The assignee accepts an offer | $assignment |
AssignmentDeclined | The assignee declines an offer | $assignment |
OfferExpired | An overdue offer ends by the tick, a job, or a new write | $assignment |
Unassigned | An active assignment ends by unassign() or reassignment | $assignment |
Reassigned | A reassignment replaces one assignee with another | $oldAssignment, $newAssignment |
AssignableQueued | An assignable enters the queue | $entry |
OffersExhausted | A cascade runs out of candidates | $lastOffer |
Every payload property is public and readonly.
Some transitions dispatch more than one event, in a fixed order.
Accepting an offer dispatches AssignmentAccepted and then Assigned. A
listener that cares about work becoming active can listen only to Assigned
and catch both routes.
Reassigning dispatches Unassigned for the old row, Assigned for the new
one, and then Reassigned carrying both. A listener on Unassigned that
should ignore reassignment checks the reason:
use ByRcsc\LaravelAssignment\Enums\EndReason;
public function handle(Unassigned $event): void
{
if ($event->assignment->ended_reason === EndReason::Reassigned) {
return;
}
// a real removal
}A declined offer that cascades dispatches AssignmentDeclined, then
AssignmentOffered for the next candidate, in the same request.
Completing dispatches nothing. complete() ends the row with reason
completed and dispatches no event. Put completion logic in your own code
around the call.
Every event implements ShouldDispatchAfterCommit. Inside a transaction that
rolls back, nothing is dispatched:
DB::transaction(function () use ($enquiry, $tradie) {
$enquiry->assign($tradie);
throw new PaymentFailed; // no Assigned event reaches any listener
});A listener can therefore assume that the row it receives exists. A listener also does not run until the outermost transaction commits, so an assignment made inside a long transaction notifies late.
None of the events implement ShouldQueue, so listeners run synchronously
inside the call that caused the transition. A listener that calls a slow API
slows down assign(), and inside a cascade it slows down every step.
Queue the listener, not the event:
namespace App\Listeners;
use ByRcsc\LaravelAssignment\Events\OffersExhausted;
use Illuminate\Contracts\Queue\ShouldQueue;
final class EscalateUntakenWork implements ShouldQueue
{
public string $queue = 'notifications';
public function handle(OffersExhausted $event): void
{
$assignable = $event->lastOffer->assignable;
// page the dispatcher, open a ticket, post to Slack ...
}
}Register it as you would any listener. The events serialize their models, so a queued listener reloads the row and sees its committed state.
Flush the queue when somebody becomes available. The listener assigns waiting work sooner than the next tick does:
public function handle(CrewBecameAvailable $event): void
{
Assignment::flushQueue(Callout::class);
}Alert on exhaustion. OffersExhausted means that nobody took the work. No
notification covers that case.
Watch the backlog. AssignableQueued fires once per entry created, so put
a counter or a threshold alert in its listener.
Track handling time. Assigned marks the start of the work, and the row's
ended_at marks the end. Write your own metrics from the pair rather than
querying the table later.
assignedBy, not on the event.assignee() or
workload().