História a auditná stopa
Zapnite attributes.history.enabled a pri každom pripojení, zmene hodnoty či metadát alebo odpojení atribútu sa zaznamená revízia starý→nový. Predvolene je vypnutá — kým ju nezapnete, nezapíše sa ani jedna revízia:
ATTRIBUTES_HISTORY=trueuse RoundlyConsulting\Attributes\Facades\Attributes;
Attributes::for($product)->history(); // Collection<AttributeRevision> — newest first
Attributes::for($product)->history('color'); // filtered by attribute name
$product->history('color'); // model shorthand
$product->attributeHistory(); // the underlying MorphMany relationČo revízia obsahuje
| Stĺpec | Obsahuje |
|---|---|
type | RevisionType — attached, updated alebo detached. |
name | Názov atribútu. |
old_value, new_value | Uložené reťazce pred zmenou a po nej — pri šifrovaných atribútoch šifrový text. |
old_value_type, new_value_type | Hodnoty AttributeType pred zmenou a po nej. |
old_meta, new_meta | Metadáta pred zmenou a po nej, pretypované na Collection. |
owner_type, owner_id | Vlastniaci model (relácia owner() typu morph-to). |
actor_type, actor_id | Kto zmenu urobil (relácia actor() typu morph-to) — pri zapnutom history.actor.enabled; null, ak sa nikto nezaznamenal. |
created_at | Čas zmeny. |
use RoundlyConsulting\Attributes\Enums\RevisionType;
foreach ($product->history('color') as $revision) {
$revision->type; // RevisionType::Attached | Updated | Detached
$revision->old_value; // previous storage string, or null
$revision->new_value; // new storage string, or null on detach
$revision->old_value_type; // 'string', 'integer', ...
$revision->new_value_type;
$revision->old_meta; // Collection|null
$revision->new_meta;
$revision->created_at;
$revision->type->is(RevisionType::Updated); // bool
}
$product->attributeHistory('color')->limit(10)->get(); // a relation — query it freelyČo sa zaznamenáva
- attached — prvý zápis názvu cez ktorúkoľvek zapisovaciu metódu, vrátane meta(), a opätovné pripojenie odpojeného názvu.
- updated — zápis hodnoty či metadát k pripojenému názvu, vrátane hodnôt znova odoslaných synchronizáciou.
- detached — každé odstránenie pripojeného názvu: forget(), forgetExcept() a atribúty navyše, ktoré odstráni sync(), aj ich metódy na modeli — a trvalé zmazanie vlastníka.
Kto zmenu urobil
Zapnite aj history.actor.enabled a každá revízia zaznamená aj svojho autora — kto zmenu urobil — do nullable stĺpcov actor. Najprv spustite migráciu update_attribute_revisions_add_actor (pozrite Inštalácia); kým voľbu nezapnete, zostáva vypnutá:
ATTRIBUTES_HISTORY=true
ATTRIBUTES_HISTORY_ACTOR=true
# optional: take the default actor from another guard
ATTRIBUTES_HISTORY_ACTOR_GUARD=adminAutor sa určuje v tomto poradí:
- autor, ktorého uvedie zápis — Attributes::for($model)->by($user)->set(…); by(null) nezaznamená nikoho, pre systémovú zmenu;
- inak to, čo vráti váš resolver — Attributes::resolveActorUsing(fn () => …), uložený model alebo null;
- inak prihlásený používateľ guardu history.actor.guard (pri null predvolený guard), ak je to uložený Eloquent model — inak nikto.
use RoundlyConsulting\Attributes\Facades\Attributes;
// The signed-in user of the configured guard is recorded by default
Attributes::for($product)->set('price', 12);
// Name the actor for this handle's writes — an admin acting for a customer
Attributes::for($product)->by($admin)->set('price', 10);
Attributes::for($product)->by($admin)->sync(['price' => 10, 'color' => 'red']);
// by(null) records nobody — a system change, such as a background import
Attributes::for($product)->by(null)->setMany($importedValues);by() určí autora pre každý zápis daného handlu — set(), setMany(), setValidated(), sync(), forget(), forgetExcept(), meta() aj stage() — a len pre tento handle. Neuložený model vyhodí AttributesException, rovnako ako resolver, ktorý vráti niečo iné ako uložený model alebo null. Zápisy vo volaní by() po skončení obnovia vonkajšieho autora, takže vnorené zápisy svojho autora nikam neprenesú.
Resolver zaregistrujte v service provideri. Kým je história vypnutá, nikdy sa nezavolá, a platí pre celý proces — pod Octane pretrvá medzi requestmi, čo je pri resolveri žiaduce:
// app/Providers/AppServiceProvider.php — boot()
use RoundlyConsulting\Attributes\Facades\Attributes;
Attributes::resolveActorUsing(fn () => auth('admin')->user() ?? auth()->user());
Attributes::resolveActorUsing(null); // back to the guard defaultAutora z revízie prečítate cez reláciu actor(), surové stĺpce cez actorType() a actorId() — tie vrátia null, nikdy nevyhodia výnimku, ak stĺpce actor ešte neboli zmigrované:
$revision = $product->attributeHistory('price')->with('actor')->first();
$revision->actor; // the User model — null when nobody was recorded
$revision->actorType(); // the stored actor_type (class or morph alias), or null
$revision->actorId(); // 42, or nullRevízie v API
AttributeRevisionResource vykreslí revíziu pre API. Vlastník sa vynecháva, pretože história sa vypisuje pre konkrétneho vlastníka:
use RoundlyConsulting\Attributes\Http\Resources\AttributeRevisionResource;
// A product's history for an API, newest first
return AttributeRevisionResource::collection($product->history());{
"id": 12,
"name": "price",
"type": "updated",
"old_value": "12",
"new_value": "10",
"old_value_type": "integer",
"new_value_type": "integer",
"old_meta": null,
"new_meta": null,
"actor": { "id": 42, "type": "User" },
"created_at": "2026-11-09T09:30:00.000000Z"
}old_value a new_value sú uložený text, takže revízia šifrovaného atribútu nesie šifrový text, nikdy otvorenú hodnotu. actor je null pre revíziu bez autora; inak sa vykreslí cez resources.revision_actor. Predvolený výstup je { id, type }, kde type je krátky názov triedy modelu autora (User) — pri morph map krátky názov namapovaného modelu, nikdy alias —, takže žiadny názov triedy sa k API klientovi nedostane. Číta vlastné stĺpce revízie, preto nepridá dopyt na riadok a vykreslí aj zmazaného autora.
Vlastná podoba autora
Ak chcete pridať meno či avatar, nasmerujte resources.revision_actor na vlastného potomka JsonResource. Dostane AttributeRevision — čítajte actorId(), actorType() alebo reláciu actor, ktorú si v takom prípade načítajte vopred:
// config/attributes.php
'resources' => [
'revision_actor' => App\Http\Resources\RevisionActorResource::class,
],
// app/Http/Resources/RevisionActorResource.php — receives the AttributeRevision
final class RevisionActorResource extends JsonResource
{
public function toArray(Request $request): array
{
return [
'id' => $this->resource->actorId(),
'name' => $this->resource->actor?->name,
];
}
}
// Eager-load the actor so each row doesn't query it
return AttributeRevisionResource::collection(
$product->attributeHistory()->with('actor')->latest()->get(),
);Čokoľvek, čo nie je inštanciovateľný potomok JsonResource — neexistujúca trieda, samotný JsonResource, abstraktný resource —, vyhodí InvalidConfigurationException s názvom kľúča; null alebo prázdna hodnota použije predvolený resource.
Prečistenie revízií
attributes:prune sa týka len atribútov — prečistenie tabuľky revízií je na vašej aplikácii, napríklad plánovaným dopytom:
// routes/console.php
use Illuminate\Support\Facades\Schedule;
use RoundlyConsulting\Attributes\Models\AttributeRevision;
Schedule::call(function (): void {
AttributeRevision::query()
->where('created_at', '<', now()->subYear())
->forceDelete();
})->weekly();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.