Laravel gives you two ways to wrap database operations in a transaction.
The first is the closure-based approach.
DB::transaction(function () {
$order = Order::create($orderData);
Payment::create(['order_id' => $order->id, 'amount' => $total]);
});
The second is the manual approach.
DB::beginTransaction();
$order = Order::create($orderData);
Payment::create(['order_id' => $order->id, 'amount' => $total]);
DB::commit();
They look equivalent, but they fail very differently.
With DB::transaction(), if anything inside the closure throws an exception, Laravel catches it, calls rollBack() for you, and re-throws the exception.
Your database is back to a clean state.
The manual approach has no such safety net.
If an exception is thrown between beginTransaction() and commit(), the transaction just stays open.
Laravel doesn't know you intended to roll back.
Now imagine this code lives in a service class, and the calling code catches the exception to show a friendly error page or log the failure. The caller has no idea there's still an open transaction on the connection. Any database writes that happen after that point, like logging the error, recording a failed attempt, or sending a database notification, silently run inside the abandoned transaction.
Then at the end of the request, the database connection closes, and MySQL automatically rolls back any open transaction. Everything is gone. Not just the data from the failed operation, but all the "normal" work that happened after it too.
The closure-based approach avoids all of this. It's not just syntactic sugar. It's a meaningful safety difference.
If you do need the manual approach for some reason, always wrap it in try/catch with an explicit rollBack().
DB::beginTransaction();
try {
$order = Order::create($data);
Payment::create(['order_id' => $order->id, 'amount' => $total]);
DB::commit();
} catch (Throwable $e) {
DB::rollBack();
throw $e;
}
But in most cases, DB::transaction() is the simpler and safer choice.
Here to help,
Joel
P.S. Subtle transaction bugs can hide in a codebase for months. A fresh perspective can catch them before they bite you. Schedule a code review.