Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

kendrick's avatar

Broadcasting from Partner to User

Having two systems, Partner.php and User.php, I am trying to broadcast an Event, which is triggered by a Partner (post method), and then will be displayed on a User side for information and update reasons through a simple component.

So when a User makes a booking (Booking.php) at Partner, the Partner confirms it, which then notifies a User through the Component.

Controller

public function confirm(Booking $booking, Request $request)
    {
        $partner = Auth::user(); 
        $user = $booking->user; 
    
        broadcast(new ConfirmBookingEvent($booking, $user));

        return response($user->id, 200);
    } 

Event

public function broadcastOn()
    {
        return new PrivateChannel('Booking.' . $this->user['id']);
    }

How could I get a start on the component? Any recommendations?

0 likes
28 replies
JohnBraun's avatar

I recently wrote an article on WebSockets and took some stuff from my blog and adjusted it to your use case:

In your routes/channels.php:

Broadcast::channel('Booking.{userId}', function ($user, $userId) {
  return $user->id === $id; // or any other validation rule you want
});

Pass the Booking with user to the view from the controller.

In your view:

<html>
  <head>
    <title>Booking's page</title>
    <!-- always include your CSRF token -->
    <meta name="csrf-token" content="{{ csrf_token() }}">
  </head>

  <body>
    <div id="app">
      <!-- our Vue component -->
      <booking :booking="{{ $booking }}" />
    </div>

    <!-- include our compiled javascript -->
    <script src="{{ asset('js/app.js') }}"></script>
  </body>
</html>

In your Vue component:

<template>
    <div>
        Your booking with ID {{ booking.id }} is currently:
        <span v-if="processing">
           Available
        </span>
        <span v-else>
           Booked!
        </span>
    </div>
</template>

<script>
    export default {
        props: ['booking'],

        data() {
            return {
                processing: true,
            }
        },

        created() {
            window.Echo.private(`Booking.${this.booking.user.id}`)
                .listen("ConfirmBookingEvent", (e) => {
                    this.processing = false;
            });
        },
    }
</script>
1 like
kendrick's avatar

Wow, thank you, John. Will now get into it.

JohnBraun's avatar

You're welcome, let me know if you got everything working.

1 like
kendrick's avatar

What if $booking is not available everywhere, because the component is not included e.g. on the booking view, where $booking would be available, but on the home view? As a User can have many bookings.

I logically get undefined variable $booking within my User's view.

JohnBraun's avatar

I need to know more about what you're trying to achieve to be able to answer your question adequately, but you could pass the user (with a booking relationship eager loaded) to the Vue component instead of the booking itself.

But, it depends on what you actually want to do.

1 like
kendrick's avatar

Sorry, John, you are right.

If a Partner confirms a booking for a User, it triggers a status_id to e.g. 2 on the booking which has a user_id and partner_id.

So basically, what ConfirmBookingEvent should do, is show something like a popup for once at either the booking view itself or home view of the user. More like a notification.

It requires a more specific layer to the Booking record, I guess. Because currently following your really helpful answer, the booking component will be shown statically, not ephemeral.

I guess it would require something like a read_at record, that the notification will not be displayed statically, right?

Also, we would need to specify the booking, only shown at a specific status_id, because currently the component will be shown at every $booking, if included at the booking view.

At the home view, I couldn't make it work, yet.

Suggestion for channel:


Broadcast::channel('Booking.{booking}', function ($user, $booking) {
  return $user->id === $id; 
});

JohnBraun's avatar

I hope I correctly understand your goal.

It sounds like you want to work with the authenticated user object, so pass that to your component in the view.

View

<booking-confirmation :user="{{ auth()->user() }}" />

Component

On your homepage you want to listen for incoming booking events that are associated with the currently logged in user.

