Obmedzenia a guardy
Každá kontrola prechádza jednou pipeline v pevnom poradí. Prvé tri kroky sú štrukturálne a vyhodnocovanie zastavia; ostatné sa zbierajú všetky, takže rozhranie môže zobraziť všetky dôvody naraz:
- 1. Prechod existuje (unknown_transition, no_transition_to_state), stav nie je koncový (terminal_state) a prechod odchádza z aktuálneho stavu (not_from_current_state).
- 2. Očakávaná verzia (stale_version), zmrazenie (frozen) a zapečatenie (sealed).
- 3. Kontext: system_only / system_not_allowed.
- 4. Pravidlá pre aktéra: actor_required, actor_not_allowed, unauthorized (Gate).
- 5. Vstup: reason_required, reason_too_long, invalid_payload.
- 6. Čas: not_yet_available, deadline_passed, min_dwell_not_reached, cooldown_active.
- 7. max_occurrences_reached, vaše guardy (najprv tie pre celý cyklus), quota_exceeded.
- 8. Rate limity (rate_limited) — započítajú sa, len ak prešlo všetko ostatné.
Soft-deleted subjekt sa zamietne ešte pred všetkými týmito krokmi: aj subject_trashed je štrukturálny kód a vráti sa ako jediné zamietnutie.
Systémový kontext
asSystem(), sweep a frontované joby bežia v systémovom kontexte, ktorý preskakuje pravidlá pre aktéra, požiadavku na dôvod, minimálny čas v stave, cooldowny a rate limity. Nastaviť sa dá len v kóde, nikdy cez vstup. Prechod musí deklarovať systemOnly() alebo allowSystem(), aby v ňom mohol bežať. Prechod bez pravidla pre aktéra môže spustiť akákoľvek cesta kódu — ak na aktérovi záleží, pridajte ability(), actors(), actor() alebo requiresActor().
Deklarovanie obmedzení
$lifecycle->transition('approve')
->from('pending')->to('approved')
->ability('approve') // Gate / policy
->actors(User::class) // only users, not API clients
->requiresReason(10)
->rules(['note' => 'required|string|max:500'], ['note.required' => 'Say why.'])
->when(fn (TransitionContext $context): bool => $context->subject->photos()->exists(), code: 'no_photos')
->when(EnsureInvoicePaid::class) // a Guard class, resolved per call
->notAfter('deadline_at');Gate abilities dostanú subjekt a TransitionContext; chýbajúci aktér znamená actor_required, nikdy nie „povolené pre hosťa“:
Gate::define('publish', fn (User $user, Listing $listing, TransitionContext $context): bool => $listing->user_id === $user->id);Vlastné guardy
Guard vráti true alebo null na povolenie, false na zamietnutie so svojím kódom (predvolene guard_failed) alebo vlastný Denial. Triedy guardov sa z kontajnera získavajú pri každom volaní:
use RoundlyConsulting\Lifecycle\Contracts\Guard;
use RoundlyConsulting\Lifecycle\DataTransferObjects\Denial;
use RoundlyConsulting\Lifecycle\DataTransferObjects\TransitionContext;
final readonly class EnsureInvoicePaid implements Guard
{
public function __construct(private Billing $billing) {}
public function check(TransitionContext $context): ?Denial
{
return $this->billing->isPaid($context->subject)
? null
: Denial::of('invoice_unpaid', message: 'Pay the invoice first.', retryAfter: now()->addHour()->toImmutable());
}
}Guardy pre celý cyklus ($lifecycle->guard(...)) bežia pred guardmi prechodu a spustí sa každý guard. Vlastný kód je opakovateľný, len ak nesie retryAfter. Jeho správa pochádza zo zamietnutia, inak z lifecycle::denials.<code>, ak tento kľúč pridáte do lang/vendor/lifecycle, inak sa použije všeobecný text guard_failed:
// lang/vendor/lifecycle/en/denials.php (after --tag="lifecycle-translations")
return [
// … package keys …
'closed_now' => '":transition" is only possible during opening hours.',
];TransitionContext dáva guardom, handlerom aj closures subject, lifecycle, transition (definíciu), from, to, actor, system, reason, payload, now (UTC), version a scheduleId.
Kódy zamietnutí
Hodnoty DenialCode, s anglickými a slovenskými správami v lifecycle::denials. Opakovateľné zamietnutia nesú retryAfter a sweep naplánované prechody opakuje len pri nich:
| Kód | Opakovateľný | Význam |
|---|---|---|
unknown_transition | — | Prechod neexistuje (štrukturálne). |
no_transition_to_state | — | transitionTo() nenašlo prechod do cieľa (štrukturálne). |
terminal_state | — | Aktuálny stav je koncový (štrukturálne). |
not_from_current_state | — | Prechod neodchádza z aktuálneho stavu (štrukturálne). |
subject_trashed | — | Subjekt je soft-deleted (štrukturálne) — odpoveď check(), allowedTransitions() a canRollback(); apply() a rollback() namiesto toho vyhodia SubjectTrashedException. |
stale_version | — | expectingVersion() už nesedí. |
frozen | — | Cyklus je zmrazený. |
sealed | — | Stav je zapečatený (sealedAfter()). |
system_only | — | Spustiť ho smie len systémový kontext. |
system_not_allowed | — | Systémový kontext ho spustiť nesmie. |
actor_required | — | Vyžaduje sa aktér. |
actor_not_allowed | — | actors() alebo actor() aktéra odmietli. |
unauthorized | — | Gate ability zamietla. |
reason_required, reason_too_long | — | Dôvod chýba alebo je dlhší ako history.reason_max_length. |
invalid_payload | — | Validácia payloadu zlyhala; chyby polí sú v errors. |
not_yet_available | ✓ | Okamih z notBefore() ešte nenastal. |
deadline_passed | — | Okamih z notAfter() už uplynul. |
min_dwell_not_reached | ✓ | Minimálny čas v stave ešte neuplynul. |
cooldown_active | ✓ | Cooldown od posledného behu ešte plynie. |
max_occurrences_reached | — | maxOccurrences() je vyčerpané. |
guard_failed | ✓ | Guard zamietol s predvoleným kódom. |
quota_exceeded | ✓ | Kvóta cieľového stavu je plná. |
rate_limited | ✓ | rateLimit() je vyčerpaný. |
nothing_to_rollback, not_on_path, state_mismatch, irreversible, not_reversible, rollback_window_passed, rollback_conflict | — | Zamietnutia rollbacku — pozri Rollbacky a história. |
Prejavte lásku k open source
Tento balík je zadarmo pod licenciou MIT. Ak vám šetrí čas, jednorazový príspevok alebo členstvo na Patreone nám pomôže ho ďalej udržiavať, testovať a dokumentovať.
Ďalšie spôsoby podpory vrátane kryptomienOdoslaním daru súhlasíte s našimi podmienkami prijímania darov.
Chcete to zabudovať do svojho produktu?
Naše balíky integrujeme do zákazkových Laravel a AI riešení. Napíšte nám, na čom pracujete, a ozveme sa do 48 hodín.