Salt la conținut

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

Proprietățile generate de relații ($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();