Hook-uri de salvare
Hook-uri de salvare (Lifecycle Hooks)
ORM-ul Leto oferă hook-uri de ciclu de viață prin trait-ul HookCapability, deja inclus în clasa Model. Nu este necesar (și nici recomandat) să adaugi manual use HookCapability pe modelele tale — hook-urile funcționează direct, fără nicio configurare suplimentară. Hook-urile se definesc prin atribute PHP 8 pe metode — nu prin nume magice de metode.
Hook-uri disponibile
| Atribut | Când se execută |
|---|---|
#[BeforeCreate] | Înainte de INSERT |
#[AfterCreate] | După INSERT (ID-ul e deja populat) |
#[BeforeUpdate] | Înainte de UPDATE (doar mod single-entity) |
#[AfterUpdate] | După UPDATE (doar mod single-entity) |
#[BeforeDelete] | Înainte de DELETE (doar mod single-entity) |
#[AfterDelete] | După DELETE (doar mod single-entity) |
Fiecare atribut acceptă un parametru opțional priority (int, implicit 10). Hook-urile cu prioritate mai mică se execută primele.
use Leto\Database\ORM\Functionality\Hooks\Attributes\BeforeCreate;
use Leto\Database\ORM\Functionality\Hooks\Attributes\AfterCreate;
use Leto\Database\ORM\Functionality\Hooks\Attributes\BeforeUpdate;
use Leto\Database\ORM\Functionality\Hooks\Attributes\AfterUpdate;
use Leto\Database\ORM\Functionality\Hooks\Attributes\BeforeDelete;
use Leto\Database\ORM\Functionality\Hooks\Attributes\AfterDelete;User::where(...)->update(), User::where(...)->delete()) nu declanșează niciun hook și, în plus, nu trec prin verificările de Access Control. Dacă ai nevoie de logica din hook-uri sau de ACL, iterează instanțele și salvează-le individual.created_at / updated_at, folosește HasTimestamps; pentru recalcularea valorilor derivate folosește HasRecompute; pentru validare la salvare, HasValidation. Hook-urile sunt pentru logica proprie a aplicației tale care nu e acoperită de un trait (generare de slug-uri, notificări, reguli de business).Utilizare
Aplici atributele pe metode cu orice nume și orice vizibilitate (public, protected sau private). Singura restricție: hook-ul trebuie să fie o metodă de instanță, non-statică.
static este respins la runtime cu o excepție (A hook may not be applied onto static method ...). Hook-urile lucrează cu $this, deci au nevoie de o instanță.use Leto\Database\ORM\Attributes\Table;
use Leto\Database\ORM\Attributes\PrimaryKey;
use Leto\Database\ORM\Attributes\AutoIncrement;
use Leto\Database\ORM\Attributes\DataStorage\Varchar;
use Leto\Database\ORM\Functionality\Hooks\Attributes\BeforeCreate;
use Leto\Database\ORM\Functionality\Hooks\Attributes\AfterCreate;
use Leto\Database\ORM\Functionality\Hooks\Attributes\BeforeDelete;
use Leto\Database\ORM\Model;
#[Table('articles')]
class Article extends Model
{
#[PrimaryKey, AutoIncrement]
public int $id;
#[Varchar(150)]
public string $title;
#[Varchar(150)]
public string $slug;
#[Varchar(100)]
public string $author_email;
#[Varchar(20)]
public string $status;
#[BeforeCreate(priority: 5)]
protected function generateSlug(): void
{
// Logică proprie: derivă slug-ul din titlu înainte de INSERT
$this->slug = strtolower(trim(preg_replace('/[^a-z0-9]+/i', '-', $this->title), '-'));
}
#[BeforeCreate]
protected function normalizeEmail(): void
{
// Prioritate implicită 10, deci rulează după generateSlug
$this->author_email = strtolower(trim($this->author_email));
}
#[AfterCreate]
protected function notifyEditors(): void
{
// ID-ul e deja populat aici
NotificationService::announceNewArticle($this);
}
#[BeforeDelete(priority: 1)]
protected function preventDeletePublished(): void
{
if ($this->status === 'published') {
throw new \Exception('Nu poți șterge un articol publicat.');
}
}
}Prioritatea
Hook-urile sunt sortate după priority (crescător), în cadrul aceluiași tip de hook (ex. toate hook-urile #[BeforeCreate] formează o coadă separată de cele #[BeforeUpdate]). Valoarea implicită este 10.
În exemplul de mai sus, hook-urile #[BeforeCreate] se execută în ordinea:
generateSlug(priority: 5) — se execută primulnormalizeEmailși alte hook-uri#[BeforeCreate]cu prioritatea implicită10— după
Ordinea de execuție
create():
#[BeforeCreate] → INSERT → #[AfterCreate]
update() (single-entity):
#[BeforeUpdate] → UPDATE → #[AfterUpdate]
delete() (single-entity):
#[BeforeDelete] → DELETE → #[AfterDelete]Limitări
- Hook-urile nu rulează în operațiunile bulk (vezi avertismentul de mai sus).
- Nu există un bloc
try/finallyîntre hook-ulbefore*șiafter*. Dacă apare o excepție între ele (de exemplu la$query->execute()), hook-ulafter*nu se mai execută, deci trebuie să gestionezi această situație în codul tău.