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

Subjekty v multi-tenant aplikácii

Sweep, job naplánovaného prechodu vo fronte, upozornenia na expiráciu, schedules()->retry() a adoptAll() / lifecycle:adopt pracujú so subjektom mimo kontextu vášho kódu: konzolový príkaz ani worker fronty nemá žiadneho tenanta, prípadne má iného. Od verzie 2.1 každý z nich spracuje subjekt v kontexte jeho tenanta, a to cez jeden kontrakt:

namespace RoundlyConsulting\Lifecycle\Contracts;

use Closure;
use Illuminate\Database\Eloquent\Model;
use RoundlyConsulting\Lifecycle\Exceptions\SubjectContextUnavailableException;

interface RestoresSubjectContext
{
    /** @throws SubjectContextUnavailableException when the subject's tenant is gone */
    public function run(Model $subject, Closure $callback): mixed;
}

run() vstúpi do kontextu subjektu, zavolá callback a potom obnoví predchádzajúci kontext — aj vtedy, keď callback vyhodí výnimku. V tomto kontexte bežia všetky udalosti, hooky, handlery aj joby prechodu subjektu, takže sa záznam uloží ako vlastný tenant a e-maily, notifikácie a nadväzujúce joby, ktoré spustí, patria tiež tomuto tenantovi.

CestaČo beží v kontexte
lifecycle:sweep, sweep(), schedules()->runDue()Transakcia každého splatného plánu — priamo alebo v RunScheduledTransitionJob vo fronte.
sweep(), schedules()->warn()Odoslanie LifecycleExpiring pri každom upozornení na expiráciu.
schedules()->retry($id), retryScheduled()Opakovanie plánu.
adoptAll(), model()->adopt(), lifecycle:adoptAdopcia každého riadku.

Všetko, čo voláte s modelom v ruke — $ticket->transition(), Lifecycles::for($ticket), adopt($ticket), zmrazenie, rollback — beží ako doteraz vo vašom vlastnom kontexte. Kontrola kvóty len číta a počíta celú tabuľku bez globálnych scopes, preto pre limit na tím nastavte scope kvóty na stĺpec tímu:

$lifecycle->state(TicketStatus::Open)
    ->quota(5, scope: 'team_id');   // a per-team limit: the quota count itself ignores global scopes

Ktorý kontext

lifecycle.subjects.context určuje triedu RestoresSubjectContext. Kľúč je vedľa zoznamu modelov; obyčajný zoznam modelov funguje ďalej:

// config/lifecycle.php
'subjects' => [
    App\Models\Ticket::class,

    'context' => null,   // null = the teams-for-laravel bridge when teams is installed, else none
],
  • null (predvolené) — most k teams-for-laravel, ak je teams nainštalovaný, inak Support\NullSubjectContext, ktorý callback spustí bez zmeny.
  • Vlastná trieda — ľubovoľný spôsob multi-tenancy. Hodnota, ktorá nie je inštanciovateľnou implementáciou (samotný kontrakt, abstraktná trieda, neexistujúca či nesúvisiaca trieda, hodnota, ktorá nie je reťazec), vyhodí InvalidLifecycleConfigurationException.
  • php artisan about v sekcii Lifecycle zobrazí Subject context: teams, none alebo krátky názov vašej triedy.

teams-for-laravel

Ak máte nainštalovaný teams-for-laravel, subjekt s ScopedToTeam beží v Teams::runAs() svojho vlastného tímu — bez akéhokoľvek nastavovania. Kľúč tímu sa prečíta z getTeamColumn() a tím sa načíta cez nakonfigurovaný model tímu. Bez toho by ochrana zápisov v teams zápis odmietla, pretože chýba aktuálny tím, takže naplánovaný prechod ani expirácia cez TTL (expiresVia) subjektu viazaného na tím by sa z konzoly nikdy nespustili — vyriešený tiket by sa nikdy automaticky neuzavrel. Joby odoslané v tomto kontexte nesú tím ďalej, pretože teams prenáša svoj kontext do jobov vo fronte. Model bez ScopedToTeam, aj taký, ktorý používa len BelongsToTeam, beží bez zmeny.

Tenant, ktorý už neexistuje

Ak bol tím subjektu zmazaný (počítajú sa aj soft-deleted tímy), uložený tím neexistuje alebo je stĺpec tímu NULL, kontext vyhodí SubjectContextUnavailableException a so subjektom sa nič neurobí:

CestaVýsledok
Splatný plánNespustí sa, nejde o chybu a nezapočíta sa pokus: due_at sa posunie na teraz + schedules.retry_after a last_denial bude context_missing. Započíta sa ako contextMissing — stĺpec context missing v lifecycle:sweep a k tomu riadok s varovaním. Spustí sa, keď sa tím vráti, napríklad po obnovení.
Upozornenie na expiráciuNeodošle sa ani nenahlási: next_warn_at sa posunie na teraz + schedules.retry_after.
schedules()->retry()Vráti false; riadok zostane failed.
adoptAll() / lifecycle:adoptRiadok sa preskočí a odovzdá callbacku skipped; príkaz vypíše pre každý riadok Skipped: … a nakoniec súhrn Skipped N subject(s).
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Log;
use RoundlyConsulting\Lifecycle\Exceptions\SubjectContextUnavailableException;
use RoundlyConsulting\Lifecycle\Facades\Lifecycles;

$result = Lifecycles::sweep();
$result->contextMissing;                       // schedules put off: their subject's team is gone

Lifecycles::schedules()->retry($scheduleId);   // false while the subject's team is gone — the row stays failed

Lifecycles::model(Ticket::class)->adopt(
    skipped: fn (Model $ticket, SubjectContextUnavailableException $e) => Log::warning($e->getMessage()),
);

Plán tímu, ktorý sa už nevráti, sa odkladá o každé retry_after. Keď tím odstraňujete natrvalo, ukončite plány jeho subjektov v Teams::withoutTeamScope() — cancelScheduled('…'), neverExpire() — alebo subjekty natrvalo zmažte, čím sa vyčistia aj ich plány.

Vlastná multi-tenancy

Implementujte kontrakt, pri tenantovi, ktorý už neexistuje, vyhoďte SubjectContextUnavailableException — balík ju na všetkých vyššie uvedených cestách zachytí — a triedu uveďte v konfigurácii:

use Closure;
use Illuminate\Database\Eloquent\Model;
use RoundlyConsulting\Lifecycle\Contracts\RestoresSubjectContext;
use RoundlyConsulting\Lifecycle\Exceptions\SubjectContextUnavailableException;

final class TenantSubjectContext implements RestoresSubjectContext
{
    public function run(Model $subject, Closure $callback): mixed
    {
        $tenant = Tenant::find($subject->tenant_id)
            ?? throw new SubjectContextUnavailableException("Tenant {$subject->tenant_id} is gone.");

        return $tenant->execute($callback); // restores the previous tenant, also on an exception
    }
}

// config/lifecycle.php
'subjects' => [
    App\Models\Ticket::class,
    'context' => App\Lifecycle\TenantSubjectContext::class,
],

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.