By default, we won't show the modal but whenever a new broadcast event comes in we'll show details of the booking and hide the modal after 3 seconds or something like that (with javascript's setTimeout()).

<template>
    <div v-if="showConfirmation">
           Confirmation of booking with ID {{ booking ? booking.id : 'N/A' }}
    </div>
</template>

<script>
    export default {
        props: ['user'],

        data() {
            return {
                showConfirmation: false,
                booking: null,
            }
        },

        created() {
            window.Echo.private(`Booking.${this.user.id}`)
                .listen("ConfirmBookingEvent", (e) => {
                    this.booking = e.booking; 
                    this.showConfirmation = true; 

                   // reset modal after 3 seconds
                    setTimeout(() => {
                       this.showConfirmation = false;
                       this.booking = null;
                   }, 3000);

            });
        },
    }
</script>
1 like
kendrick's avatar

Yes, that is perfect. Thanks John.

Currently it won't show anything, after the event was triggered within pusher, how could I get behind the problem? Followed your instructions.

JohnBraun's avatar

I haven't tested the code and adjusted it on the fly, so I can't guarantee the code should work as I've provided. It might need some debugging on your side. To help with that, I need some more info.

I noticed that I had a typo in my code: this.bookingId = e.booking; should become this.booking = e.booking; (already updated in the abovementioned code).

Furthermore:

  • Maybe change the v-if to v-show.

  • Do you have any errors in the console?

  • Do you have a public $booking property in your ConfirmBookingEvent class? Otherwise, we can't get it off the event e in the listener of course.

In your ConfirmBookingEvent class:

class ConfirmBookingEvent extends ... 
{

  public $booking;

  public function __construct(Booking $booking)
  {
    $this->booking = $booking;
  }
}
kendrick's avatar

Thanks, John. No errors in console.

Yes $booking is included.

Controller

broadcast(new ConfirmBookingEvent($booking, $user));

Event

    public $booking;
    public $user;

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

    /**
     * Get the channels the event should broadcast on.
     *
     * @return \Illuminate\Broadcasting\Channel|array
     */
    public function broadcastOn()
    {
        return new PrivateChannel('Booking.' . $this->user['id']);
    }
JohnBraun's avatar

Have you adjusted this.bookingId = e.booking; to this.booking = e.booking; as well?

  • If so, I would remove the v-if / v-show and see if the event is captured at all by the listener.

  • What do you see if you console.log(e.booking) in the .listen() method after broadcasting the event?

created() {
window.Echo.private(`Booking.${this.user.id}`)
  .listen("ConfirmBookingEvent", (e) => {
    console.log(e.booking);
  });
}
1 like
kendrick's avatar

v-if and v-show are not working.

If I remove them, the information is shown, but static again.

Thanks for your patience, John

console.log(e.booking)won't be triggered. Nothing is pushed to the console.

If I add {{booking.id}} to the component it throws TypeError: null is not an object (evaluating '_vm.booking.id') (makes sense, as it should only be displayed if available, that is why N/A is show currently)

JohnBraun's avatar

console.log(e.booking)won't be triggered. Nothing is pushed to the console.

So, the event is broadcasted but the listener doesn't act upon it. Can you share the contents of the routes/channels.php file?

kendrick's avatar

Yes, could be the problem


Broadcast::channel('Booking.{userId}', function ($user, $userId) {
  return $user->id === $id; 
});

By the way, this is my BroadcastServiceProvider:

public function boot()
    {
        // Broadcast::routes();

        Broadcast::routes(['middleware' => ['web', 'auth:partner']]);

        require base_path('routes/channels.php');
    }

Maybe because I didn't add the middleware to the channel?

JohnBraun's avatar

Your variables don't match up, adjust it to:

Broadcast::channel('Booking.{userId}', function ($user, $userId) {
  return $user->id === $userId; 
});
1 like
kendrick's avatar

So sorry, John. Still won't console log it, also the normal created logic won't trigger the certificate ID to the component.

JohnBraun's avatar

What do you mean exactly?

You could - by the way - also maybe better use a mounted() callback instead of created()

1 like
kendrick's avatar

Referred to this as the normal/default created logic

created() {
            window.Echo.private(`Booking.${this.user.id}`)
                .listen("ConfirmBookingEvent", (e) => {
                    this.booking = e.booking; 
                    this.showConfirmation = true; 

                   // reset modal after 3 seconds
                    setTimeout(() => {
                       this.showConfirmation = false;
                       this.booking = null;
                   }, 3000);

            });
        },

Also tried a mounted() callback, no difference.

Why doesn't it even console.log the booking? Weird, right?

JohnBraun's avatar

It might be something very trivial that I can't know of as I am not working with your codebase.

You could try to debug your application using Laravel Telescope, which can give you more insight in the broadcasted events.

Other than that, I'm afraid this is all the help I could give you, since you're not getting any specific errors.

kendrick's avatar

Ok, I will further investigate and keep you posted.

Thank you so much for your help and patience, John.

kendrick's avatar

When using Telescope, I get socket: null, on the Broadcast Event. Is this a problem?

EventData

booking: "App\Models\Booking:1",
user: "App\Models\User:1",
socket: null
JohnBraun's avatar

Your socket should not be null. You mentioned before that you are using Pusher.

Did you recompile your assets with npm run dev (or npm run prod) already after updating your Pusher credentials in the .env file?

kendrick's avatar

Thank you for coming back.

Yes, always running npm run dev after changing something within the component.

JohnBraun's avatar

Do you see the event coming in over at Pusher? Could you provide some log details?

kendrick's avatar

Yes, the Event is coming in over Pusher. It includes the correct Booking, as well as User Model.

{
  "booking": {...},
  "user": {...},
}

But no logs, on both sides. When I confirm it as a Partner, no logs, as well as waiting for an update at the User side, no logs.

I don't know, why nothing is being logged after using :

created() {
window.Echo.private(`Booking.${this.user.id}`)
  .listen("ConfirmBookingEvent", (e) => {
    console.log(e.booking);
  });
}
JohnBraun's avatar

Hey @splendidkeen did you make any progress on the problem?

Just to be sure... did you uncomment the BroadcastServiceProvider in the config/app.php file?

        /*
         * Application Service Providers...
         */
        App\Providers\AppServiceProvider::class,
        App\Providers\AuthServiceProvider::class,
        // App\Providers\BroadcastServiceProvider::class,
        App\Providers\EventServiceProvider::class,
        App\Providers\RouteServiceProvider::class,

    ],
