›
›
›
  1. docs
  2. ›
  3. byrcsc/laravel-assignment
1.x
Browse documentationOpenClose

Getting started

  • Introduction
  • Installation and setup
  • Quick start

Core concepts

  • Assignments and slots
  • Assigning and offering
  • Selection policies
  • Scopes and rotation
  • Profiles
  • The queue
  • Offer cascades

Operations

  • Expiry and scheduling
  • Reading assignments
  • Notifications
  • Events and listeners
  • Concurrency guarantees

Reference

  • Configuration
  • API reference
  • Console commands
  • Exceptions
  • Testing
  • Troubleshooting

Getting started

  • Introduction
  • Installation and setup
  • Quick start

Core concepts

  • Assignments and slots
  • Assigning and offering
  • Selection policies
  • Scopes and rotation
  • Profiles
  • The queue
  • Offer cascades

Operations

  • Expiry and scheduling
  • Reading assignments
  • Notifications
  • Events and listeners
  • Concurrency guarantees

Reference

  • Configuration
  • API reference
  • Console commands
  • Exceptions
  • Testing
  • Troubleshooting

byrcsc/laravel-assignment · 1.x

Exceptions.

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);
}

The seven

ExceptionThrown when
SlotOccupiedThe slot already holds an open assignment, or a concurrent writer won the race
AlreadyAssignedThe assignee already holds the slot, or a queued assignable is already assigned
NotAssignedunassign() or reassign() found no open row to act on
NotTheAssigneeA model other than the one an offer names tried to answer it
OfferNotOpenThe offer was already answered, or its expiry has passed
InvalidTransitioncomplete() ran on an assignment that is not active
NoProfileautoAssign() ran on a model with no registered profile

SlotOccupied

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.

AlreadyAssigned

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.

NotAssigned

unassign() or reassign() found an empty slot. Check with isAssigned() first when an empty slot is an expected state rather than a bug.

NotTheAssignee

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.

OfferNotOpen

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
}

InvalidTransition

complete() only applies to an active assignment. Completing an offer that nobody has accepted, or a row that already ended, throws.

NoProfile

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.

Exceptions that are not ours

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.

What does not throw

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.

What to read next

  • Concurrency guarantees for which exceptions come from races and when to retry.
  • Assigning and offering for the calls that raise each one.
  • Troubleshooting for symptoms that are not exceptions.
PreviousConsole commandsNextTesting
View source

On this page

  1. The seven
  2. SlotOccupied
  3. AlreadyAssigned
  4. NotAssigned
  5. NotTheAssignee
  6. OfferNotOpen
  7. InvalidTransition
  8. NoProfile
  9. Exceptions that are not ours
  10. What does not throw
  11. What to read next