Salt la conținut

Best practices

Păstrează modelele slabe

Un model e un DTO + metode simple. Logica grea de business (calcule multi-model, orchestrări, integrări externe) trăiește în servicii / use-case-uri, nu în model. Excepție: metode de domeniu care operează doar pe datele modelului curent (ex. Invoice::computePayable() care folosește $this->lines).

// ✅ BINE: Model curat
#[Table('users')]
class User extends Model
{
    #[PrimaryKey, AutoIncrement]
    public int $id;

    #[Varchar(100)]
    public string $name;

    #[Computable('initials', 'string')]
    public static function computeInitials(array $models): array
    {
        return array_map(fn($m) => strtoupper(substr($m->name, 0, 1)), $models);
    }
}

// ❌ RĂU: Logică de business în model
class User extends Model
{
    public function sendWelcomeEmail() { /* ... */ }
    public function createInvoice() { /* ... */ }
}

Declară @property în PHPDoc

Relațiile și atributele computable sunt proprietăți virtuale — nu sunt declarate explicit în clasă. Pentru autocomplete în IDE, declară-le în PHPDoc:

/**
 * @property ParentModel $parent          // relație BelongsTo
 * @property Student[] $students          // relație HasMany
 * @property string $full_name            // computable
 * @property int $warning_count           // computable
 */
#[Table('school_students')]
class Student extends Model
{
    #[PrimaryKey, AutoIncrement]
    public int $id;

    #[BelongsTo('parent', ParentModel::class, 'id')]
    public int $parent_id;

    public ?string $name = null;
    // ...
}

Tipuri nullable explicit

Dacă o coloană din BD poate fi NULL, declară tipul ca ?type cu = null:

#[Varchar(50)]
public ?string $middle_name = null;

Dacă e non-null în BD, lasă proprietatea fără null și neinițializată (fără = null) — ORM-ul verifică tipurile la serializare și va semnala dacă lipsește o valoare obligatorie:

#[Varchar(100)]
public string $name;  // fără = null, fără valoare implicită

BackedEnum pentru statusuri

enum UserStatus: string
{
    case Active = 'active';
    case Inactive = 'inactive';
    case Banned = 'banned';
}

class User extends Model
{
    // BackedEnum-urile sunt auto-detectate — ORM-ul creează un ENUM din cazuri
    #[DefaultValue('active')]
    public UserStatus $status;
}

DateTimeImmutable pentru date

class Event extends Model
{
    #[DateTime]
    public \DateTimeImmutable $starts_at;

    #[Date]
    public \DateTimeImmutable $event_date;
}

BcMath\Number pentru valori monetare

Nu folosi float pentru prețuri sau sume — folosește BcMath\Number cu #[Decimal]. Aritmetica e exactă, neafectată de precizia IEEE 754:

use BcMath\Number;

class Invoice extends Model
{
    #[Decimal(10, 2)]
    public Number $total_amount;

    #[Decimal(10, 2)]
    public Number $discount_amount;

    #[Decimal(10, 2)]
    public Number $payable_amount;
}

// Operații exacte (BcMath\Number suportă operatorii aritmetici în PHP 8.4)
$invoice->payable_amount = $invoice->total_amount - $invoice->discount_amount;

Eager loading — evită N+1

Ori de câte ori iterezi peste o colecție și accesezi relații sau atribute computable, folosește with() ca să eviți problema N+1. Explicația completă și exemplul înainte/după sunt în secțiunea de eager loading din Relații.

Reguli practice:

  • Orice dată accesezi o relație într-un foreach, cheamă with() înainte de get().
  • Orice #[Computable] care face interogări, folosit într-o listă: eager cu with().
  • Pentru listări mari (mii de rânduri), paginează cu paginate().
  • Dacă performanța e critică și ai nevoie doar de câteva coloane, folosește db() direct cu SELECT țintit.

Înfășoară operațiile multi-entity în tranzacții

Orice operațiune care atinge mai multe rânduri sau tabele merită înfășurată într-o tranzacție (withTransaction()), ca să rămână atomică. Vezi exemplul complet în Operațiuni CRUD.

Nu atribui proprietăți pe model în controller

// ✅ BINE — instanțiere + salvare
$user = new User();
$user->name = $request->input('name');
$user->email = $request->input('email');
$user->create();

orderBy cu enum-ul Order

La nivelul modelului, al doilea argument al lui orderBy() este enum-ul Order (Order::ASC / Order::DESC), nu un string. Detalii și exemplu în secțiunea de sortare din Operațiuni CRUD.