JohnBraun's avatar
Level 33

I've setup a working demo project, which you can download from this repo: https://github.com/Jhnbrn90/laracasts-broadcasting-demo

While setting up this demo, I noticed we are comparing an integer to a string in channels.php, which you can solve by casting the passed in id to an integer.

Broadcast::channel('Booking.{userId}', function ($user, $userId) {
  return $user->id === (int) $userId; 
});

Demo app

The demo app has two main routes /home and /send/{userId}.

Create an account and leave one browser tab open on /home. The user you're currently logged in as, probably has the ID of 1. If you open a new tab and visit /send/1 you'll receive a notification on the /home tab.

1 like
kendrick's avatar

Wow, thank you so much for creating a demo repository, John. And sorry for the late reply.

Yes, I uncommented the BroadcastServiceProvider. I guess the problem lies within the architecture.

I am using two Models, Partner.php and User.php, both using an own guard. That is why I added this line to the BroadcastServiceProvider:

 Broadcast::routes(['middleware' => ['web', 'auth:partner']]);


// auth.php

'providers' => [
        'minds' => [
            'driver' => 'eloquent',
            'model' => App\Models\User::class,
        ], // guards: web 

        'doors' => [
            'driver' => 'eloquent',
            'model' => App\Models\Partner::class,
        ], // guards: partner 

    ],

Within channels.php I then specify the guard on the channel e.g.:

Broadcast::channel('Requests', function ($partner) {
    return $partner;
}, ['guard' => ['partner']]);

Then in our case as a logged in Partner I confirm the booking, which triggers:

broadcast(new ConfirmBookingEvent($booking, $user));

Through the component, as a logged-in User (web), I try to grab the Event, but that is where it is failing somehow without even getting a broadcasting/auth or something similar. No error, no response.

What do you think? Thanks so much, John

Please or to participate in this conversation.