$ lexprog.com

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

[June 25, 2025] Eloquent ORM

Eloquent Factories and Seeders

Eloquent Factories: Tips & Tricks

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

Eloquent Factories: Tips & Tricks

Tip: Use States for Variants

public function published(): static
{
    return $this->state(fn() => [
        'published' => true,
        'published_at' => now(),
    ]);
}

// Usage:
Post::factory()->published()->create();

Gotcha: Factory Relationships Need for()

Post::factory()->for(Category::factory())->create();

Creates both the category and the post.

Tip: Use has() for Related Models

Post::factory()->hasComments(5)->create();

Creates a post with 5 comments.

Gotcha: count() on create() vs make()

Post::factory()->count(10)->create(); // Saves to DB
Post::factory()->count(10)->make();   // Returns unsaved models

Tip: Use sequence() for Incrementing Values

Post::factory()
    ->count(10)
    ->state(new Sequence(
        ['position' => 1],
        ['position' => 2],
        ['position' => 3],
    ))
    ->create();

Tip: Faker Methods You Might Not Know

fake()->word();           // Single word
fake()->sentence(3);      // 3-word sentence
fake()->imageUrl();       // Placeholder image URL
fake()->boolean(75);      // 75% chance of true

Gotcha: Factory Callbacks

Post::factory()
    ->afterCreating(fn($post) => $post->tags()->attach(Tag::inRandomOrder()->limit(3)->get()))
    ->create();

Tip: Reset Factory State

Post::factory()->state([])->create();

Clears any previously set states.

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

Factories are a testing cornerstone, but they're frequently misused. The number one issue: factories that create deeply nested related records by default, making tests slow and unpredictable. I design factories to create the minimum valid record — no relations unless explicitly requested via states. A test should set up exactly what it needs, nothing more. This keeps test suites fast and makes test setup readable.

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

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