$ lexprog.com

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

[August 20, 2025] Eloquent ORM

Eloquent Accessors and Mutators

Eloquent Accessors & Mutators: Tips & Tricks

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

Eloquent Accessors & Mutators: Tips & Tricks

Tip: Use the New Attribute Syntax

protected function name(): Attribute
{
    return Attribute::make(
        get: fn($value) => ucfirst($value),
        set: fn($value) => strtolower($value),
    );
}

Replaces the old getNameAttribute() / setNameAttribute() methods.

Gotcha: Accessors Run on Every Access

$post->title; // Accessor runs
$post->title; // Accessor runs again (not cached)

For expensive computations, cache the result.

Tip: Computed (Virtual) Attributes

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

No database column needed.

Gotcha: Accessors Break where() Clauses

// Accessor makes name uppercase
$post->name; // "John"

// But this searches the raw DB value
User::where('name', 'John')->get(); // Won't find "john" in DB

Tip: Cast to Custom Classes

protected $casts = [
    'address' => AddressCast::class,
];

Tip: Date Casting

protected $casts = [
    'published_at' => 'datetime',
    'birthday' => 'date',
];

Automatically converts to Carbon instances.

Gotcha: toArray() Includes Accessors

When you call $model->toArray(), accessors are applied. This can be surprising if the accessor transforms data.

Tip: Encrypted Casting

protected $casts = [
    'ssn' => 'encrypted',
    'secrets' => 'encrypted:array',
];

Data is encrypted at rest, decrypted on access.

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

Accessors and mutators are where the line between Eloquent model and business logic blurs dangerously. I've seen accessors that make database queries, call external APIs, and even send emails — all triggered by simply accessing a property. My rule: an accessor should format data, not fetch it. If you need computed data that requires database lookups, use a dedicated method or a repository. Properties should feel like properties, not method calls.

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

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