Search code examples
phplaravellaravel-5laravel-5.5laravel-horizon

Laravel 5.5 - Horizon custom job tags for queued event listener


In the documentation for Horizon, it mentions that custom tags can be added to queued event listeners. However, I can't find any way to pull in my event instance containing the data I need. The example given uses type-hinting to pull the relevant model out of the service container and assigns it to an instance variable in the constructor, then uses that instance variable in the tags() method to get data about the particular model instance being operated on.

When doing this in a queued event listener though, it doesn't work. In fact, the constructor doesn't ever seem to be called at all, due to the model being serialized and 're-hydrated' when it comes to be executed. So type-hinting in the constructor does nothing, and tags() appears to be called before handle(), so I can't get access to the event object I'm listening to.

Does anyone know how I can get event information in a tag in this situation?

Update:

Event called in controller:

event(new PostWasCreated($user, $post));

Event 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');
    }
}

Listener 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...
    }
}

The issue is $this->queueTags never gets assigned, so there are no tags in Horizon for this queued listener... (queue name show up though, but we need tags as well).


Solution

  • Horizon collects any tags before even pushing a job onto a queue, so we can't rely on any values that a job doesn't know before it executes. In this case, the job knows the User and Post because we pass them to initialize the event.

    For queued listeners, the tagging system checks for tags on both the event object and the listener class. As described in the question, there's no way to set tags with dynamic data on the listener because the handler executes after Horizon pops the job off of the queue. We can only declare static tags on a listener that Horizon will merge* with the tags on the event:

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

    With the event object, Horizon attempts to automatically generate tags for any Eloquent model members. For example, Horizon will create the following tags for a PostWasCreated event:

    • $event->userApp\User:<id>
    • $event->postApp\Post:<id>

    We can override this behavior and tell Horizon which tags to set for the event by defining a tags() method like above:

    class PostWasCreated 
    {
        ...
        public function tags() 
        {
            return [ 'post:' . $this->post->id ];
        }
    }
    

    Note that, at the time of writing, Horizon will not automatically create tags for models if the event or the listener provide tags manually.

    The issue is $this->queueTags never gets assigned, so there are no tags in Horizon for this queued listener... (queue name show up though, but we need tags as well).

    Horizon doesn't create tags for every property; the automatic tagging only works for those that contain Eloquent models (and generally not for a listener).


    *If the event is also used for broadcasting (it implements ShouldBroadcast), the additional job created to publish the message does not inherit any tags provided by the listener.