NovinkaZverejnili sme 50+ Laravel balíkov ako open source
Custom AI apps, agents and automation — Roundly ConsultingRoundly
Všetky balíky
Lifecycle for Laravel

Definovanie životného cyklu

Hodí sa pre akýkoľvek Eloquent model so stavom — niekoľko typických cyklov:

ModelTypický cyklusPravidlá
Objednávkapending → paid → shipped, refundedstav platby ako druhý cyklus
Tiketnew → open → waiting → resolvedznovu otvorený najviac 3-krát
Inzerátdraft → active → expiredvyprší po 30 dňoch, najviac 5 aktívnych na zákazníka
Predplatnétrial → active → grace → cancelledobnovené alebo znovu aktivované
Žiadosťsubmitted → in review → approved / rejectedvrá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-stubs

LifecycleBuilder

MetódaVý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ódaVý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ódaVý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 kryptomien

Odoslaní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.