merkeleon/laravel-transactional-events

Laravel 事件调度器的事务层

v2.1.2 2024-04-08 09:02 UTC

This package is auto-updated.

Last update: 2024-09-04 18:57:37 UTC


README

本包为 Laravel 事件调度器引入了一个事务层。其目的是在不更改任何代码的情况下,确保事件发送和数据库事务之间的一致性。此行为也适用于 Eloquent 事件,如 savedcreated

简介

让我们从一个简单的订票示例开始。假设这涉及到数据库更改和支付注册,并且自定义事件 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许可证授权。