$ lexprog.com

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

[February 09, 2025] Eloquent ORM

Eloquent Model States: Workflow

Eloquent Model States: Workflow

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

Eloquent Model States: Workflow

Tip: State Column

$table->string('status')->default('draft');

Gotcha: State Transitions

public function publish(): void
{
    if ($this->status !== 'draft') {
        throw new Exception('Only drafts can be published');
    }
    $this->update(['status' => 'published']);
}

Tip: State Scope

public function scopeDraft(Builder $query): Builder
{
    return $query->where('status', 'draft');
}

Gotcha: State Validation

protected static function booted(): void
{
    static::updating(function ($post) {
        $validTransitions = ['draft' => ['published', 'archived']];
        // Validate transition
    });
}

Tip: State Enum (PHP 8.1+)

enum PostStatus: string {
    case Draft = 'draft';
    case Published = 'published';
    case Archived = 'archived';
}

Gotcha: State and Events

Fire events on state transitions for side effects.

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

Model states are an elegant way to manage workflows, but I've seen them used where a simple status enum would suffice. State machines add complexity — transition rules, guard clauses, after-hooks — that's justified only when the workflow has genuinely complex rules (like order processing with cancellations, refunds, and chargebacks). For simple status values (draft/published/archived), use enums and avoid the overhead.

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

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