Pride Mutumbami
Pride Mutumbami

Reputation: 11

Laravel DB transaction and events

I want that If any of the products is updated we fire an event once after the transaction finish

DB::transaction(function() {
   foreach($products as $product) {
       $product = Product::find($product->id) ;
       $product->price = 2;
       $product->update();
   } 
});

Upvotes: 1

Views: 9858

Answers (2)

Soubhagya Kumar Barik
Soubhagya Kumar Barik

Reputation: 2595

Laravel Eloquent models allowing you to hook into the following moments

retrieved, creating, created, updating, updated, saving, saved, deleting, deleted, restoring, restored, and replicating

The above events are varies based on the laravel version.

Laravel 8.x

If you are using Laravel 8.x, then Laravel took care of eloquent events with the transaction.

You just need to use public $afterCommit = true; in your observer. Then the eloquent event will fire after committing a transaction.

Reference: https://laravel.com/docs/8.x/eloquent#observers-and-database-transactions

Laravel < 8.x

Those who are using the below version, Please have a look at the below code.

You need to create a trait inside app/Traits folder

In app/Traits/TransactionalEloquentEvents.php

<?php

namespace App\Traits;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Events\TransactionCommitted;
use Illuminate\Support\Facades\Log;

    trait TransactionalEloquentEvents
    {
        protected static $eloquentEvents = ['created', 'updated'];
    
        protected static $queuedTransactionalEvents = [];
    
        public static function bootTransactionalEloquentEvents()
        {
            $dispatcher = static::getEventDispatcher();
            if (!$dispatcher) {
                return;
            }
    
            foreach (self::$eloquentEvents as $event) {
                static::registerModelEvent($event, function (Model $model) use ($event) {
                    /*get updated parameters*/
                    $updatedParams = array_diff($model->getOriginal(),$model->getAttributes());
                    if ($model->getConnection()->transactionLevel()) {
                        self::$queuedTransactionalEvents[$event][] = $model;
                    } else {
                        Log::info('Event fired without transaction '.$event. json_encode($updatedParams));
                        /* Do your operation here*/
                    }
                });
            }
    
            $dispatcher->listen(TransactionCommitted::class, function (TransactionCommitted $event) {
                if ($event->connection->transactionLevel() > 0) {
                    return;
                }
                foreach ((self::$queuedTransactionalEvents ?? []) as $eventName => $models) {
                    foreach ($models as $model) {
                        Log::info('Event fired with transaction '.$eventName);
                        /* Do your operation here*/
                    }
                }
                self::$queuedTransactionalEvents = [];
            });
        }
    }

In your model app/Models/Product.php

<?php

namespace App\Models;

use App\Traits\TransactionalEloquentEvents;

class Product extends Model
{
    use TransactionalEloquentEvents;

}

Note: In case of updated eloquent event. You will get the updated parameters through the $updatedParams variable.

The above case handles both transaction and non-transaction model operations.

I am maintaining a log here for each activity. You can do your operation too.

Upvotes: 3

Kevin Bui
Kevin Bui

Reputation: 3035

I have just checked the Laravel source code. I think you can utilize the Illuminate\Database\Events\TransactionCommitted event, which is provided out of the box. You can simply register this event with a listener in the EventServiceProvider:

namespace App\Providers;

class EventServiceProvider extends ServiceProvider
{
    protected $listen = [
        TransactionCommitted::class => [
            WhatYouWantToDo::class,
        ],
    ];
}

Upvotes: -1

Related Questions