laraveleventsqueuebroadcast

Laravel Broadcast: Wait for more events before broadcasting


I would like to make sure that events that happen within a short time period (~5 seconds) after the previous event keep delaying the broadcast until no new events are triggered within that period. The desired effect is that only one broadcast is sent AFTER no new events are dispatched within the defined period. Example: 100 new instances are created within 2 seconds. Only after there are no new instances created for another 5 seconds, a Broadcast should be sent.

-> Using laravel-10 with php-8.2 and mariadb-10


I'm using a Model Observer to dispatch an event every time a new instance is created in the Model's boot() method:

    static::created(function ($notification) {
        event(
            new FrontendNotificationEvent(
                $notification->user_id, 'update_notifications'
            )
        );
    }); 

In the FrontendNotificationEvent class, I implemented ShouldBeUnique with uniqueId() to allow only one Event of each eventName/userId combination at a time. The queue is configured to use the 'database' driver.

namespace App\Events;

use Carbon\Carbon;
use Illuminate\Broadcasting\BroadcastEvent;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBeUnique;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class FrontendNotificationEvent implements ShouldBeUnique, ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public function __construct(private readonly string $userId, public string $userEventName, public array $payload = [])
    {
        $this->payload['timestamp'] = Carbon::now();
    }

    public function broadcastOn(): Channel
    {
        return new PrivateChannel(USER_EVENTS_CHANNEL_PREFIX . $this->userId);
    }

    public function broadcastAs(): string
    {
        return $this->userEventName;
    }

    public function uniqueId() {
        return '_' . $this->broadcastAs() . '-' . $this->userId;
    }
}

I have tried some combinations of setting the delay and/or uniqueFor variables in FrontendNotificationEvent:

public int $delay = 5;
public int $uniqueFor = 5;

I could only achieve the FIRST broadcast to be sent and the following suppressed, but I want the first one delayed until no new ones come in within 5 seconds.


Please let me know if any clarifications are needed.


Solution

  • After some more digging and trying, I created a separate Job which is unique and delayed. Since the job is unique, no additional notifications will be queued while it is delayed, having the effect that only one event is triggered within USER_EVENT_NOTIFICATIONS_MAX_EVERY_SECONDS seconds.

        static::created(function ($notification) {
            // [WARNING] The uniqueness does not seem to work with the "array" CACHE_DRIVER!
            UserEventNotificationJob::dispatch(
                $notification->user_id,
                'update_notifications'
            )->delay(now()->addSeconds(USER_EVENT_NOTIFICATIONS_MAX_EVERY_SECONDS));
        });
    
    
    
    class UserEventNotificationJob implements ShouldBeUnique, ShouldQueue
    {
        use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
    
        public function __construct(
            public string $userId,
            public string $userEventName
        ) {
        }
    
        public function handle(): void
        {
            event(new UserEventNotificationBroadcastEvent($this->userId, $this->userEventName, $this->uniqueId()));
        }
    
        public function uniqueId(): string
        {
            return $this->userEventName . '_' . $this->userId;
        }
    }