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

Pečatenie a zápisy

Vytvorenie modelu ho zapečatí; aktualizácia znovu zapečatí pečate, ktorých stĺpce sa zmenili (pečate s vypočítanými hodnotami pri každej aktualizácii). Pokrytá je každá cesta Eloquent zápisu — save, update, create, firstOrCreate, updateOrCreate, touch, push, increment a decrement (na Laraveli 13+ aj incrementEach / decrementEach), restore, delete, forceDelete a tiché varianty. Automatické pečatenie vyžaduje zapnuté sealing.auto, pečať auto() a žiadne aktívne pozastavenie.

SpúšťačČo sa staneUdalosť v denníku
Eloquent vytvorenie (create, save nového modelu, firstOrCreate, updateOrCreate)Zapíše sa každá auto() pečať.sealed
Eloquent aktualizácia (save, update, touch, push, increment / decrement, restore, quiet varianty)Znovu sa zapečatia pečate, ktorých stĺpce sa zmenili; pečate s vypočítanými hodnotami pri každej aktualizácii.resealed
Sentinel::seal($model), Sentinel::for($model)->seal(), $model->seal()Pečať sa zapíše (pri zmenenom modeli sa odmietne).sealed / resealed
acknowledge()Znovu zapečatí model, ktorý nie je neporušený.acknowledged
sentinel:reseal, Sentinel::model(…)->reseal()Znovu zapečatí neporušené riadky aktuálnym kľúčom / definíciou.rotated
sentinel:seal-missing, sealMissing()Zapečatí riadky, ktoré nikdy pečať nemali.baseline
Tvrdé zmazanie / forceDeleteRiadky pečatí sa zmažú, pridá sa tombstone.deleted
unseal($reason)Riadok pečate sa zmaže, pridá sa tombstone.unsealed

Na Laraveli 12 model incrementEach() nemá: volanie prepadne do query buildera a aktualizuje všetky riadky tabuľky bez pečatenia — použite tam increment() pre každý stĺpec.

Explicitné zapečatenie

use RoundlyConsulting\Sentinel\Actions\Seals\SealModelAction;
use RoundlyConsulting\Sentinel\DataTransferObjects\SealRequest;

$result = Sentinel::for($invoice)->by($admin)->because('Lines recomputed')->seal();
$result = Sentinel::seal($invoice, 'financial', reason: 'Lines recomputed', actor: $admin);
$result = $invoice->seal('financial');
$result = app(SealModelAction::class)->execute(new SealRequest($invoice, 'financial', 'Lines recomputed', $admin));

$result->version;     // the new version
$result->event;       // SealEvent::Sealed (first seal) or SealEvent::Resealed
$result->keyId;       // the ring's current signing key

Explicitným zapečatením sa zapisujú pečate manual() a prepečatí sa posun len vypočítaných hodnôt. Zapečatí modely, ktoré sú neporušené, zastarané, nikdy nezapečatené, odpečatené (benevolentné pečate) alebo sa v nich zmenili len vypočítané polia; čokoľvek iné vyhodí TamperedModelException. Explicitné zapečatenie nikdy potichu nelegalizuje zmenu — zmenený model treba potvrdiť. Neuložený model vyhodí SealingFailedException::notPersisted.

Atomicita

HasSeals posiela každý zápis cez manažéra, ktorý spustí zápis aplikácie aj pečatenie v jednej transakcii na pripojení modelu (vo vonkajšej transakcii ako savepoint). Zápis aplikácie sa nikdy neopakuje. Akékoľvek zlyhanie pečatenia — chýbajúci podpisový kľúč, hodnota, ktorá sa nedá kanonizovať, prehraný súbeh verzií — vyhodí výnimku a zápis sa vráti.

Pečatenie zamkne riadok modelu a zapečatené stĺpce načíta tak, ako sú uložené, takže predvolené hodnoty databázy, triggery aj prepisy enginu sa zapečatia presne tak, ako ich databáza drží. Potom zamkne (alebo vytvorí) riadok pečate, určí ďalšiu verziu, zostaví kanonický dokument, vypočíta MAC alebo podpis, pridá záznam do denníka a riadok pečate aktualizuje cez compare-and-swap na verzii; ModelSealed sa spustí po commite. Poradie zámkov je vždy riadok modelu → riadok pečate → zápis do denníka, takže nevznikajú cykly zámkov.

Listener modelu, ktorý v rámci zápisu znovu uloží ten istý riadok — napríklad listener created, ktorý faktúru očísluje cez saveQuietly() — sa pripojí k vonkajšiemu zápisu: ten drží zámok riadku a na konci raz prepečatí každú auto pečať nad riadkom tak, ako je uložený.

Zápisy do modelu, ktorý nie je neporušený

Pred aktualizáciou alebo inkrementom sa riadok zamkne a overí vrátane kontroly denníka. Model zmenený mimo aplikácie sa spracuje podľa sealing.on_tampered_write alebo onTamperedWrite() pečate:

