$ lexprog.com

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

[April 15, 2026] Eloquent ORM

Eloquent First Or: Find or Create Patterns

Eloquent First Or: Find or Create Patterns

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

Eloquent First Or: Find or Create Patterns

Tip: firstOrCreate()

$user = User::firstOrCreate(
    ['email' => 'john@example.com'],
    ['name' => 'John']
);

Finds by email, creates with name if not found.

Gotcha: firstOrNew() Doesn't Save

$user = User::firstOrNew(['email' => 'john@example.com']);
$user->name = 'John';
$user->save(); // Must call save() manually

Tip: firstOrFail() vs first()

User::where('email', 'john@example.com')->firstOrFail();
// Throws ModelNotFoundException if not found

Gotcha: Race Conditions with firstOrCreate()

Two simultaneous requests might both try to create. Use database unique constraints as a safety net.

Tip: firstOr() for Custom Fallback

$post = Post::where('slug', $slug)->firstOr(function () {
    return new Post(['title' => 'Default']);
});

Gotcha: firstOrCreate() Fires Events

creating and created events fire when a new record is created. firstOrNew() doesn't fire events until save().

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 *Or* family of methods is elegant but treacherous for concurrent access. firstOrCreate() runs a SELECT followed by an INSERT, and between those two calls another process can insert the same row, causing a duplicate key exception. I always pair these methods with a database-level unique constraint as a safety net. For high-concurrency operations, I prefer explicit transaction handling with lock checks.

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

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