Database Transactions in Laravel with saveOrFail, updateOrFail, and deleteOrFail

Arie Visser • October 26, 2021

laravel php database

When you want to rollback a set of database operations if one of them fails, you can execute them within a database transaction.

Laravel provides several techniques to do this. One of them, that I hadn't seen before, is by using the saveOrFail, updateOrFail, and deleteOrFail methods.

First, I would like to show briefly what a transaction is.

The transaction method in Laravel

Imagine, you have a user model, that you want to inactivate, and unsubscribe at the same time.

When the queriy is executing, something may fail, and you can end up with inconsistent results in your database.

To prevent this, you can rollback the query automatically if an exception is thrown by using Laravel's transaction method like this:

use Illuminate\Support\Facades\DB;
use App\Models\User;

DB::transaction(function () {
    User::where('id', 1)->update(['active' => false, 'subscribed' => false]);
});

You can add as many statements as you like to the transaction method. They will all be rolled back when one of them fails, before the exception is thrown.

The saveOrFail, updateOrFail, and deleteOrFail methods.

When you only are adding a single statement to a transaction, Laravel provides three shorthand methods that are available on the model, saveOrFail, updateOrFail, and deleteOrFail.

These methods are not to be confused with the findOrFail and firstOrFail methods. The findOrFail and firstOrFail methods are not wrapped in a transaction, but will return a 404 when no model is found.

These methods will wrap the statement in a transaction. Let's look at the definition of saveOrFail:

public function saveOrFail(array $options = [])
{
    return $this->getConnection()->transaction(function () use ($options) {
        return $this->save($options);
    });
}

The save method, is added to a transaction when executed by saveOrFail.

Database statements that are executed by updateOrFail and deleteOrFail will also be added to a transaction.

As a result, the update and delete statementa from the previous example, could be rewritten like this:

use App\Models\User;

User::where('id', 1)->first()->updateOrFail(['active' => false, 'subscribed' => false]);

This can make your code just a bit cleaner. When there is an exception, all database operations will be rolled back first.

When you need more control, it is also possible to begin, rollback and commit transactions manually.

A more in depth discussion of transactions can be found in the Database Transactions in Laravel article, written by Chris Fidao.