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

Getting started

  • Introduction
  • Installation and setup
  • Quick start

Core concepts

  • Holdables and holders
  • Capacity and slots
  • Acquiring holds
  • Releasing and extending
  • Expiry
  • Hold state

Operations

  • Events and listeners
  • Scheduling expiry
  • Pruning history
  • Concurrency and databases

Reference

  • Configuration
  • Console commands
  • Database schema
  • Testing
  • Troubleshooting

Getting started

  • Introduction
  • Installation and setup
  • Quick start

Core concepts

  • Holdables and holders
  • Capacity and slots
  • Acquiring holds
  • Releasing and extending
  • Expiry
  • Hold state

Operations

  • Events and listeners
  • Scheduling expiry
  • Pruning history
  • Concurrency and databases

Reference

  • Configuration
  • Console commands
  • Database schema
  • Testing
  • Troubleshooting

byrcsc/laravel-hold · 1.x

Holdables and holders.

Add writable hold behavior to resources and read relationships to holders.

A hold has two ends. The holdable is the resource being claimed, and the holder is whoever claims it. Each end gets its own trait, and the split is not symmetric: the holdable owns every write.

The holdable side

Holdable goes on the resource. It brings capacity, the read API, and both acquisition methods.

use ByRcsc\LaravelHold\Concerns\Holdable;

class Seat extends Model
{
    use Holdable;
}
MemberReturns
holds()MorphMany
activeHolds()MorphMany
holdCapacity()int
availableSlots()int
isFullyHeld()bool
activeHoldFor(Model $holder)?Hold
acquireHold($holder, $expiresAt = null, $metadata = [])?Hold
acquireHoldOrFail($holder, $expiresAt = null, $metadata = [])Hold

The trait requires the host to be an Eloquent model and nothing else. It adds no columns to the resource's own table.

The holder side

HasHolds goes on whoever holds. It is read-only by design and adds two relations.

use ByRcsc\LaravelHold\Concerns\HasHolds;

class Cart extends Model
{
    use HasHolds;
}
MemberReturns
holds()MorphMany
activeHolds()MorphMany

A holder does not need this trait. The hold row is written from the holdable side, so any Eloquent model can be passed to acquireHold() as-is. Add HasHolds when you want to read from the holder end.

Why the holder cannot acquire

Capacity is a fact about the resource, so the resource has to serialize the write. Acquisition takes a row lock on the holdable, counts its active holds, compares against holdCapacity(), and inserts, all inside one transaction. A holder cannot serialize a slot it does not yet occupy.

That is why HasHolds offers no acquire-from-here and no release-all. Neither could be made correct from the holder end without locking every resource the holder might touch.

Any model can hold

Nothing in the package asks a holder to be a user. It is not typed against Authenticatable, it never calls notify(), and it reads no attributes off the holder beyond its key and class.

$seat->acquireHold($cart);       // a checkout session with nobody signed in
$seat->acquireHold($user);       // an authenticated person
$venue->acquireHold($event);     // a model holding another model

This is the reason the package ships events rather than notifications: half the holders in a real application have nowhere to send a message.

The three relations on a hold

From the hold itself, all three ends are morph relations:

$hold->holdable;     // the resource being held
$hold->holder;       // whoever holds it
$hold->releasedBy;   // whoever released it, or null

releasedBy stays null until a release names someone. See releasing and extending.

A model that is both

A locker can be held by a person and can itself hold other resources. Both traits name their relations holds and activeHolds, one per side of the same table, so PHP needs to be told which side keeps the plain names.

use ByRcsc\LaravelHold\Concerns\HasHolds;
use ByRcsc\LaravelHold\Concerns\Holdable;

class Locker extends Model
{
    use Holdable, HasHolds {
        Holdable::holds insteadof HasHolds;
        Holdable::activeHolds insteadof HasHolds;
        HasHolds::holds as holdsAsHolder;
        HasHolds::activeHolds as activeHoldsAsHolder;
    }
}
$locker->holds;                // holds taken on the locker
$locker->activeHolds;          // the blocking subset of those
$locker->holdsAsHolder;        // holds the locker has taken elsewhere
$locker->activeHoldsAsHolder;  // the blocking subset of those

Which side keeps the plain names is your choice. Point them at whichever question you ask this model most often.

The two sides never mix. A model composing both traits can even hold itself, and the resource side and the holder side still return different rows, because they query different columns.

What to read next

  • Capacity and slots for what holdCapacity() decides and where to override it.
  • Acquiring holds for the two acquisition methods and how refusal is reported.
  • Hold state for the scopes, the status accessor, and the helper methods on the Hold model.
PreviousQuick startNextCapacity and slots
View source

On this page

  1. The holdable side
  2. The holder side
  3. Why the holder cannot acquire
  4. Any model can hold
  5. The three relations on a hold
  6. A model that is both
  7. What to read next