›
›
›
  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 package failure throws a typed exception that extends one base class, and each type names one 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);
}

Package exceptions

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

SlotOccupied is 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 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
}

The package throws it from:

  • assign() and offer() on an occupied slot.
  • A reassignment whose target row changed after the engine read it.
  • Any unique index violation on the open-slot constraint.

The package does not convert unrelated integrity errors, so a foreign key failure still throws its own exception.

Retrying is often right, particularly for autoAssign(), where the profile selects again and can pick a different candidate.

AlreadyAssigned

AlreadyAssigned means that the requested state is already true:

  • reassign() was given the assignee who already holds the slot. The package throws instead of treating a reassignment to the same assignee as a successful no-op.
  • 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(). Treat it as an authorization failure and report it, rather than catching and ignoring it. The offer is still open and still belongs to the assignee it named.

OfferNotOpen

Nobody can answer the offer 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() applies only 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.

Plain PHP exceptions

Programming errors raise plain PHP exceptions rather than package ones:

  • 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 when a role is an empty string, when an offer TTL is below one second, or, from the migration, when a configured key type is not id, uuid, ulid, or string.

An AssignmentException block catches neither of them, by design. They mean the wiring is wrong, not that another request got there first.

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. Package exceptions
  2. SlotOccupied
  3. AlreadyAssigned
  4. NotAssigned
  5. NotTheAssignee
  6. OfferNotOpen
  7. InvalidTransition
  8. NoProfile
  9. Plain PHP exceptions
  10. What does not throw
  11. What to read next