mvanduijker/laravel-transactional-model-events
Installation:
composer require mvanduijker/laravel-transactional-model-events
No publisher or config required—just add the trait.
First Use Case:
Add TransactionalAwareEvents to a model (or base model):
use Mvanduijker\LaravelTransactionalModelEvents\TransactionalAwareEvents;
class User extends Model
{
use TransactionalAwareEvents;
}
Listen for Events:
Register listeners in EventServiceProvider:
protected $listen = [
'afterCommit.created:App\Models\User' => [
'App\Listeners\HandleUserCreation',
],
'afterRollback.saved:App\Models\User' => [
'App\Listeners\HandleUserUpdateRollback',
],
];
Trigger Events:
Use DB::transaction() as usual—events fire after commit/rollback:
DB::transaction(function () {
$user = User::create([...]); // Triggers `afterCommit.created`
$user->update([...]); // Triggers `afterCommit.saved`
});
Event Granularity:
afterCommit.created: Fired when a model is created in a transaction.afterCommit.saved: Fired when a model is updated in a transaction.afterRollback.created/afterRollback.saved: Fired if the transaction rolls back.afterCommit.deleted: For soft/deletes (if using SoftDeletes trait).Base Model Integration: Extend a base model to apply transactional events globally:
class BaseModel extends Model
{
use TransactionalAwareEvents;
}
All child models inherit event support.
Dynamic Event Handling: Use wildcards in listeners for reusable logic:
'afterCommit.*.App\Models\User' => [UserEventHandler::class],
Transaction Scoping:
DB::transaction(fn => DB::transaction(...))) work as expected.Queue Integration: Dispatch queued jobs in listeners (events fire post-transaction):
public function handle(AfterCommitCreated $event) {
SendWelcomeEmail::dispatch($event->model);
}
Testing:
Use DB::fake() + DB::transaction() to test rollback scenarios:
DB::transaction(function () {
User::create([...]);
$this->assertEventFired(AfterRollbackCreated::class);
throw new \Exception('Simulate rollback');
});
Performance: Avoid heavy logic in listeners—events fire after the transaction completes.
Model Events Conflict:
Regular Eloquent events (creating, saved) still fire before transactional events. Use transactional events for post-commit logic (e.g., notifications, analytics).
Event Timing:
afterRollback.* events fire—no afterCommit.*.Model State:
update(), delete()) happen within the transaction.Soft Deletes:
SoftDeletes trait for afterCommit.deleted to work.forceDelete()) do not trigger this event.Database Connections:
config/database.php).Event Payload:
$event->model is a detached copy (not managed by the transaction).
⚠️ Avoid modifying it—use fresh() if you need the latest state.Verify Events: Check if events fire with:
Event::listen('afterCommit.*', function ($event) {
logger()->debug('Transactional event fired:', ['model' => $event->model]);
});
Transaction Boundaries:
Ensure all model operations are within DB::transaction(). Events won’t fire for:
// ❌ No event (outside transaction)
User::create([...]);
DB::transaction(function () {
// ✅ Events fire here
User::create([...]);
});
Rollback Testing:
Simulate rollbacks with DB::rollBack() in tests or by throwing exceptions.
Custom Events: Extend the trait to add domain-specific events:
use Mvanduijker\LaravelTransactionalModelEvents\TransactionalAwareEvents;
class User extends Model
{
use TransactionalAwareEvents;
protected static function bootTransactionalEvents()
{
static::afterCommit(function ($model) {
if ($model->isAdmin()) {
event(new AdminCreated($model));
}
});
}
}
Conditional Listeners: Dynamically register listeners based on model attributes:
if ($user->isActive()) {
event(new UserActivated($user));
}
Global Configuration: Override default behavior via service provider:
public function boot()
{
TransactionalAwareEvents::setEventPrefix('custom.');
}
Queue Failures:
Handle listener failures gracefully (e.g., retry with ShouldQueue):
public function handle(AfterCommitCreated $event)
{
try {
SendEmail::dispatch($event->model);
} catch (\Throwable $e) {
report($e);
}
}
How can I help you explore Laravel packages today?