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

Selection policies.

A selection policy picks one assignee from the candidates you pass in.

A policy decides which of the given candidates gets the work. It never asks whether a candidate should have been in the list.

use ByRcsc\LaravelAssignment\Facades\Assignment;
use ByRcsc\LaravelAssignment\Policies\RoundRobin;

Assignment::for($enquiry)
    ->among(Tradie::query()->where('available', true)->get())
    ->using(RoundRobin::class)
    ->assign();

The shipped policies

PolicyPicksState
RoundRobinThe next candidate after the last one this scope tookCursor per scope
LeastWorkloadThe candidate with the fewest open assignmentsNone
RandomAny candidateNone
FirstAvailableThe first candidate in the order you passed themNone
Any closureWhatever you returnYours

using() takes a class name, an instance, or a closure. The container resolves a class name, so a policy of your own can take constructor dependencies.

RoundRobin

Rotation starts from the last assignee this scope picked, not from a position in the array. The policy finds that assignee in the current list and takes the next one. After the last candidate, it goes back to the first. When the previous assignee is no longer in the list, the policy starts from the front.

Because the policy tracks the last assignee instead of a position, rotation keeps working when you rebuild the candidate list on every call. Reordering, adding, and removing candidates between calls do not break the rotation. See scopes and rotation.

LeastWorkload

Workload is the count of open assignments: active rows plus offers nobody has answered. The policy counts offers on purpose, so one idle crew cannot collect ten pending offers.

use ByRcsc\LaravelAssignment\Policies\LeastWorkload;

->using(LeastWorkload::class)                    // every open assignment counts
->using(LeastWorkload::of(Callout::class))      // only callouts count

of() constrains the count to one assignable type, so a crew's open support tickets do not make them look busy for callouts.

On a tie, the policy picks the candidate assigned least recently. A candidate who has never been assigned wins over one who has. Equal workloads therefore still rotate instead of always going to the same person.

The policy issues a fixed number of queries whatever the size of the candidate list: one grouped count and one grouped maximum, both constrained to the candidates you passed.

FirstAvailable

The policy takes the first candidate in the order you passed them, so your query order is the priority order. To put senior tradies first, add orderBy('grade') to the query.

Random

The policy picks a candidate uniformly at random with random_int().

Closures

A closure receives the assignable and the candidates, and returns one candidate or null:

->using(fn ($enquiry, $candidates) => $candidates->sortByDesc('rating')->first())

Returning null selects nobody, and the call returns null rather than throwing. Return null to say "none of these are good enough right now" and let the queue hold the work.

If a policy returns a model that is not in the candidate list, the call throws UnexpectedValueException. A policy cannot add an assignee that the application did not pass in.

Writing a policy class

use ByRcsc\LaravelAssignment\Contracts\SelectionPolicy;
use ByRcsc\LaravelAssignment\ScopeState;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;

final class HighestRated implements SelectionPolicy
{
    public function select(Model $assignable, Collection $candidates, ScopeState $state): ?Model
    {
        return $candidates->sortByDesc('rating')->first();
    }
}

The third argument is the scope's own key-value store. The package locks the scope row for the duration of the selection. Use the store when your policy needs to remember something between calls. See scopes and rotation.

Eligibility is yours

By design, the package ships no territory policy and no trade policy. Who can take an assignment is a query on data your application already owns. Who gets it, among those, is what a policy decides.

Routing by suburb is therefore a query plus a scope, not a feature:

Assignment::for($callout)
    ->among(Crew::query()
        ->where('suburb', $callout->suburb)
        ->where('available', true)
        ->get())
    ->role('crew')
    ->scope('suburb:'.$callout->suburb)
    ->using(LeastWorkload::class)
    ->assign();

Per-assignee capacity caps work the same way. Filter by $crew->workload() < 3 before passing the candidates in, and no policy needs to know the cap exists.

What it does not do

  • Filter candidates. A policy chooses and never rejects. Your query does the filtering.
  • Retry with a different candidate. One call selects once. Passing an assignee whose slot is taken raises SlotOccupied. The transaction rolls back any rotation cursor that moved during that attempt.
  • Look at assignment history beyond workload. LeastWorkload reads counts and last-assigned timestamps. Anything richer belongs in a closure or a policy class of your own.

What to read next

  • Scopes and rotation for where stateful policies keep their cursor.
  • Profiles to move the candidates and the policy off the call site.
  • API reference for the exact contract signature.
PreviousAssigning and offeringNextScopes and rotation
View source

On this page

  1. The shipped policies
  2. RoundRobin
  3. LeastWorkload
  4. FirstAvailable
  5. Random
  6. Closures
  7. Writing a policy class
  8. Eligibility is yours
  9. What it does not do
  10. What to read next