This will be extremely useful if your event depend on a database record or newly added data to database. If your code has several database transactions and you dispatch an event in the middle of the code, it will run before the database transaction completed causing may issues. Do not let your events to fire for failed database transactions or with incomplete data.
Why we need to run events after database transaction?
Imagine a situation of sending activation email to newly registered users. You are using event listener feature of Laravel to send emails. What happen user registration failed and is rolled back. You have already send the activation email but user is not registered on the database. What happen if the event listener is depend on the newly added data on the database. Event listener may use old data for processing if it run before completing the database transaction.
How to run Events after successful database transaction
You can use ShouldDispatchAfterCommit interface on the event class (not on event listener class) to ensure event is run after the current database transaction is committed. By using ShouldDispatchAfterCommit on event class eliminate the need of moving event dispatch method to the end of the logic or code block (ex: after database transaction completed).
First we will create a sample event class and event listener and dispatch that event in the middle of a database transaction and see what will happen.
Step 1: Create a sample event class
php artisan make:event ItemShipped
Step 2: Create a sample event listener
php artisan make:event NotifyShippingCompany
Step 3: Bind created event listener class to the above defined event (ItemShipped).
All you have to do is type hint the event class in the handle method. Then log information to Laravel logger when the event listener is executed. So that we can find when this event listener is executed.
<?php
namespace App\Listeners;
use App\Events\ItemShipped;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Facades\Log;
class NotifyShippingCompany
{
/**
* Create the event listener.
*/
public function __construct()
{
//
}
/**
* Handle the event.
*/
public function handle(ItemShipped $event): void
{
Log::info("Event Listener running..");
}
}
You can create a sample database table to add test data to perform database transaction. Here I have already created item table on the database using Laravel Migrations and created Item model. I am not going to include how to create a model and migration in this article since it is not relevant.
Step 4: Dispatch created event
We are not going to create any controller classes to test this example. For simplicity I am going to dispatch this event in web.php file with a simple database transaction. open web.php file in the routes directory.
<?php
use App\Events\ItemShipped;
use App\Models\Item;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
Log::info("DB Transaction starting..");
DB::beginTransaction();
$item = new Item();
$item->name = "New item";
$item->save();
ItemShipped::dispatch($item);
Log::info("DB Transaction closing..");
DB::commit();
});
step 5: check firing order in the Laravel log file.
First run above example. Then go to storage directory -> logs directory and open laravel.log file
[2024-11-25 14:30:15] local.INFO: DB Transaction starting..
[2024-11-25 14:30:15] local.INFO: Event Listener running..
[2024-11-25 14:30:15] local.INFO: DB Transaction closing..
Event listener had fired in the middle of the transaction. If event listener class uses any database information it will be outdated because data is not committed to database yet. Other problem is that this event listener may run even for failed database transactions.
With using ShouldDispatchAfterCommit
Consider above example of firing an event that uses ShouldDispatchAfterCommit in the middle of a database transaction.
open ItemShipped event class and add ShouldDispatchAfterCommit.
<?php
namespace App\Events;
use App\Models\Item;
use Illuminate\Contracts\Events\ShouldDispatchAfterCommit;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class ItemShipped implements ShouldDispatchAfterCommit
{
use Dispatchable, InteractsWithSockets, SerializesModels;
/**
* Create a new event instance.
*/
public function __construct(public Item $item)
{
//
}
}
Now run this Laravel application and check log file.
[2024-11-25 14:30:07] local.INFO: DB Transaction starting..
[2024-11-25 14:30:07] local.INFO: DB Transaction closing..
[2024-11-25 14:30:07] local.INFO: Event Listener running..
Without changing the order of the code execution the web.php file you will be able to fire the event listener after completing the database transaction.