›
byrcsc/laravel-hold · 1.x
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.
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;
}| Member | Returns |
|---|---|
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.
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;
}| Member | Returns |
|---|---|
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.
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.
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 modelThis is the reason the package ships events rather than notifications: half the holders in a real application have nowhere to send a message.
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 nullreleasedBy stays null until a release names someone. See
releasing and extending.
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 thoseWhich 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.
holdCapacity() decides
and where to override it.Hold model.