Laravel 13.27 adds refreshForUpdate(), reloading an existing model instance in-place with fresh database attributes while acquiring an exclusive FOR UPDATE row lock.
When an Eloquent model is loaded before a transaction begins (such as through route model binding or an earlier service method), its memory state can drift from the database before you mutate it.
Previously, refreshing and locking an existing model instance required re-querying the table and reassigning the variable. Laravel 13.27 provides refreshForUpdate() to handle this directly on the existing instance.
Before & After
use App\Models\Product;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\DB;
public function purchase(Product $product): Response
{
DB::transaction(function () use ($product) {
// Before Laravel 13.27: manual query lookup and reassignment
// $product = Product::lockForUpdate()->findOrFail($product->getKey());
// Laravel 13.27+: refreshes attributes in place with a row lock
$product->refreshForUpdate();
if ($product->stock < 1) {
throw new RuntimeException('The product is out of stock.');
}
$product->decrement('stock');
});
return response()->noContent();
}
Key Takeaways
- In-Place Mutation: Refreshes the existing model object and syncs its original attributes without requiring variable reassignment.
- Under the Hood: Uses
$this->newQueryWithoutScopes()->lockForUpdate()targeting the model's primary key. - Transaction Requirement: Must be called inside
DB::transaction()or an active transaction block for the database engine to maintain the row lock. - Common Use Cases: Prevents race conditions during inventory decrements, wallet balances, or seat reservations when models originate from route model binding.
Related Tips
View all tips →Scope Pivot Table Queries with Closures in wherePivot() in Laravel 13.26
Laravel 13.26 allows wherePivot() and orWherePivot() to accept closures, enabling direct reuse of local scopes defined on custom Pivot models.
Detect Eloquent Model Attribute Changes Before and After Saving
Use isDirty() and isClean() before saving to inspect unsaved in-memory mutations, and use wasChanged() and getOriginal() after saving to verify persisted changes and access previous values.