Laravel 5.5 - Horizon自定义作业标签,适用于排队的事件侦听器。

3
Horizon文档中提到可以在排队的事件监听器中添加自定义标签。然而,我找不到任何方法来获取包含所需数据的事件实例。给出的示例使用类型提示从服务容器中提取相关模型并将其分配给构造函数中的实例变量,然后在tags()方法中使用该实例变量来获取有关正在操作的特定模型实例的数据。
但是,在排队的事件侦听器中执行此操作时,它不起作用。事实上,由于模型被序列化和“重新激活”时执行,因此似乎根本没有调用构造函数。因此,在构造函数中进行类型提示无效,而tags()似乎在handle()之前调用,因此我无法访问正在侦听的事件对象。
在这种情况下,是否有人知道如何在标记中获取事件信息? 更新: 在控制器中调用的事件:
event(new PostWasCreated($user, $post));

事件 PostWasCreated

<?php
namespace App\Events;

use Illuminate\Broadcasting\Channel;
use Illuminate\Queue\SerializesModels;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use App\User;
use App\Post;

class PostWasCreated
{
    use InteractsWithSockets, SerializesModels;

    public $user;
    public $post;

    public function __construct(User $user, Post $post)
    {
        $this->user = $user;
        $this->post = $post;
    }

    public function broadcastOn()
    {
        return new PrivateChannel('channel-name');
    }
}

监听器PostWasCreatedNotificationSend

<?php
namespace App\Listeners;

use App\Events\PostWasCreated;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;

class PostWasCreatedNotificationSend implements ShouldQueue
{
    protected $event;
    public $queue = 'notifications'; // Adds queue name

    public function __construct(PostWasCreated $event)
    {
      $this->event = $event;
      // Does NOT add queue tag
      $this->queueTags = ['post: ' . $this->event->post->id];
    }

    public function tags()
    {
      return $this->queueTags;
    }

    public function handle(PostWasCreated $event)
    {
      // handle event here...
    }
}

问题在于$this->queueTags从未被赋值,因此在Horizon中没有标签用于这个队列的监听器...(队列名称可以显示,但我们也需要标签)。

如果我没记错的话,实际排队的类是CallQueuedListener,而不是您定义的侦听器(请参见调度程序的参考)。因此,我认为文档要么是误导性的,要么缺少的功能应该被视为一个bug。 - Namoshek
Horizon从CallQueuedListener作业中提取事件以查找任何标签。从这个描述中很难理解正在发生什么。您能否展示您尝试排队的事件的代码?它是否表现出SerializesModels特性? - Cy Rossignol
@CyRossignol 抱歉耽搁了,我已经发布了可重现的更新代码。 - Wonka
1个回答

2

Horizon会在将作业推入队列之前收集任何标记,因此我们不能依赖于作业在执行之前不知道的任何值。在这种情况下,作业知道UserPost,因为我们在初始化事件时将它们传递给它们。

对于排队的监听器,标记系统会检查事件对象和监听器类上的标记。如问题所述,无法在侦听器上使用动态数据设置标记,因为处理程序在Horizon从队列中弹出作业后才执行。我们只能在监听器上声明静态标记,Horizon将与事件上的标记合并*:

class PostWasCreatedNotificationSend implements ShouldQueue 
{
    ...
    public function tags() 
    {
        return [ 'listener:' . static::class, 'category:posts' ];
    }
}

使用事件对象,Horizon 试图自动为任何 Eloquent 模型成员生成标记。例如,Horizon 将为 PostWasCreated 事件创建以下标记:
  • $event->userApp\User:<id>
  • $event->postApp\Post:<id>
我们可以通过定义类似上面的 tags() 方法来覆盖此行为,并告诉 Horizon 为事件设置哪些标记。
class PostWasCreated 
{
    ...
    public function tags() 
    {
        return [ 'post:' . $this->post->id ];
    }
}

请注意,截至撰写本文时,如果事件或侦听器手动提供标签,则Horizon不会自动为模型创建标签。
问题是$this->queueTags从未被分配,因此在Horizon中没有这个排队的侦听器的标签...(队列名称显示出来,但我们也需要标签)。
Horizon不会为每个属性创建标签;自动标记仅适用于包含Eloquent模型的属性(通常不适用于侦听器)。
*如果事件也用于广播(它实现ShouldBroadcast),则用于发布消息的附加作业不会继承侦听器提供的任何标签。

1
谢谢您回复,它按照您所描述的方式工作,当标签设置在事件中并且侦听器排队时。不过奇怪的是,对于通知而言,动态标签可以工作,但是对于事件侦听器本身却不能。我认为它应该适用于通知、作业和事件侦听器,所以仍然有点困惑为什么它只对通知动态地起作用,而对于事件侦听器则不行 :/ - Wonka
@Wonka - 让我们看看我是否理解你的意思... JobEventNotification以及它们上面的任何模型或数据,在进入队列之前都已经存在,当Horizon分配标签时。然而,事件监听器不包含任何数据...它从弹出队列的项目中消耗数据,因此我们不能在监听器的标签中使用任何该数据。我理解你的问题了吗? - Cy Rossignol
我相信是的。我尝试过使用SerializedModels和普通的(非排队)监听器来触发一个处理程序的事件,并在排队的通知中成功使用了动态标签。我更新了问题中监听器的最后一段代码,使其与可工作的动态通知标签相匹配。基本上更新了构造函数并添加了标签方法,但这种设置在监听器上不起作用,只适用于通知(通知使用Queueable)。所以我不确定这是否是一个错误还是我做错了什么... - Wonka
1
@Wonka - 我同意 - 开始时确实有点混淆...我不会称其为bug...更多的是Horizon当前版本中的限制。监听器工作方式与其他类型的任务略有不同,因为队列系统会从容器中解决它们(其他任务则从存储在队列中的序列化数据重新创建),所以我们无法通过构造函数注入模型/数据。我们需要依靠事件标签,将带有动态数据的队列侦听器作业进行标记。 - Cy Rossignol

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接