3 Real-World Examples of Laravel Database Transactions

Post a Comment


Database transactions are extremely handy for performing many database tasks, and Laravel supports them.

But what are some concrete examples of WHEN you should use them? In summary, transactions are beneficial for many database activities when you want to ensure that if one of them fails, all of them are instantly rolled back.

In this article, I will present three typical Laravel examples:

  • Creating a new record with many-to-many related records
  • Deleting multiple records for a user
  • Updating summary table after a new record

Let's get practical.


Example 1. Many-to-Many with Transaction.

Take a look at this typical Controller code:

public function store(StoreUserRequest $request) {
    $user = User::create($request->validated());
    $user->roles()->attach($request->input('roles'));

    return redirect()->route('users.index');
}

As you can see, there's a new User record, and then multiple roles are attached to the User. But what if something goes wrong in the second sentence?

Let's imagine that $request->input('roles') is passed not as array but as an invalid string. What happens then?

Laravel Database Transaction Error

And the worst part is not about the error, but the fact that the User record has been actually saved to the database.

In the case of users, it may have a bad consequence of email being already taken, although the registration hasn't actually been finished, as the users.email field is unique on the database level.

That's why it's beneficial to use a Database Transaction here:

use Illuminate\Support\Facades\DB;

// ...

public function store(StoreUserRequest $request) {
    DB::transaction(function() use ($request) {
        $user = User::create($request->validated());
        $user->roles()->attach($request->input('roles'));
    }

    return redirect()->route('users.index');
}

Keep in mind that you must pass utilize ($request) or any other external variable that you will be using within the transaction function.

Don't get me wrong: the error "Incorrect integer value: 'abcd' for column 'role id' at row 1" will still appear. However, the User creation statement will be turned back, and the user will not be in the database.


Example 2. Deleting Multiple Records for User

Assume you wish to delete a record with a large number of hasMany/belongsToMany relationships. Do you also need to delete them? You must manually set the cascadeOnDelete() on the DB level in migrations if you haven't already. Something along the lines of:

$profile->avatar->forceDelete();
MediaTag::whereProfileId($profile->id)->delete();
StatusHashtag::whereProfileId($profile->id)->delete();
DirectMessage::whereFromId($profile->id)->delete();
FollowRequest::whereFollowingId($profile->id)
    ->orWhere('follower_id', $profile->id)
    ->forceDelete();
Follower::whereProfileId($profile->id)
    ->orWhere('following_id', $profile->id)
    ->forceDelete();
Like::whereProfileId($profile->id)->forceDelete();

// ... only then delete the profile itself:
$profile->delete();

Consider what would happen if one of the middle sentences in this code piece failed. So we got rid of something but not everything?

Of course, in comparison to the prior example, the repercussion is less severe because, well, we still want to delete those entries anyhow; we'd simply do it later.

However, the profile would stay live but would not show parts of their data, such as their username without an avatar. Isn't it awful?

Simply add the following lines of code:

DB::transaction(function() use ($profile) {
    $profile->avatar->forceDelete();
    MediaTag::whereProfileId($profile->id)->delete();
    StatusHashtag::whereProfileId($profile->id)->delete();
    DirectMessage::whereFromId($profile->id)->delete();
    FollowRequest::whereFollowingId($profile->id)
        ->orWhere('follower_id', $profile->id)
        ->forceDelete();
    Follower::whereProfileId($profile->id)
        ->orWhere('following_id', $profile->id)
        ->forceDelete();
    Like::whereProfileId($profile->id)->forceDelete();

    $profile->delete();
});

Example 3. Updating "Summary" Table

Imagine a project with users and financial operations. Usually, they would be called "Transactions", but to avoid confusion with the subject of the article, I will call them just "Expenses".

You need to track all Expenses and also the current balance of each user. So, after every purchase, you would do something like this:

Expense::create($expenseDetails);
$user->decrement('balance', $expenseDetails->amount);

Sounds simple enough, but in more complex circumstances, you'd also need to update some more summary data in other tables for reporting purposes.

In this case, the cost of not using DB transactions is enormous: consumers will have more money to spend than they should. Let's put this right:

DB::transaction(function() use ($expenseDetails, $user) {
    Expense::create($expenseDetails);
    $user->decrement('balance', $expenseDetails->amount);
});

So, these are just three simple examples of DB Transactions. I hope they will push you towards making your data correct all the time.

Related Posts

Post a Comment

Subscribe Our Newsletter