1

I'm not sure if I'm overcomplicating events/listeners/notifications in Laravel, but it feels like maybe I am, and I'm getting some unexpected results, although technically it's working.

My basic structure is this:

  1. MonthlySummaryCompletedEventis fired.
  2. The HandleMonthlySummaryCompletedEvent listener listens for the event.
  3. It triggers a SendMonthlySummaryCreatedNotification notification.

This is all working (locally at least), except for 2 things:

  1. I've had to put in a 5 second sleep in my livewire component that receives the notification because of timing issues. It almost seems like the pusher notification arrives before the notification has been written to the database.
  2. It appears that 2 messages are sent to pusher: enter image description here

Here's the code for my event:

class MonthlySummaryCompletedEvent implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public User $requestedBy;
    public string $fileDownloadUrl;
    public string $fileName;

    /**
     * Create a new event instance.
     *
     * @return void
     */
    public function __construct($event, string  $fileDownloadUrl, string $fileName)
    {
        $this->requestedBy = $event->requestedBy;
        $this->fileDownloadUrl = $fileDownloadUrl;
        $this->fileName = $fileName;
    }

    /**
     * Get the channels the event should broadcast on.
     *
     * @return \Illuminate\Broadcasting\Channel|array
     */
    public function broadcastOn()
    {
        return new PrivateChannel('users.' . $this->requestedBy->id);
    }
}

here's the code for my listener:

class HandleMonthlySummaryCompletedEvent implements ShouldQueue
{
    /**
     * Create the event listener.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * Handle the event.
     *
     * @param  object  $event
     * @return void
     */
    public function handle($event)
    {
        Notification::send($event->requestedBy, new SendMonthlySummaryCreatedNotification($event));
    }
}

and here's the code for the notification:

class SendMonthlySummaryCreatedNotification extends Notification implements ShouldQueue, ShouldBroadcast
{
    use Queueable;

    public $fileDownloadUrl;
    public $fileName;
    /**
     * Create a new notification instance.
     *
     * @return void
     */
    public function __construct($event)
    {
        $this->fileDownloadUrl = $event->fileDownloadUrl;
        $this->fileName = $event->fileName;
    }

    /**
     * Get the notification's delivery channels.
     *
     * @param  mixed  $notifiable
     * @return array
     */
    public function via($notifiable)
    {
        return ['database', 'broadcast'];
    }

    /**
     * Get the broadcastable representation of the notification.
     *
     * @param  mixed  $notifiable
     * @return BroadcastMessage
     */
    public function toBroadcast($notifiable)
    {
        Log::debug($notifiable->toArray());
        return new BroadcastMessage([
            'title' => 'Monthly Summary Complete',
            'message' => "{$this->fileName} is ready. ",
            'link' => $this->fileDownloadUrl,
            'link_text' => 'Click here to download',
            'show_toast' => true,
            'user_id' => $notifiable->id
        ]);
    }

    /**
     * Get the array representation of the notification.
     *
     * @param  mixed  $notifiable
     * @return array
     */
    public function toDatabase($notifiable)
    {
        return [
            'title' => 'Monthly Summary Complete',
            'message' => "{$this->fileName} is ready. ",
            'link' => $this->fileDownloadUrl,
            'link_text' => 'Click here to download',
            'show_toast' => true,
            'user_id' => $notifiable->id
        ];
    }
}

And here's my front end livewire component:

public function getListeners()
    {
        return [
            "echo-private:users.{$this->user->id},StatementCompleted" => 'notifyUser',
            "echo-private:users.{$this->user->id},MonthlySummaryCompletedEvent" => 'notifyUser',
        ];
    }

    public function mount()
    {
        $this->user = Auth::user();
        $this->refreshNotifications();
        $this->showNotificationsBadge = ($this->notificationCount > 0) ? true : false;
    }

    public function render()
    {
        return view('livewire.components.notifications');
    }



    public function notifyUser()
    {
        $this->showNotificationsBadge = true;
        sleep(5);
        $this->refreshNotifications();

        $message = $this->notifications->first()->data['message'];
        if (isset($this->notifications->first()->data['show_toast']) && $this->notifications->first()->data['show_toast'] == true) {
            $this->dispatchBrowserEvent('triggerToast', [Helper::notification('Success', $message)]);
        }
    }
hyphen
  • 2,368
  • 5
  • 28
  • 59
  • 1
    Yes you do seem to be overcomplicating things a bit. Just to note that when you are using `via` the notifications are sent through each channel concurrently so the order is generally unpredictable. You are also broadcasting the `SendMonthlySummaryCreatedNotification` notification and the `MonthlySummaryCompletedEvent` which explains the two notifications being broadcasted. I'm sure you don't need to broadcast both – apokryfos Feb 06 '23 at 11:47
  • @apokryfos - Can you tell me what the right way to handle this is, at a high level? Which class should do the broadcasting in a normal notification scenario? There's nothing really unique about what I'm doing that would necessitate it being complex, I just don't have a firm grasp of how Laravel's event/broadcasting/notifications work, or when each should be used or not used. – hyphen Feb 06 '23 at 11:59
  • 1
    Broadly speaking I don't think you need to broadcast `MonthlySummaryCompletedEvent` at all. You are using a custom `SendMonthlySummaryCreatedNotification` which is sent in response to that event and you can use to control exactly what happens. However I am not sure why you want to broadcast on the database and why it's important to have the data saved before the user receives the notification in the front-end. Perhaps an even better design would be to create an event that listens for the creation of a database model and broadcast that event as a notification only. – apokryfos Feb 06 '23 at 12:03
  • @apokryfos Ok thanks for that! My thinking around the database notification was being able to persist those in the event that a user logged out, so that when they log in next it would show their notifications. I think I'm following what you're saying though. I haven't tried it yet, but I'm assuming I should be able to listen for a database notification to be created since that's really just a model like anything else. This particular function isn't writing anything to the database, it's just going out to external services and generating a document in AWS based on the data received. – hyphen Feb 06 '23 at 12:17
  • @apokryfos So if I'm thinking about it right, the general flow could be: An event is triggered when a database notification (model) is created. A listener listens for that event and triggers a notification which would then broadcast that notification. From a couple of quick google searches, it looks like I can create a Notification model and looks like I can listen for a new model being created, so theoretically this should work. – hyphen Feb 06 '23 at 12:24
  • 1
    Yes, or you can even broadcast the event itself e.g. create a [model event](https://laravel.com/docs/9.x/eloquent#events) and mark it as `ShouldBroadcast`. There's also a [full docs page](https://laravel.com/docs/9.x/broadcasting#model-broadcasting) dedicated to this topic for even simple broadcasting – apokryfos Feb 06 '23 at 12:49

0 Answers0