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]
| Parametru | Descriere |
|---|---|
attributeName | Numele proprietății virtuale generate |
dataType | Tipul PHP: 'string', 'int', 'bool', 'array', etc. |
nullable | (implicit false) Dacă valoarea poate fi null |
arrayType | Dacă 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.