merkeleon / laravel-transactional-events
Laravel 事件调度器的事务层
Requires
- illuminate/database: ~5.8.0|6.*|7.*|8.*|9.*|10.*|11.*
- illuminate/events: ~5.8.0|6.*|7.*|8.*|9.*|10.*|11.*
- illuminate/support: ~5.8.0|6.*|7.*|8.*|9.*|10.*|11.*
Requires (Dev)
- orchestra/testbench: ~3.8.0
README
本包为 Laravel 事件调度器引入了一个事务层。其目的是在不更改任何代码的情况下,确保事件发送和数据库事务之间的一致性。此行为也适用于 Eloquent 事件,如 saved
和 created
。
简介
让我们从一个简单的订票示例开始。假设这涉及到数据库更改和支付注册,并且自定义事件 OrderWasProcessed
在数据库中的订单处理完成后立即发送。
DB::transaction(function() { $user = User::find(...); $concert = Concert::find(...); $order = $concert->orderTickets($user, 3); event(new OrderWasProcessed($order)); PaymentService::registerOrder($order); });
上述示例中的事务可能因以下几种原因失败:在 orderTickets
方法或支付服务中发生异常,或者仅仅是死锁。
失败将会回滚事务中进行的数据库更改。然而,对于实际上已经发送并最终执行的事件 OrderWasProcessed
,这并不适用。考虑到这个事件可能会导致发送订单确认邮件,正确管理它变得至关重要。
本包的目的是实际上只在事务提交时发送事件。例如,在上面的示例中,如果事务失败,则不会发送 OrderWasProcessed
事件。
请注意,事务之外发送的事件将绕过事务层,这意味着它们将由默认的事件调度器处理。这也适用于将 $halt
参数设置为 true
的事件。
安装
Laravel
由于 Laravel 5.5+ 的 包自动发现 功能,本包在 Laravel 中的安装是自动的。只需将此包添加到 composer.json
文件中,它将为您的应用程序准备好。
composer require merkeleon/laravel-transactional-events
此包也提供了一个配置文件。运行以下命令以将提供的配置文件 transactional-events.php
复制到您的 config
文件夹。
php artisan vendor:publish --provider="Merkeleon\Events\EventServiceProvider"
Lumen
由于 Lumen 是基于 Laravel 包构建的,因此本包也应该在这个微 Web 框架上顺利运行。运行以下命令以安装此包
composer require merkeleon/laravel-transactional-events
为了配置本包的行为,复制配置文件
cp vendor/merkeleon/laravel-transactional-events/src/config/transactional-events.php config/transactional-events.php
然后,在 bootstrap/app.php
中注册配置和服务提供者
注意: 本包必须在默认的 EventServiceProvider 之后注册,这样您的监听器就不会被覆盖。
// The default EventServiceProvider must be registered. $app->register(App\Providers\EventServiceProvider::class); ... $app->configure('transactional-events'); $app->register(Merkeleon\Events\EventServiceProvider::class);
使用
默认情况下,对于放置在 App\Events
命名空间下的事件启用事务层。
但是,使您的事件表现像事务事件的最简单方法是实现合同 Merkeleon\Events\Contracts\TransactionalEvent
。
注意,实现此合同的事件即使未包含在配置中,也将表现如事务事件。
namespace App\Events; use Illuminate\Queue\SerializesModels; use Illuminate\Foundation\Events\Dispatchable; ... use Merkeleon\Events\Contracts\TransactionalEvent; class TicketsOrdered implements TransactionalEvent { use Dispatchable, InteractsWithSockets, SerializesModels; ... }
由于此包不需要对您的代码进行任何更改,您仍然可以使用 Event
门面并调用 event()
或 broadcast()
辅助函数来发送事件
Event::dispatch(new App\Event\TicketsOrdered) // Using Event facade event(new App\Event\TicketsOrdered) // Using event() helper method broadcast(new App\Event\TicketsOrdered) // Using broadcast() helper method
即使您正在使用队列,它们仍然可以平滑地工作,因为此包不会更改事件调度器的核心行为。它们将在活动事务成功时立即入队。否则,它们将被丢弃。
提醒: 当事件在事务中发送时,它们被认为是事务性的。当事件在事务外发送时,它将绕过事务层。
配置
配置文件中存在以下键
通过更改以下属性来启用或禁用事务行为
'enable' => true
默认情况下,事务行为将应用于App\Events
命名空间中的事件。您可以自由使用模式和命名空间。
'transactional' => ['App\Events']
选择应始终绕过事务层的事件,即应通过默认事件调度器处理。默认情况下,所有*ed
Eloquent事件都被排除。
'excluded' => [ // 'eloquent.*', 'eloquent.booted', 'eloquent.retrieved', 'eloquent.saved', 'eloquent.updated', 'eloquent.created', 'eloquent.deleted', 'eloquent.restored', ],
已知问题
测试中不会调度事务事件。
此问题已在Laravel 5.6.16+中修复(见#23832)。
对于旧版本,它与RefreshDatabase
特性相关联,即当它在每次测试后使用数据库事务重置数据库时。此包依赖于在事务开始/提交/回滚时分发的应用程序事件,而每个事件都在事务中执行,并且在测试结束时回滚,因此分发的应用程序事件实际上从未被调度。为了获得预期行为,请在您的测试中使用Merkeleon\Testing\RefreshDatabase
特性,而不是Laravel提供的原始特性。
许可证
此软件包是开源软件,根据MIT许可证授权。