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

Profiles.

A profile holds the candidates, policy, scope, and mode for one assignable model, so assignment takes no arguments.

The fluent builder needs a candidate list at the call site. That works in a controller and fails everywhere else: a queued job, a scheduled command, or the queue flush has no chain to build.

A profile is a class that answers those questions for one assignable model. Once it is registered, assignment takes no arguments:

$enquiry->autoAssign();
$callout->autoAssign('crew');

Write a profile

Two methods are required. The rest have defaults.

use ByRcsc\LaravelAssignment\AssignmentProfile;
use ByRcsc\LaravelAssignment\Policies\RoundRobin;
use Illuminate\Database\Eloquent\Model;

final class EnquiryProfile extends AssignmentProfile
{
    public function candidates(Model $assignable, ?string $role): iterable
    {
        return Tradie::query()->where('available', true)->get();
    }

    public function policy(Model $assignable, ?string $role): string
    {
        return RoundRobin::class;
    }

    public function scope(Model $assignable, ?string $role): ?string
    {
        return 'trade:'.$assignable->trade;
    }
}

candidates() returns any iterable of persisted Eloquent models. policy() returns a class name, a policy instance, or a closure, which is the same set using() accepts.

Every method receives the assignable and the role, so one profile can serve several roles on the same model by branching on $role.

The contract types the assignable as Model. The engine does not know which model a profile was registered for. Narrow it yourself when you need the model's own properties, either with an instanceof check or by accepting the wider type and failing loudly.

Register it

// config/assignment.php
'profiles' => [
    App\Models\Enquiry::class => App\Assignment\EnquiryProfile::class,
    App\Models\Callout::class => App\Assignment\CalloutProfile::class,
],

The key is the assignable class, the value is the profile class name. Profiles are resolved through the container, so a profile can take constructor dependencies.

Resolution walks the class and then its parents, so a profile registered against a base class serves every subclass that has no profile of its own.

autoAssign() on a model with no registered profile throws NoProfile.

The optional methods

MethodDefaultControls
scope()nullThe rotation scope; null uses the derived one
mode()Mode::AssignWhether to assign outright or offer
offerTtl()nullSeconds an offer stands; null never expires
queueWhenEmpty()trueQueue the assignable when nobody is available
queuePriority()0Its position in the queue, highest first

Offering from a profile

use ByRcsc\LaravelAssignment\Enums\Mode;

public function mode(Model $assignable, ?string $role): Mode
{
    return $role === 'crew' ? Mode::Offer : Mode::Assign;
}

public function offerTtl(Model $assignable, ?string $role): ?int
{
    return 120;
}

Profile-driven offers are the ones that cascade. An offer created through the fluent builder has no cascade, so it ends when it is declined or expires. See offer cascades.

Queueing when nobody is available

public function queueWhenEmpty(Model $assignable, ?string $role): bool
{
    return true;
}

public function queuePriority(Model $assignable, ?string $role): int
{
    return $assignable->emergency ? 10 : 0;
}

With queueWhenEmpty() returning true, an autoAssign() that finds no candidates parks the assignable rather than doing nothing. Return false and autoAssign() returns null and leaves no trace. See the queue.

What happens on autoAssign

  1. The profile is resolved for the assignable's class.
  2. candidates(), scope(), policy(), and mode() are read.
  3. In offer mode a cascade id is generated, so this offer and its successors belong to one cascade.
  4. The selection runs under the scope lock and writes the assignment.
  5. If nothing was written and queueWhenEmpty() is true, the assignable is queued at queuePriority().

The return value is the assignment, or null when nobody was selected.

Calling it without the trait

The facade takes the same path for a model you hold but whose trait you do not want to call through:

use ByRcsc\LaravelAssignment\Facades\Assignment;

Assignment::autoAssign($enquiry);
Assignment::autoAssign($callout, 'crew');

What it does not do

  • Store profiles in the database. They are classes, registered in config. Admin-editable routing rules are the application's to build.
  • Fall back to a default profile. An unregistered model throws rather than guessing.
  • Cache anything. candidates() runs on every call, which is what lets a flush minutes later see a tradie who has since become available.

What to read next

  • The queue for what happens when nobody is available.
  • Offer cascades for how a declined profile offer moves on.
  • Configuration for the registration key.
PreviousScopes and rotationNextThe queue
View source

On this page

  1. Write a profile
  2. Register it
  3. The optional methods
  4. Offering from a profile
  5. Queueing when nobody is available
  6. What happens on autoAssign
  7. Calling it without the trait
  8. What it does not do
  9. What to read next