$ lexprog.com

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

[September 18, 2024] Eloquent ORM

Eloquent Increment/Decrement: Atomic Operations

Eloquent Increment/Decrement: Atomic Operations

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

Eloquent Increment/Decrement: Atomic Operations

Tip: increment() for Counters

$post->increment('views');

Single atomic query: UPDATE posts SET views = views + 1 WHERE id = ?.

Gotcha: increment() Doesn't Fire Events

No updating or updated events fire. The model instance in memory is also not updated.

Tip: Increment by Custom Amount

$post->increment('views', 5);

Adds 5 instead of 1.

Gotcha: Model Instance Not Updated

$post->increment('views');
echo $post->views; // Still shows old value
$post->refresh();  // Reload from DB

Tip: decrement() Works the Same Way

$product->decrement('stock', $quantity);

Atomic decrement — safe for concurrent operations.

Gotcha: increment() with Additional Columns

$post->increment('views', 1, ['last_viewed_at' => now()]);

Updates additional columns in the same query.

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 increment() and decrement() methods are atomic, but they bypass model events and validation. I've seen counters go negative because decrement() was called without a check, and cache values become inconsistent because the model events that invalidated the cache were skipped. Use these methods only for simple counters, and always implement database-level constraints (CHECK counter >= 0) as a safety net.

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

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