ahloul/rabbitevents-mod

Laravel 连续广播事件。它使用 RabbitMQ 作为传输。

6.0.6 2021-12-19 17:31 UTC

README

Tests Status codecov Total Downloads Latest Version License

此分支来自 nuwber/rabbitevents

事件提供了一种简单的观察者实现,允许您监听当前应用程序和另一个应用程序中发生的各种事件。例如,如果您需要响应来自另一个 API 发布的事件。

不要将此包与 Laravel 的广播混淆。此包是为了实现后端到后端的通信而制作的。

实际上,它是对 Laravel 的 事件队列 的一个改进组合,包括一些改进,例如 中间件

监听器类通常存储在 app/Listeners 文件夹中。您可以使用 Laravel 的 artisan 命令生成它们,如 官方文档 中所述。

所有 RabbitMQ 调用都通过使用 Laravel 队列包 作为示例来完成。因此,为了更好地理解,请首先阅读它们的文档。

目录

  1. 安装
  2. 事件
  3. 监听器
  4. 控制台命令
  5. 日志记录
  6. 测试
  7. 示例
  8. 非标准用法

安装

您可以使用 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

存在辅助函数 publishfire(将在下一版本中弃用)。示例1、3和4说明了如何使用它们。

重试失败的事件

命令 rabbitevents:listen 默认将处理作业的尝试次数设置为 1。这意味着将尝试两次处理您的活动(第一次尝试和一次重试),延迟由 sleep 选项(默认为5秒)指定。 --tries=0 表示 Rabbitevents 将无限期地尝试处理事件。如果由于某种原因事件处理不应重试,则从监听器抛出 \RabbiteventsMod\Events\Exception\FailedException。它将事件作业标记为 failed,而不进行新的处理尝试。

更多示例 这里

监听器

注册监听器

RabbitEventsServiceProviderlisten 属性包含所有事件(键)及其监听器(值)的数组。当然,您可以根据应用程序的需要向此数组添加任意数量的事件。

<?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 &

在这种情况下,您需要记住,您需要组织一些系统,例如 Supervisorpm2,这些系统将控制您的进程。

如果监听器崩溃,管理器将重新运行您的监听器,并将发送到队列的所有消息按照发送的顺序处理。存在已知问题:由于队列是分开的,并且您有影响相同实体的消息,因此无法保证所有操作都将按照预期顺序执行。为了避免此类问题,您可以在负载中发送消息时间作为一部分,并在监听器内部处理它。

命令 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()
    {
        //
    }
}

日志记录

该软件包提供两种方式来查看您的监听器发生了什么。默认情况下,它将 processingprocessedfailed 消息写入 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 的某个部分,您应该知道一些事情

  1. 记住,我们使用 RabbitMQ 作为传输层。在 Supervisorpm2RabbitMQ 文档 中,您可以找到如何使用路由键发布消息的示例。这是一个像上面例子中的 something.happened 一样的事件名称。

  2. RabbitEvents 预期消息体是一个 JSON 编码的数组。数组中的每个元素都将作为单独的变量传递给监听器。例如

[
  {
    "key": "value"  
  },
  "string",
  123 
]

数组有3个元素,因此需要传递3个变量给监听器(数组、字符串和整数)。如果传递的是关联数组,分发器会自动封装这个数组。