Relații
Relații între modele
ORM-ul Leto suportă patru tipuri de relații, definite prin atribute PHP. Toate atributele de relație au primul parametru numele virtual al relației — acesta va fi numele proprietății prin care accesezi relația.
De ce ai nevoie de PHPDoc
$order->user, $user->orders) nu sunt declarate explicit în clasă — sunt create dinamic de ORM la accesare. Ca să beneficiezi de autocompletare în IDE și type checking, declară-le în blocul /** @property */ al modelului. Fiecare exemplu de mai jos include PHPDoc-ul recomandat.#[BelongsTo]
Relația inversă a HasMany / HasOne. Cheia străină este pe modelul curent.
use Leto\Database\ORM\Attributes\Relationships\BelongsTo;
/**
* @property User $user
*/
class Order extends Model
{
#[BelongsTo('user', User::class, 'user_id')]
public ?int $user_id = null;
}
// Utilizare
$order = Order::find(1);
echo $order->user->name; // Încarcă automat relația
Semnătură: #[BelongsTo('nume_relatie', ModelClass::class, 'pk_in_modelul_mama')]
#[HasOne]
Modelul curent are exact o înregistrare în altă tabelă.
use Leto\Database\ORM\Attributes\Relationships\HasOne;
/**
* @property Profile $profile
*/
class User extends Model
{
#[HasOne('profile', Profile::class, 'user_id')]
public int $id;
}
// Utilizare
$user = User::find(1);
echo $user->profile->bio;Semnătură: #[HasOne('nume_relatie', ChildModel::class, 'fk_in_child')]
#[HasMany]
Modelul curent are mai multe înregistrări în altă tabelă. HasMany este repetabil — poți pune mai multe pe aceeași proprietate (de obicei pe $id). Exemplu real din modulul School:
/**
* @property RegistrationRequestContact[] $contact_info
* @property RegistrationRequestStudent[] $candidates
* @property RegistrationRequestPayment[] $payments
*/
class RegistrationRequest extends Model
{
#[PrimaryKey, AutoIncrement]
#[HasMany('contact_info', RegistrationRequestContact::class, 'request_id')]
#[HasMany('candidates', RegistrationRequestStudent::class, 'request_id')]
#[HasMany('payments', RegistrationRequestPayment::class, 'request_id')]
public int $id;
}De asemenea, poți combina #[BelongsTo] pe o cheie primară compusă:
use Leto\Database\ORM\Attributes\Relationships\HasMany;
/**
* @property Order[] $orders
*/
class User extends Model
{
#[HasMany('orders', Order::class, 'user_id')]
public int $id;
}
// Utilizare
$user = User::find(1);
foreach ($user->orders as $order) {
echo $order->total;
}Semnătură: #[HasMany('nume_relatie', ChildModel::class, 'fk_in_child')]
#[HasManyThrough]
Relație many-to-many printr-o tabelă intermediară (pivot).
use Leto\Database\ORM\Attributes\Relationships\HasManyThrough;
/**
* @property Role[] $roles
*/
class User extends Model
{
#[HasManyThrough('roles', Role::class, 'role_id', UserRole::class, 'user_id', 'role_id')]
public int $id;
}
// Utilizare
$user = User::find(1);
foreach ($user->roles as $role) {
echo $role->name;
}Semnătură: #[HasManyThrough('nume_relatie', TargetModel::class, 'fk_in_target', ThroughModel::class, 'pk_through_to_this', 'fk_through_to_target')]
Eager loading cu with()
Pentru a evita problema N+1, folosește with():
// FĂRĂ eager loading — N+1 interogări
$users = User::all()->get();
foreach ($users as $user) {
echo $user->orders[0]->id; // Interogare nouă pentru fiecare user!
}
// CU eager loading — 2 interogări
$users = User::all()->with('orders')->get();
foreach ($users as $user) {
echo $user->orders[0]->id; // Datele sunt deja încărcate
}with() suportă și relații imbricate:
User::all()->with('orders.items')->get();