PolitikaÚčinok
refusePredvolená. TamperedModelException; nič sa nezapíše. Najprv zmenu potvrďte.
resealZapíše sa a znovu zapečatí; záznam v denníku uchová previous_status; spustí sa TamperDetected.
skipZapíše sa, pečať ostane ako bola (stále nie je neporušená).

Neškodný posun sa vždy prepečatí a zaaudituje: zmenená definícia (Outdated) a vypočítané polia, ktorých zdrojové riadky sa zmenili (Tampered s dôvodom computed — dokázané MAC atribútov pečate, nikdy značkami polí). Vytvorenie s id, ktoré predtým patrilo riadku zmazanému mimo aplikácie, sa riadi tou istou politikou so Stale(entity_recreated).

Mazanie

  • Tvrdé zmazanie (vrátane forceDelete) sa nikdy neodmietne. Riadky pečatí sa zmažú a do denníka sa pre každú pečať pridá tombstone deleted — aj pre pečate manual() — so stavom, ktorý model mal.
  • Soft delete pečať ponechá a tombstone nezapíše; obnovenie ide cez save().
  • Zmazanie neuloženého modelu nič neurobí a vráti null, rovnako ako v Eloquente.
  • S vypnutým sealing.auto alebo vo withoutSealing() sa tombstone nezapíše, takže sentinel:verify --ledger nahlási zmazanie ako EntityDeleted, rovnako ako zmazanie mimo aplikácie.

Pozastavenie

Pre seedery a importy pozastavte pečatenie na čas callbacku. Pozastavenie platí v rámci požiadavky alebo jobu (bezpečné pre Octane), dá sa vnárať, obnoví sa aj pri výnimke v callbacku a zaaudituje ho udalosť SealingSuspended. Treba ho výslovne zapnúť: sealing.allow_suspension je predvolene vypnuté a kým nenastavíte SENTINEL_ALLOW_SUSPENSION=true (prázdna hodnota SENTINEL_ALLOW_SUSPENSION= sa považuje za nenastavenú, takže zostane vypnuté), withoutSealing() vyhodí SealingSuspensionNotAllowedException a callback sa vôbec nespustí. Zápisy vo vnútri sú nezapečatené alebo zastarané; striktné pečate ich hlásia, kým nepríde sealMissing() alebo potvrdenie.

// .env (only where seeders or imports run): SENTINEL_ALLOW_SUSPENSION=true
Sentinel::withoutSealing(fn () => Invoice::query()->create($row), reason: 'Legacy import');

Prepis save() alebo delete()

Každé uloženie a zmazanie zapečatiteľného modelu musí prebehnúť v zapečatenej ceste zápisu, persistSealed(). V triede, ktorá používa HasSeals, prepis zatieni metódu traitu, preto obaľte volanie rodiča:

public function save(array $options = []): bool
{
    $this->number = trim((string) $this->number);

    return $this->persistSealed(fn (): bool => parent::save($options)) === true;
}

public function delete(): ?bool
{
    return $this->persistSealed(fn (): ?bool => parent::delete(), PersistOperation::Delete);
}

Podtrieda zapečatiteľného modelu jednoducho volá parent::save() — metóda rodiča je tá zapečatená. Uloženie či zmazanie, ktoré zapečatenú cestu obíde, sa odmietne skôr, než sa čokoľvek zapíše (SealingMisconfiguredException), a to aj pri tichých zápisoch a withoutEvents().

Uzly len na overovanie a obchádzky

Servery len s verejnými kľúčmi nastavia SENTINEL_AUTO_SEAL=false: ukladanie prebieha bez pečatenia, overovanie funguje ako zvyčajne a explicitné seal() vyhodí NoSigningKeyException. Zápisy cez query builder (Invoice::query()->update(), DB::table(), upsert(), insert()) a surové SQL nikdy nepečatia — odhalia sa dodatočne ako Tampered alebo Missing. Na zámerné hromadné aktualizácie použite updateAndReseal() (pozri Hromadné operácie).

Výkon

Z orientačného výkonnostného testu balíka — 1 000 riadkov vytvorených cez Eloquent (každý zapečatený vo svojej transakcii) a overených skenom po dávkach, na SQLite v pamäti s PHP 8.4 na Apple M3 Pro:

AlgoritmusPečatenie (create + seal)Overenie (sken)
hmac-sha2561.06 ms/row0.27 ms/row
hmac-sha3841.01 ms/row0.25 ms/row
hmac-sha5121.03 ms/row0.25 ms/row
ed255191.04 ms/row0.30 ms/row
ecdsa-p256-sha2561.10 ms/row0.45 ms/row
ecdsa-p384-sha3842.01 ms/row1.09 ms/row

Zapečatená aktualizácia pridá pred zápisom jedno zamknuté čítanie a jeden MAC. Načítanie zapečatiteľného modelu nestojí nič navyše — 5 000 riadkov za 31 ms, rovnako ako obyčajný Eloquent — pokiaľ niektorá z jeho pečatí neoveruje pri načítaní; až vtedy HasSeals zaregistruje listener retrieved. Kde na tom záleží, označte pečať manual() a automatické pečatenie vypnete.

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.