$ lexprog.com

// notes from an old coder -- php, databases, and the occasional rant

[March 16, 2026] Eloquent ORM

Eloquent Appending: Virtual Attributes

Eloquent Appending: Virtual Attributes

────────────────────────────────────────────────────────

Eloquent Appending: Virtual Attributes

Tip: $appends for Serialized Attributes

protected $appends = ['full_name'];

protected function fullName(): Attribute
{
    return Attribute::make(
        get: fn() => "{$this->first_name} {$this->last_name}"
    );
}

full_name appears in toArray() and JSON.

Gotcha: $appends Runs on Every Serialization

Even if you don't need the attribute, it's computed. This can be expensive for complex logic.

Tip: Conditional Appending

public function toArray(): array
{
    $array = parent::toArray();
    if ($this->include_details) {
        $array['full_name'] = $this->full_name;
    }
    return $array;
}

Gotcha: makeHidden() Removes Attributes

$user->makeHidden(['email', 'password']);

Temporarily hides attributes from serialization.

Tip: makeVisible() for Hidden Attributes

$user->makeVisible(['created_at']);

Shows attributes that were hidden by the model.

Gotcha: Appends Don't Work with select()

If you select() specific columns, appended attributes still compute but selected columns may be missing data the accessor needs.

Tip: Use cursor() for Memory-Neutral Iteration

When exporting 100K rows, get() loads everything into memory. cursor() uses yield and keeps memory flat regardless of row count. Perfect for artisan commands.

Tip: whereHas() vs load() — Two Different Things

whereHas() filters the parent query by relationship existence. load() eager-loads relationships AFTER the query. Mixing them up is a common source of logic bugs.

Gotcha: withCount() Adds a Subquery

withCount('comments') runs a correlated subquery on every row. On large tables, this can be slower than a separate query. Profile before relying on it.

Senior Insight

The appends property on Eloquent models is convenient but dangerous — every serialized response includes those attributes, even when they're not needed. I've seen API responses balloon from 20KB to 200KB because a model appended an expensive computed attribute that triggered multiple database queries. My rule: never put accessor-based attributes in appends. Instead, load them explicitly in API resources with when() or merge() when the response actually needs them.

Source: Laravel Docs (https://laravel.com/docs/eloquent), Laravel News (https://laravel-news.com/), Freek.dev (https://freek.dev/tags/eloquent)

────────────────────────────────────────────────────────
<-- back to posts