›
byrcsc/laravel-assignment · 1.x
Nine events cover every transition, and all of them wait 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. Anything the package deliberately leaves out, a manager alert, a dashboard, an escalation, is a listener on one of these.
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 is ended by the tick or a job | $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, 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 is silent, so a completion hook belongs 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
});That is what makes it safe for a listener to assume the row it receives exists. It also means a listener 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. More responsive than waiting for the next tick:
public function handle(CrewBecameAvailable $event): void
{
Assignment::flushQueue(Callout::class);
}Alert on exhaustion. OffersExhausted is the signal that nobody would take
the work, which no notification covers.
Watch the backlog. AssignableQueued fires once per entry created, so a
counter or a threshold alert belongs here.
Track handling time. Assigned and the row's ended_at bracket the work.
Write your own metrics from the pair rather than querying the table later.
assignedBy, not on the event.assignee() or
workload().