Salt la conținut

Atribute calculate

Atributul #[Computable] permite definirea de proprietăți virtuale care nu sunt stocate în baza de date, ci calculate eficient în bulk pentru o listă de modele. Este util pentru agregări, lookup-uri externe sau join-uri cross-namespace.

#[Computable] se pune pe o metodă statică cu semnătură fixă.

Semnătură

#[Computable('nume_atribut', 'tipul_php', nullable: bool, arrayType: ?string)]
protected static function loaderMethod(array $models): array
  • Primește lista completă de modele
  • Trebuie să returneze o listă de aceeași dimensiune, în aceeași ordine
  • Fiecare element din array-ul returnat corespunde modelului de pe aceeași poziție

Exemplu: Nume complet

use Leto\Database\ORM\Attributes\Computable;

/**
 * @property string $full_name
 */
class User extends Model
{
    #[Varchar(50)]
    public string $first_name;

    #[Varchar(50)]
    public string $last_name;

    #[Computable('full_name', 'string')]
    protected static function loadFullName(array $models): array
    {
        return array_map(
            fn(User $u) => $u->first_name . ' ' . $u->last_name,
            $models
        );
    }
}

// Utilizare
$users = User::all()->with('full_name')->get();
foreach ($users as $user) {
    echo $user->full_name; // "Ion Popescu"
}

Exemplu: Agregare cu relații

/**
 * @property OrderItem[] $items
 * @property string $total
 */
class Order extends Model
{
    #[HasMany('items', OrderItem::class, 'order_id')]
    public int $id;

    #[Computable('total', 'string')]  // BcMath\Number ca string
    protected static function loadTotal(array $models): array
    {
        // Încarcă toate items pentru toate order-ele din listă
        $orderIds = array_map(fn(Order $o) => $o->id, $models);
        $items = OrderItem::all()
            ->where('order_id', 'IN', $orderIds)
            ->get();

        // Grupează pe order_id
        $totals = [];
        foreach ($items as $item) {
            $totals[$item->order_id] = bcadd(
                $totals[$item->order_id] ?? '0',
                bcmul($item->price, $item->quantity, 2),
                2
            );
        }

        // Returnează în aceeași ordine ca modelele primite
        return array_map(
            fn(Order $o) => $totals[$o->id] ?? '0.00',
            $models
        );
    }
}

Exemplu avansat: cross-namespace

În Jobs, #[Computable] e folosit pentru a încărca date din namespace-uri diferite:

#[Computable('user', 'array', true)]
protected static function getUsers(array $models): array
{
    // Colectează user IDs per namespace
    $userIds = [];
    foreach ($models as $model) {
        if (!$model->namespace || !$model->user_id) continue;
        $userIds[$model->namespace][$model->user_id] = true;
    }

    // Încarcă din fiecare namespace
    $results = [];
    foreach ($userIds as $namespace => $data) {
        Company::executeInCompanyContext($namespace, function () use (...) {
            $results[$namespace] = Company::users(...);
        });
    }

    // Returnează în ordinea modelelor primite
    $resultArray = [];
    foreach ($models as $model)
        $resultArray[] = $results[$model->namespace][$model->user_id] ?? null;

    return $resultArray;
}

Vezi mai multe exemple reale în Exemple din practică.

Parametrii #[Computable]

ParametruDescriere
attributeNameNumele proprietății virtuale generate
dataTypeTipul PHP: 'string', 'int', 'bool', 'array', etc.
nullable(implicit false) Dacă valoarea poate fi null
arrayTypeDacă dataType e 'array', tipul elementelor

Încărcare automată și preloading cu with()

Atributele calculate se încarcă automat la accesare — când ceri $user->full_name, ORM-ul execută metoda statică și îți returnează valoarea. Este un lazy-load per instanță.

Pentru performanță în liste, folosește with() — acesta face preloading bulk, executând metoda o singură dată pentru toate modelele:

// Fără with() — se încarcă automat, dar câte o interogare per model
$users = User::all()->get();
foreach ($users as $user) {
    echo $user->full_name; // se încarcă automat la fiecare iterație
}

// Cu with() — o singură execuție bulk, ideal pentru liste
$users = User::all()->with('full_name')->get();
foreach ($users as $user) {
    echo $user->full_name; // deja preîncărcat
}
Când folosești with(): ori de câte ori iterezi o listă de modele și accesezi un atribut computable, sau când pasezi modelul într-un template Twig — ca să eviți încărcări individuale multiple.