Laravel transactions: Why and how to use them with real-life use cases

Published on
Read time
5 min read
Authors
  • Name
    Dumitru Caldare
    Written by
    Dumitru Caldare
transactions

Introduction

In the dynamic world of web development, maintaining data integrity is crucial and Laravel transaction is one of the tool that ensure it. But what are Laravel transactions? Why are they indispensable? How you can use their power? Buckle up, as we embark on a journey through the realm of transactions

What Are Laravel Transactions?

Transactions, in the context of Laravel, are sequences of database operations bundled together. They're like a protective cocoon around your data changing queries (insert, update, delete), making sure that all or none of them go through. Transactions, in fact, are a core concept from SQL and the database itself and laravel only uses abstraction on top of it, to provide a simplified interface.

Why Do I Need to Use Them?

Well, imagine this: You're purchasing an item from an online store, and the system fails after you changed the stock of this product in your system but before you subtract the amount from the user balance. Nightmare, right? Transactions save you from such data inconsistency horrors.

How To Use Them?

Let's dive into implementation of transactions in Laravel, note that the example is oversimplified to be easier to understand:

// Letting know the database that next queries will be executed together
DB::beginTransaction();

//Handle Exceptions
try {
    // Perform multiple database operations, including updates, inserts, and deletions.
    $product->update(['stock' => $product->stock - 1]);

    $user->update(['balance' => $user->balance - $price]);

} catch(Throwble $e) {
    // In case of an exception we rollback all the queries
    DB::rollback();
    throw $e;
}

// If everything goes right, we save the changes to database
DB::commit();

In the example above, I demonstrated the detailed and comprehensive approach to use transactions in Laravel. This was done to ensure a clear understanding of each step involved in the process. However, there is a simpler method to achieve the same functionality using DB::transaction function, which offers a more straightforward way to accomplish the task.

DB::transaction(function () use ($product, $user, $price) {
    $product->decrement('stock');
    $user->decrement('balance', $price);
});
Note: The decrement function is a convinient way to update a numeric field, internally it's using an update statement

Here, you can truly appreciate the elegance of Laravel. It's the same code, but much easier to understand what it's doing. Once you fully understand concept of transactions, I recommend adopting this syntax. It's beneficial when custom error checking isn't required. It's worth mentioning that under the hood, this transaction approach uses the same syntax as in the first example.

Real Life Examples

1. Registering a new company with default users for a workplace app:

Imagine you have an app where users can make a company. You want to make a special user automatically, like an admin, who can fully control that company. This way, when the company is made, the user is made too. But if making the user has a problem, the company-making also goes back. This stops having a company without a user in your system.

DB::transaction(function () {
    // Creating a new company
    $company = Company::create(['name' => 'New Company']);

    // Creating a default user for the company
    $user = $company->users()->create(['name' => 'Default User']);
});

2. Database tests with transactions to reset database data:

When you're testing and using a real database, you can use transactions to undo all the changes you make during the test. This ensures that each test starts with a fresh database and only the data you set for that test is there when the test runs.

use Illuminate\Foundation\Testing\RefreshDatabase;

class YourTest extends TestCase
{
    use RefreshDatabase;

    public function testSomething()
    {
        // Test code within the transaction
        DB::transaction(function () {
            // Your test logic here
        });
    }
}
Note: Laravel has the trait DatabaseTransactions that you can use, and it will take care that all the changes to a database will be rolled back after the test runs.

3. Many-to-Many relationship with transaction:

Another situation when you want to use transations is dealing with a many-to-many relationship. You want to make sure that the data you added—like tags, for example are properly linked with the post in your system.

DB::transaction(function () {
    $post = Post::create(['title' => 'Sample Post']);
    $tag1 = Tag::create(['name' => 'Tag1']);
    $tag2 = Tag::create(['name' => 'Tag2']);

    // Attaching tags to the post
    $post->tags()->attach([$tag1->id, $tag2->id]);
});

4. Safely deleting users info with transactions:

Here, we're removing a user and their related stuff. If any step fails, like not deleting comments or posts right, everything reverses. This stops having incomplete or mixed-up user info, and avoids showing posts without an author on a page.

DB::transaction(function () {
    $user = User::find(1);

    if ($user) {
        // Delete user info manually
        $user->comments()->delete();
        $user->posts()->delete();
        // ... Delete other related data

        // Delete the user
        $user->delete();
    }
});
Note: This can be achieved with cascade on delete, but sometimes you will prefer to do this manually.

5. E-commerce example with custom error cheking:

In this example we have precise control over data changes and handle errors gracefully. If validation errors occur, we rollback the changes and return to the form with errors. For other unexpected errors, we also rollback and re-throw the error. Finally, if everything goes well, we commit the changes manually.

DB::beginTransaction();

try {
    $product->decrement('stock');
    $user->decrement('balance', $price);

} catch(ValidationException $e) {
    DB::rollback();
    return Redirect::to('/form')
        ->withErrors($e->getErrors())
        ->withInput();

} catch(Throwable $e) {
    DB::rollback();
    throw $e;
}

DB::commit();

Conclusion

In the world of web development, Laravel transactions have a vital role in keeping data reliable. They bundle important database actions, making sure that changes either happen properly or not at all. This helps avoid mix-ups when things go wrong. While we've covered the main ideas of transactions, we're leaving out related topics like dealing with race conditions using database transactions and how to use lock mechanisms with transactions, this topic will be explored in a future blog post.

Tags

web developmentlaraveleloquenttransactions
© KodeKrush 2023