Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

samwiser's avatar

DB Transactions and Eloquent Models, don't seem to work...

Environment

  • PHP version: 8.2.20
  • Laravel version: 10.48.2
  • Database and version: 10.11.8-MariaDB-ubu2204
  • DB Engine: InnoDB

Looking at Laravel documentation there are no examples of using Eloquent models inside Transaction blocks. I have seen many unofficial examples stating they work but I am not entirely convinced. The Laravel documentation presents examples using the DB facade.

Example: Eloquent model

DB::transaction(function() {
    $user = User::where('email', '[email protected]')->firstOrFail();
    $user->name = 'Doughboy'; // Username was John Doe.
    $user->save();

    $user->email = null; // Trigger QueryException.
    $user->save();
});

This code throws Illuminate\Database\QueryException SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'email' cannot be null.

Checking the database the name is now. 'Doughboy' I expected it to be to 'John Doe'.

My understanding any exceptions would be caught and then the transaction rolled back.

Example: DB Facade

DB::beginTransaction();
try {
    DB::table('users')
        ->where('email', '=', '[email protected]')
        ->update(['name'=> 'Doughboy']);  // Username was John Doe.
    
    DB::table('users')
        ->where('email', '=', '[email protected]')
        ->update(['email'=> null]); // Trigger QueryException.

    DB::commit();
} catch (\Throwable $e) {
    $this->error($e->getMessage());
    DB::rollBack();
}

When I use the DB facade it rolls back the transaction as expected.

Checking the database the name is still John Doe.

So am I doing something wrong or are DB Transactions and Eloquent Models a no-go?

Thanks

0 likes
4 replies
LaryAI's avatar
Level 58

It looks like you're encountering an issue where the transaction is not rolling back as expected when using Eloquent models. This should work correctly, as Eloquent models are fully compatible with transactions in Laravel. The issue might be related to how the transaction is being handled or an unexpected behavior in your specific environment.

Here's a revised version of your Eloquent model example with some additional debugging and best practices to ensure the transaction rolls back correctly:

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

DB::transaction(function() {
    try {
        $user = User::where('email', '[email protected]')->firstOrFail();
        $user->name = 'Doughboy'; // Username was John Doe.
        $user->save();

        $user->email = null; // Trigger QueryException.
        $user->save();
    } catch (\Throwable $e) {
        // Log the error message for debugging
        Log::error('Transaction failed: ' . $e->getMessage());
        
        // Rethrow the exception to ensure the transaction is rolled back
        throw $e;
    }
});

Key Points:

  1. Exception Handling: Ensure that any exceptions are caught and rethrown within the transaction block. This ensures that the transaction is rolled back properly.
  2. Logging: Adding logging can help you debug why the transaction might not be rolling back as expected.
  3. Database Configuration: Ensure that your database configuration supports transactions. InnoDB should support transactions, but double-check your database settings.

Additional Debugging Steps:

  1. Check Database Logs: Look at your database logs to see if there are any errors or warnings that might indicate why the transaction is not rolling back.
  2. Database Connection: Ensure that the database connection being used supports transactions and is correctly configured in your config/database.php file.
  3. Laravel Version: Ensure that you are using a compatible version of Laravel and PHP. You mentioned using Laravel 10.48.2 and PHP 8.2.20, which should be fine, but always check for any known issues or updates.

Example with DB Facade for Comparison:

To further ensure that the issue is not with the Eloquent model, you can compare it with the DB facade example you provided:

use Illuminate\Support\Facades\DB;

DB::beginTransaction();
try {
    DB::table('users')
        ->where('email', '=', '[email protected]')
        ->update(['name' => 'Doughboy']);  // Username was John Doe.
    
    DB::table('users')
        ->where('email', '=', '[email protected]')
        ->update(['email' => null]); // Trigger QueryException.

    DB::commit();
} catch (\Throwable $e) {
    Log::error('Transaction failed: ' . $e->getMessage());
    DB::rollBack();
}

If the DB facade example works as expected and the Eloquent model example does not, it might indicate an issue specific to how Eloquent is handling the transaction in your environment.

By following these steps and ensuring proper exception handling, you should be able to get transactions working correctly with Eloquent models.

MohamedTammam's avatar

I don't see any issue with your DB::transaction. I think you need to double check the behavior. If it throws the error, that means the transaction caught it and threw it back after rolling back the changes.

What will appear in the logs when you do the following:

DB::transaction(function() {
    $user = User::where('email', '[email protected]')->firstOrFail();
	logger('Name: ' . $user->name);
    $user->name = 'Doughboy';
    // ... 
});
samwiser's avatar

Thanks, I have checked probably 20x times using a console command. So I could repeat the test.

logger('Name: ' . $user->name);

John doe appears in laravel.log but in my database the name is Doughboy

The thing is the following code works as expected when using DB::table

DB::beginTransaction();
try {
    DB::table('users')
        ->where('email', '=', '[email protected]')
        ->update(['name'=> 'Doughboy']);  // Username was John Doe.
    
    DB::table('users')
        ->where('email', '=', '[email protected]')
        ->update(['email'=> null]); // Trigger QueryException.

    DB::commit();
} catch (\Throwable $e) {
    DB::rollBack();
}

Also, this works as expected when using DB::table

DB::transaction(function() {
    DB::table('users')
        ->where('email', '=', '[email protected]')
        ->update(['name'=> 'Doughboy']);

    DB::table('users')
        ->where('email', '=', '[email protected]')
        ->update(['email'=> null]);
});

I am testing against a Podman development environment, maybe I need to test against some other environments

The bot says "If the DB facade example works as expected and the Eloquent model example does not, it might indicate an issue specific to how Eloquent is handling the transaction in your environment"

I may need to dig deeper into the call stack.

Jsanwo64's avatar

try this

DB::transaction(function() {
    try {
        $user = User::where('email', '[email protected]')->firstOrFail();
        $user->name = 'Doughboy'; // Username was John Doe.
        $user->save();

        $user->email = null; // Trigger QueryException.
        $user->save();
    } catch (QueryException $e) {
        DB::rollBack();
        throw $e;
    } catch (\Exception $e) {
        DB::rollBack();
        throw $e;
    }
});

Please or to participate in this conversation.