Definovanie životného cyklu
Hodí sa pre akýkoľvek Eloquent model so stavom — niekoľko typických cyklov:
| Model | Typický cyklus | Pravidlá |
|---|---|---|
| Objednávka | pending → paid → shipped, refunded | stav platby ako druhý cyklus |
| Tiket | new → open → waiting → resolved | znovu otvorený najviac 3-krát |
| Inzerát | draft → active → expired | vyprší po 30 dňoch, najviac 5 aktívnych na zákazníka |
| Predplatné | trial → active → grace → cancelled | obnovené alebo znovu aktivované |
| Žiadosť | submitted → in review → approved / rejected | vrátenie do hodiny |
Definícia rozširuje LifecycleDefinition a všetko deklaruje v define(). Stavy sú prípady backed enumu (string alebo int) alebo obyčajné reťazce:
enum ListingStatus: string
{
case Draft = 'draft';
case Active = 'active';
case Closed = 'closed';
case Expired = 'expired';
case Archived = 'archived';
}use RoundlyConsulting\Lifecycle\Definition\LifecycleBuilder;
use RoundlyConsulting\Lifecycle\Definition\LifecycleDefinition;
final class ListingLifecycle extends LifecycleDefinition
{
public function define(LifecycleBuilder $lifecycle): void
{
$lifecycle->states(ListingStatus::class)
->initial(ListingStatus::Draft)
->terminal(ListingStatus::Archived);
$lifecycle->state(ListingStatus::Active)
->ttl('30 days')->grace('3 days')->warnBefore('7 days', '1 day')
->expiresVia('expire')
->quota(5, scope: 'user_id')
->stamps('published_at');
$lifecycle->transition('publish')
->from(ListingStatus::Draft)->to(ListingStatus::Active)
->ability('publish')
->allowSystem();
$lifecycle->transition('close')
->from(ListingStatus::Active)->to(ListingStatus::Closed)
->rules(['note' => 'nullable|string|max:500']);
$lifecycle->transition('reopen')
->from(ListingStatus::Closed)->to(ListingStatus::Active)
->maxOccurrences(3)->cooldown('1 hour')->requiresReason()
->rules(['note' => 'nullable|string|max:500']);
$lifecycle->transition('expire')
->from(ListingStatus::Active)->to(ListingStatus::Expired)
->systemOnly();
$lifecycle->transition('reactivate')
->from(ListingStatus::Expired)->to(ListingStatus::Active);
$lifecycle->transition('archive')
->from('*')->to(ListingStatus::Archived)
->irreversible();
}
}Definícia sa skompiluje raz za proces, zvaliduje — chyby vyhodia InvalidLifecycleDefinitionException so zoznamom všetkých problémov — a potom je nemenná. Trieda sa vytvára cez new, nikdy cez kontajner: závislosti dajte do guardov, handlerov a hookov zadaných názvom triedy, ktoré sa z kontajnera získajú pri každom behu. define() nesmie čítať stav requestu; dynamické hodnoty patria do closures.
Vygenerovanie definície
make:lifecycle zapíše triedu definície do App\Lifecycles — malý platný príklad s reťazcovými stavmi alebo prípady backed enumu ako stavy, so začiatkom v prvom prípade:
php artisan make:lifecycle ListingLifecycle
php artisan make:lifecycle ListingLifecycle --enum="App\Enums\ListingStatus"
# Edit the stubs make:lifecycle uses
php artisan vendor:publish --tag=lifecycle-stubsLifecycleBuilder
| Metóda | Význam |
|---|---|
states(ListingStatus::class) / states(['draft', 'active']) | Všetky prípady backed enumu (string alebo int) alebo zoznam prípadov či reťazcov — nikdy nie zmiešane. |
initial($state) | Stav, v ktorom nový model začína. Práve jeden; nemôže byť koncový. |
terminal(...$states) | Koncové stavy — nič z nich nevedie. |
state($state) | StateBuilder pre nastavenia jedného stavu. |
transition('name') | TransitionBuilder. Názvy: písmená, číslice, _ . : -, najviac 64 znakov. |
guard($guard) | Guard, ktorý spúšťa každý prechod tohto cyklu, ešte pred vlastnými guardmi prechodu. |
label('…'), meta([...]) | Zobrazovaný názov a ľubovoľné metadáta. |
StateBuilder
| Metóda | Význam |
|---|---|
label('…' | fn () => …) | Zobrazovaný názov. Predvolene label() enumu, ak ho má, inak headline hodnoty, cez prekladač. |
ttl('30 days' | fn (Model $m) => interval|instant|null) | Čas do expirácie od vstupu do stavu. |
expiresAtAttribute('ends_at') | Okamihom expirácie je vlastný datetime stĺpec modelu (null = nikdy). |
grace('3 days') | Ochranná lehota medzi expiráciou a spustením expiračného prechodu. |
warnBefore('7 days', '1 day') | Upozornenia LifecycleExpiring (najviac 16 predstihov). |
expiresVia('expire') | Prechod, ktorý sa spustí pri expirácii. Musí z tohto stavu odchádzať a byť systemOnly() alebo allowSystem(). |
quota($max | fn (Model $m) => int, scope: 'user_id' | [...], name: null) | Najviac $max modelov tejto tabuľky v tomto stave na jeden scope. |
minDwell('1 hour') | Minimálny čas v stave, kým ho smú opustiť používateľské prechody. |
sealedAfter('14 days') | Po tomto čase v stave sa stav už nedá opustiť. |
stamps('published_at', …) | Stĺpce, ktoré sa nastavia na „teraz“ pri každom vstupe do stavu prechodom. |
onEnter($hook), onExit($hook) | StateHook (trieda, inštancia alebo closure), ktorý beží v transakcii. |
meta([...]) | Ľubovoľné metadáta. |
TransitionBuilder
| Metóda | Význam |
|---|---|
from(...$states) | Zdrojové stavy; '*' = každý nekoncový stav okrem cieľového. |
fromAnyExcept(...$states) | Zástupný znak bez uvedených stavov. |
to($state) | Cieľový stav. |
allowSelf() | Povolí, aby from obsahovalo to (prechod do toho istého stavu zachová entered_at aj plány; expirácia z expiresAtAttribute sa riadi atribútom). |
systemOnly() / allowSystem() | Len systémový kód / používatelia aj systémový kód. Bez jedného z nich sa systémový kód odmietne. |
requiresActor(), actors(User::class, …), actor(fn (?Model $actor, Model $subject) => bool) | Pravidlá pre aktéra. |
ability('publish') | Gate ability, overená ako Gate::forUser($actor)->allows('publish', [$subject, $context]). |
requiresReason(int $minLength = 1) | Vyžaduje sa dôvod (používateľský kontext). |
rules([...], [...messages]), sensitive('card_number', …) | Validácia payloadu. Ponechajú sa len zvalidované kľúče; citlivé kľúče sa nikdy neukladajú. |
when($guard, code: null, message: null) | Vlastný guard: trieda, inštancia alebo closure. |
notBefore('available_from' | fn (Model $m) => ?instant, offset: null) | Nepovolené pred daným okamihom. Reťazec je vždy názov atribútu. |
notAfter('deadline' | fn (Model $m) => ?instant, offset: null) | Nepovolené po danom okamihu. Samotný okamih je ešte povolený. |
maxOccurrences(3) | Na subjekt; počíta sa zo záznamu stavu, takže čistenie histórie ho nikdy nevynuluje. |
cooldown('1 hour') | Minimálny čas medzi dvoma behmi tohto prechodu (používateľský kontext). |
rateLimit(10, '1 hour', RateLimitScope::Actor) | Cez RateLimiter Laravelu; na Actor, Subject alebo ActorAndSubject (volanie bez aktéra sa počíta na subjekt). Aj viac na jeden prechod. Kľúč sa musí zmestiť do limitu cache 250 znakov — veľmi dlhým názvom tried modelov dajte alias v morph mape. |
ignoresFreeze(), ignoresSeal(), ignoresMinDwell() | Výnimky. |
handledBy($handler) | TransitionHandler (trieda, inštancia alebo closure), ktorý beží v transakcii. |
irreversible(), reversible(within: '1 hour', withoutCompensation: false) | Pravidlá rollbacku. |
rollbackRequires('ability'), rollbackGuard($guard) | Kto smie tento prechod vrátiť. |
snapshots('price', …) | Atribúty zachytené pred prechodom a po ňom, obnovené pri rollbacku. |
label('…'), meta([...]) | Zobrazovaný názov a ľubovoľné metadáta. |
Trvania a čas
Trvania sú CarbonInterval, DateInterval alebo reťazce ako '30 days'. Mesiace a roky nikdy nepretečú (31. január + 1 mesiac = 28. február) a dni sú presné (30 dní = 720 hodín aj cez zmenu letného času). Všetky časové údaje balíka sa ukladajú v UTC.
Validácia v CI
V CI spúšťajte lifecycle:validate --strict. Vypíše každú chybu aj varovanie — nedosiahnuteľné stavy, slepé uličky, nejednoznačné ciele, handlery, ktoré robia prechod nevratným — a pri Model:atribút overí, že stĺpce stampov, atribútu expirácie a scope kvót, ktoré definícia menuje, existujú a že sa kľúč každého prechodu s rate limitom zmestí do limitu cache 250 znakov (rate_limit_key_too_long):
php artisan lifecycle:validate --strict
php artisan lifecycle:validate "App\Models\Listing:status"Metódy buildera nikdy nevyhadzujú výnimky: chybný vstup (neplatné trvanie, záporný limit, názov stĺpca, ktorý nie je identifikátor) sa zaznamená ako problém a validátor ich nahlási všetky naraz. Lifecycles::definitions()->validate($class) vráti rovnaký ValidationReport bez vyhodenia výnimky.
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.