ahloul / rabbitevents-mod
Laravel 连续广播事件。它使用 RabbitMQ 作为传输。
Requires
- php: ^7.3|^8.0
- ext-bcmath: *
- ext-json: *
- ext-pcntl: *
- enqueue/amqp-lib: ^0.10
- illuminate/events: ^7.0 | ^8.0
- illuminate/queue: ^7.0 | ^8.0
- illuminate/support: ^7.0 | ^8.0
- phpoption/phpoption: ^1.8
Requires (Dev)
- mockery/mockery: ^1.4
- phpunit/phpunit: ^9.0
This package is auto-updated.
Last update: 2024-09-20 00:27:18 UTC
README
事件提供了一种简单的观察者实现,允许您监听当前应用程序和另一个应用程序中发生的各种事件。例如,如果您需要响应来自另一个 API 发布的事件。
不要将此包与 Laravel 的广播混淆。此包是为了实现后端到后端的通信而制作的。
实际上,它是对 Laravel 的 事件 和 队列 的一个改进组合,包括一些改进,例如 中间件。
监听器类通常存储在 app/Listeners
文件夹中。您可以使用 Laravel 的 artisan 命令生成它们,如 官方文档 中所述。
所有 RabbitMQ 调用都通过使用 Laravel 队列包 作为示例来完成。因此,为了更好地理解,请首先阅读它们的文档。
目录
- 安装
- 事件
- 监听器
- 控制台命令
- rabbitevents:install - 安装包资源
- rabbitevents:listen - 监听事件
- rabbitevents:list - 显示已注册的事件列表
- rabbitevents:make:observer - 创建 Eloquent 模型事件观察者
- 日志记录
- 测试
- 示例
- 非标准用法
安装
您可以使用 Composer 将 RabbitEvents 安装到您的 Laravel 项目中
$ composer require Ecavalier/rabbitevents
安装 RabbitEvents 后,使用 rabbitevents:install
Artisan 命令发布其配置和服务提供者
$ php artisan rabbitevents:install
配置
发布资源后,其主要配置文件将位于 config/rabbitevents.php
。此配置文件允许您配置您的连接和日志记录选项。
它与队列连接非常相似,但现在您将不会混淆是否还有另一个 RabbitMQ 连接。
<?php return [ 'default' => env('RABBITEVENTS_CONNECTION', 'rabbitmq'), 'connections' => [ 'rabbitmq' => [ 'driver' => 'rabbitmq', 'exchange' => env('RABBITEVENTS_EXCHANGE', 'events'), 'host' => env('RABBITEVENTS_HOST', 'localhost'), 'port' => env('RABBITEVENTS_PORT', 5672), 'user' => env('RABBITEVENTS_USER', 'guest'), 'pass' => env('RABBITEVENTS_PASSWORD', 'guest'), 'vhost' => env('RABBITEVENTS_VHOST', 'events'), 'logging' => [ 'enabled' => env('RABBITEVENTS_LOG_ENABLED', false), 'level' => env('RABBITEVENTS_LOG_LEVEL', 'info'), ], ], ], ];
事件
以下是如何发布事件和负载的示例
<?php $model = new SomeModel(['name' => 'Jonh Doe', 'email' => 'email@example.com']); $someArray = ['key' => 'item']; $someString = 'Hello!'; // Example 1. Old way to publish your data. Will be deprecated it next versions. // Remember: You MUST pass array of arguments fire('something.happened', [$model->toArray(), $someArray, $someString]); // Example 2. Method `publish` from `Publishable` trait SomeEvent::publish($model, $someArray, $someString); $someEvent = new SomeEvent($model, $someArray, $someString); // Example 3. Use helper `publish` publish($someEvent); // Example 4. You could use helper `publish` as you used to use helper `fire` publish('something.happened', [$model->toArray(), $someArray, $someString]); publish($someEvent->publishEventKey(), $someEvent->toPublish());
定义事件
如果您想使事件类可发布,则应实现接口 ShouldPublish
。您可以在 这里 找到此类类的示例。如果您尝试发布未实现的事件,将抛出异常 InvalidArgumentException('Event must be a string or implement "ShouldPublish" interface')
。
如果您想向事件类(例如2)添加方法 publish
,可以使用特质 Publishable
。
存在辅助函数 publish
和 fire
(将在下一版本中弃用)。示例1、3和4说明了如何使用它们。
重试失败的事件
命令 rabbitevents:listen 默认将处理作业的尝试次数设置为 1
。这意味着将尝试两次处理您的活动(第一次尝试和一次重试),延迟由 sleep
选项(默认为5秒)指定。 --tries=0
表示 Rabbitevents 将无限期地尝试处理事件。如果由于某种原因事件处理不应重试,则从监听器抛出 \RabbiteventsMod\Events\Exception\FailedException
。它将事件作业标记为 failed
,而不进行新的处理尝试。
更多示例 这里
监听器
注册监听器
RabbitEventsServiceProvider
的 listen
属性包含所有事件(键)及其监听器(值)的数组。当然,您可以根据应用程序的需要向此数组添加任意数量的事件。
<?php /** * The event listener mappings for the application. * * @var array */ protected $listen = [ 'item.created' => [ 'App\Listeners\SendItemCreatedNotification', 'App\Listeners\ChangeUserRole', ], ];
通配符事件监听器
您甚至可以使用 * 作为通配符参数来注册监听器,允许您在同一个监听器上捕获多个事件。通配符监听器接收事件名称作为它们的第一个参数,以及整个事件数据数组作为它们的第二个参数
<?php /** * The event listener mappings for the application. * * @var array */ protected $listen = [ 'item.*' => [ 'App\Listeners\ItemLogger', ], ];
定义监听器
事件监听器在其 handle
方法中接收事件数据(通常是数组)。在 handle
方法内部,您可以执行必要的任何操作来响应该事件
<?php namespace App\Listeners; class ItemLogger { /** * Handle the event. * * @param array $payload * @return void */ public function handle(array $payload) { log(...); } }
通配符事件的监听器
对于通配符事件的监听器的 handle
方法有一些不同。它接收触发事件的名称作为第一个参数,有效载荷作为第二个参数
<?php namespace App\Listeners; class ItemLogger { /** * Handle the event. * * @param string $event * @param array $payload * @return void */ public function handle(string $event, array $payload) { if ($event === 'item.created') { // do something special } log(...); } }
中间件
监听器中间件允许您在监听器作业的执行过程中包裹自定义逻辑,从而减少作业本身的样板代码。例如,我们有一个事件 'charge.succeeded',它可以在多个API中处理,但前提是此付款是其实体的付款。
<?php /** * Hadle payment but only if a type is 'mytype' * * @param array $payload * @return void */ public function handle($payload) { if (\Arr::get($payload, 'entity.type') !== 'mytype') { return; } Entity::find(\Arr::get($payload, 'entity.id'))->activate(); }
如果您只有一个监听器,那么没问题。如果有多个监听器,它们必须在每个 handle
方法的开始处执行相同的检查,会发生什么?代码将变得有点嘈杂。
而不是在每个监听器的开始处编写相同的检查,我们可以定义处理实体类型的监听器中间件。
<?php namespace App\Listeners\RabbitEvents\Middleware; class FilterEntities { /** * @param string $event Event Name. Passing only fpr wildcard events * @param array $payload */ public function handle([$event,] $payload) { return !\Arr::get($payload, 'entity.type') == 'mytype'; } }
它不按路由中间件的方式工作。我还没有找到一种优雅的方法来只传递简单的事件的 $payload
,以及通配符事件的 $event
和 $payload
。
如果我们需要停止传播,只需返回 false
。
创建监听器中间件后,它们可以通过从监听器的 middleware
方法返回它们或作为 $middleware
属性的数组项来附加到监听器上。
<?php use App\Listeners\RabbitEvents\Middleware\FilterEntities; class PaymentListener { public array $middleware = [ FilterEntities::class, `App\Listeners\RabbitEvents\Middleware\AnotherMiddleware@someAction`, ]; /** * @param string $event Event Name. Passing only for wildcard events * @param array $payload */ public function middleware([$event, ]$payload) { return !\Arr::get($payload, 'entity.type') == 'mytype'; } }
这只是说明您如何将中间件传递给监听器。您可以选择您喜欢的任何方式。
停止事件传播
有时,您可能希望停止将事件传播到其他监听器。您可以通过从监听器的 handle
方法返回 false
来这样做,就像在Laravel的监听器中一样。
控制台命令
命令 rabbitevents:install
如果您不想手动创建配置文件并注册服务提供者,可以运行 rabbitevents:install
命令,它将自动完成所有这些操作。
$ php artisan rabbitevents:install
命令 rabbitevents:listen
这是用于在 RabbitMQ 中注册事件的命令。
$ php artisan rabbitevents:listen event.name --memory=512 --timeout=60 --tries=3 --sleep=5
运行此命令后,事件将作为与事件绑定的独立队列注册到 RabbitMQ 中。
要从控制台分离命令,您可以这样做:
$ php artisan rabbitevents:listen event.name > /dev/null &
在这种情况下,您需要记住,您需要组织一些系统,例如 Supervisor 或 pm2,这些系统将控制您的进程。
如果监听器崩溃,管理器将重新运行您的监听器,并将发送到队列的所有消息按照发送的顺序处理。存在已知问题:由于队列是分开的,并且您有影响相同实体的消息,因此无法保证所有操作都将按照预期顺序执行。为了避免此类问题,您可以在负载中发送消息时间作为一部分,并在监听器内部处理它。
命令 rabbitevents:list
要获取所有注册事件的列表,有这个命令:
$ php artisan rabbitevents:list
命令 rabbitevents:make:observer
有时您可能希望在每个模型的更改中发布一个事件。观察者类具有反映您希望监听的事件的 Eloquent 事件的方法名称。每个方法都接收模型作为其唯一参数。与 Laravel 命令的不同之处在于,rabbitevents:make:observer
在每个方法中创建一个具有占位 fire
函数调用的观察者类。
$ php artisan rabbitevents:make:observer UserObserver --model=User $ php artisan rabbitevents:make:observer "App\\Modules\\User\\RabbitMqObservers\\UserRObserver" -m "Modules\\User\\Models\\User"
此命令将新观察者放置在您的 App/Observers
目录中。如果此目录不存在,Artisan 将为您创建它。您的新观察者将如下所示:
<?php namespace App\Observers; use App\User; class UserObserver { /** * Handle the user "created" event. * * @param \App\User $user * @return void */ public function created(User $user) { publish('User.created', $user->toArray()); } /** * Handle the user "updated" event. * * @param \App\User $user * @return void */ public function updated(User $user) { publish('User.updated', $user->toArray()); } /** * Handle the user "deleted" event. * * @param \App\User $user * @return void */ public function deleted(User $user) { publish('User.deleted', $user->toArray()); } /** * Handle the user "restored" event. * * @param \App\User $user * @return void */ public function restored(User $user) { publish('User.restored', $user->toArray()); } /** * Handle the user "force deleted" event. * * @param \App\User $user * @return void */ public function forceDeleted(User $user) { publish('User.forceDeleted', $user->toArray()); } }
要注册观察者,请使用您想要观察的模型上的 observe 方法。您可以在您的服务提供者的 boot 方法中注册观察者。在这个例子中,我们将观察者注册在 AppServiceProvider 中。
<?php namespace App\Providers; use App\User; use App\Observers\UserObserver; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider { /** * Bootstrap any application services. * * @return void */ public function boot() { User::observe(UserObserver::class); } /** * Register the service provider. * * @return void */ public function register() { // } }
日志记录
该软件包提供两种方式来查看您的监听器发生了什么。默认情况下,它将 processing
、processed
和 failed
消息写入 php 输出。消息包括服务、事件和监听器名称。如果您想关闭此功能,只需使用 --quiet
选项运行监听器。
该软件包还支持您的应用程序日志记录器。要使用它,将配置值 rabbitevents.connection.rabbitmq.logging.enabled
设置为 true
并选择日志级别。
测试
我们始终编写测试。我们的应用程序中的测试包含许多模拟和伪造来测试事件如何发布。我们已经使实现 ShouldPublish
并使用 Publishable
特性的 Event 类的过程变得更加容易。
只需使用 PublishableEventTesting
特性,该特性在您想要测试的类中提供了断言方法。
Event.php
<?php namespace App\BroadcastEvents; use RabbiteventsMod\Events\Event\Publishable; use RabbiteventsMod\Events\Event\ShouldPublish; use RabbiteventsMod\Events\Event\Testing\PublishableEventTesting; class Event implements ShouldPublish { use Publishable; use PublishableEventTesting; public function __construct(private array $payload) { } public function publishEventKey(): string { return 'something.happened'; } public function toPublish(): array { return $this->payload; } }
Test.php
<?php use \App\BroadcastEvents\Event; use \App\BroadcastEvents\AnotherEvent; Event::fake(); $payload = [ 'key1' => 'value1', 'key2' => 'value2', ]; Event::publish($payload); Event::assertPublished('something.happened', $payload); AnotherEvent::assertNotPublished();
如果不通过断言,将抛出 Mockery\Exception\InvalidCountException
。不要忘记在 tearDown
或测试的类似方法中调用 \Mockery::close()
。
非标准使用
如果您只使用 RabbitEvents 的某个部分,您应该知道一些事情
-
记住,我们使用 RabbitMQ 作为传输层。在 Supervisor 或 pm2 的 RabbitMQ 文档 中,您可以找到如何使用路由键发布消息的示例。这是一个像上面例子中的
something.happened
一样的事件名称。 -
RabbitEvents 预期消息体是一个 JSON 编码的数组。数组中的每个元素都将作为单独的变量传递给监听器。例如
[ { "key": "value" }, "string", 123 ]
数组有3个元素,因此需要传递3个变量给监听器(数组、字符串和整数)。如果传递的是关联数组,分发器会自动封装这个数组。