›
›
›
  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

Assignments and slots.

A slot is one assignable and one role, and the database allows one open assignment in it.

An assignment binds one assignee to one assignable, optionally under a role. The main rule is that a slot holds at most one open assignment. The database enforces that rule.

What counts as open

An assignment is open when it is active, or when it is offered and its expires_at has not passed.

StatusOpen?
offeredYes, until expires_at passes
activeYes
endedNo

An offer occupies its slot while it is open. The purpose of an offer is to stop anyone else from getting the work while the assignee decides whether to take it.

$assignment->isOpen();      // active, or offered and not past expiry
$assignment->isActive();
$assignment->isOffered();
$assignment->isEnded();
$assignment->isExpired();   // offered, has an expires_at, and it has passed

Roles make more than one slot

Leave the role off and an assignable has one slot:

$enquiry->assign($tradie);
$enquiry->assignee();          // the Tradie

Pass a role and each role is its own slot, so one callout holds two assignees at once:

$callout->assign($crew, role: 'crew');
$callout->assign($tradie, role: 'supervisor');

$callout->assignee('crew');   // the Crew
$callout->assignee('supervisor');   // the Tradie

Every reader takes the same optional role. The slot with no role is a distinct slot, not a wildcard. $callout->assignee() with no argument reads the slot with no role. In the example above, that slot is empty.

Roles are free-form strings. The package keeps no list of the ones you use. The one rule is that a role cannot be an empty string. Pass null for the slot with no role, because '' throws InvalidArgumentException on every write and every read.

The constraint behind the rule

The assignments table carries an internal slot column. While a row is open, slot holds the role, or an empty string when there is no role. When the row ends, slot becomes null. A unique index covers the assignable type, the assignable id, and slot.

A unique index allows any number of null values, so ended rows never collide. Two open rows for the same slot cannot both exist. When two concurrent callers try to fill one slot, one gets the assignment and the other gets a SlotOccupied exception. The behaviour is the same on MySQL, PostgreSQL, and SQLite.

slot is internal. The model hides it from array and JSON output, and nothing in the public API takes or returns it.

History is the table

The package ends assignments and never deletes them. An ended row records when and why:

ended_reasonWritten when
unassignedunassign() ended an active assignment
reassignedreassign() replaced this row with another
declinedThe assignee declined an offer
expiredAn offer passed its expires_at
completedcomplete() ended the work as finished

Every row also carries offered_at, accepted_at, expires_at, ended_at, and a polymorphic assigned_by. A null assigned_by means the engine created the row rather than an actor you named.

$callout->assignments()->ended()->get();   // past holders, oldest first

There is no separate transition table. A row records one holder from start to end. The sequence of rows records the assignable's full history.

The rows are a record, not an audit trail. They are ordinary Eloquent models with no append-only enforcement. Anything that can write to the database can change ended_at or expires_at. If you need attributable, tamper-evident history, listen to the events and write it yourself.

What it does not do

  • Track a slot's history separately from an assignee's. Both are the same rows, read from different sides.
  • Reopen an ended assignment. Ending is final. A new holder gets a new row.
  • Validate roles. Any string works, and a typo creates a second slot rather than an error.

What to read next

  • Assigning and offering for the two verbs and the handshake between them.
  • Reading assignments for every reader on both traits.
  • Concurrency guarantees for what the constraint promises under load.
PreviousQuick startNextAssigning and offering
View source

On this page

  1. What counts as open
  2. Roles make more than one slot
  3. The constraint behind the rule
  4. History is the table
  5. What it does not do
  6. What to read next