›
byrcsc/laravel-assignment · 1.x
Every failure is a typed exception under one root, and each one names a different situation.
The package throws rather than returning false, because filling the wrong slot
quietly is worse than an error. Every exception extends
ByRcsc\LaravelAssignment\Exceptions\AssignmentException, which extends
RuntimeException, so one catch block covers all of them.
use ByRcsc\LaravelAssignment\Exceptions\AssignmentException;
try {
$enquiry->assign($tradie);
} catch (AssignmentException $e) {
report($e);
}| Exception | Thrown when |
|---|---|
SlotOccupied | The slot already holds an open assignment, or a concurrent writer won the race |
AlreadyAssigned | The assignee already holds the slot, or a queued assignable is already assigned |
NotAssigned | unassign() or reassign() found no open row to act on |
NotTheAssignee | A model other than the one an offer names tried to answer it |
OfferNotOpen | The offer was already answered, or its expiry has passed |
InvalidTransition | complete() ran on an assignment that is not active |
NoProfile | autoAssign() ran on a model with no registered profile |
The most important one to handle. It means the slot is taken, either because it already held an open row or because another request claimed it in the moment between your read and your write.
use ByRcsc\LaravelAssignment\Exceptions\SlotOccupied;
try {
$callout->assign($crew, role: 'crew');
} catch (SlotOccupied) {
// pick again, queue the work, or tell the user
}It is raised in three places: assign() and offer() on an occupied slot, a
reassignment whose target row changed underneath it, and any unique index
violation on the open-slot constraint. Unrelated integrity errors are not
converted, so a foreign key failure still surfaces as itself.
Retrying is often right, particularly for autoAssign(), where the profile
selects again and lands on a different candidate.
Two situations, both meaning "this is already true":
reassign() was given the assignee who already holds the slot. Reassigning
somebody to their own work is a no-op the package refuses to pretend it did.queue() was called for a slot that already holds an open assignment. There
is nothing to wait for.unassign() or reassign() found an empty slot. Check with isAssigned()
first when an empty slot is an expected state rather than a bug.
Somebody other than the offer's target called accept() or decline(). It is
an authorization signal worth surfacing rather than swallowing: the offer is
still open and still belongs to whoever it named.
The offer cannot be answered any more. It was accepted, declined, or its
expires_at passed. In a race between two answers, the loser sees this.
The offer's own row tells you which it was:
catch (OfferNotOpen) {
$offer->refresh();
$offer->ended_reason; // declined, expired, or null when it was accepted
}complete() only applies to an active assignment. Completing an offer that
nobody has accepted, or a row that already ended, throws.
autoAssign() was called on a model with no entry in assignment.profiles,
and none of its parent classes had one either. The message names the class,
which is usually enough to spot a missing config entry or a typo.
Two failures raise plain PHP exceptions rather than package ones, because they are programming errors rather than states:
UnexpectedValueException when a candidate is not a persisted Eloquent
model, when a policy returns a model that was not among the candidates, when
a registered profile is not a class name or does not extend
AssignmentProfile, or when a policy class does not implement
SelectionPolicy.InvalidArgumentException from the migration when a configured key type
is not id, uuid, ulid, or string.Neither is caught by an AssignmentException block, which is deliberate: they
mean the wiring is wrong, not that the world is busy.
Not every empty outcome is an error:
assign() and offer() on the builder return null when the candidate
list is empty or the policy picks nobody.autoAssign() returns null for the same reason, after queueing the
assignable when the profile allows it.assignee(), assignment(), and queuedAssignment() return null when
there is nothing to read.