Framework: Eager-loaded data not sent to the client by event

Created on 29 Jan 2017  路  7Comments  路  Source: laravel/framework

  • Laravel Version: 5.4
  • PHP Version: 7.0.12
  • Database Driver & Version: MySQL 5.6.33

Description:

When a model that eager-loads other models is passed to an event that implements ShouldBroadcast and that is received by Laravel Echo, the eager-loaded models are not received by the client.

Here's is an example with a Post model that belongs to a User.

Controller method where the post is created:

$post = Post::create([
    'user_id' => 1,
    'topic_id' => request('topic_id'),
    'body' => request('body'),
]);

$post = Post::with('user')->find($post->id);

 event(new PostCreated($post));

Here is the event class:

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

    public $post;

    /**
     * Create a new event instance.
     *
     * @return void
     */
    public function __construct($post)
    {
        $this->post = $post;
        echo $post->user->name; // This works, name is echo'd
    }

    /**
     * Get the channels the event should broadcast on.
     *
     * @return Channel|array
     */
    public function broadcastOn()
    {
        return new Channel('topics');
    }
}

And finally the JS code :

Echo.channel('topics')
    .listen('PostCreated', (e) => {
        console.log(e.post); // Can see the post object, but not the user property
        console.log(e.post.user); // Undefined
    });

Why is post.user undefined in the JS code, while $post->user is not in the event class?

Do I need to do something special to ensure that the eager-loaded models are passed along when the event is broadcasted?

I'm using Pusher, if that matters.

Steps To Reproduce:

  • Create an event that implements ShouldDispatch, and accept an Eloquent model that eager-loads another model in the constructor
  • Emit that event
  • Intercept the event using Echo
  • Try to access the eager-loaded properties in the JS code, the result is undefined

Most helpful comment

When using eloquent models in broadcasting events, it looks like eager loaded relationship data isn't accounted for. They're available server side in your listeners but not client side.
My workaround is as follows. I'm sure there are better solutions, but it works just fine for me:

// TaskController.php

public function test()
{
    $task = Task::with('user')->first();
    event(new TaskUpdated($task->toJson()));
}
// TaskUpdated.php
public $task;

public function __construct($task)
{
    $this->task = $task;
}

public function broadcastOn()
{
    return ['tasks'];
}



md5-7e1639a5d2bef800c339bec186df9617



// TaskComponent.vue
Echo.channel('tasks')
        .listen('TaskUpdated', (e) => {
        console.log( JSON.parse(e.task) );
    });

So I just convert to JSON then parse on the client and all data including relationship data is available.
Again, there must be a more elegant solution, but this is how I've always handled it.

All 7 comments

When using eloquent models in broadcasting events, it looks like eager loaded relationship data isn't accounted for. They're available server side in your listeners but not client side.
My workaround is as follows. I'm sure there are better solutions, but it works just fine for me:

// TaskController.php

public function test()
{
    $task = Task::with('user')->first();
    event(new TaskUpdated($task->toJson()));
}
// TaskUpdated.php
public $task;

public function __construct($task)
{
    $this->task = $task;
}

public function broadcastOn()
{
    return ['tasks'];
}



md5-7e1639a5d2bef800c339bec186df9617



// TaskComponent.vue
Echo.channel('tasks')
        .listen('TaskUpdated', (e) => {
        console.log( JSON.parse(e.task) );
    });

So I just convert to JSON then parse on the client and all data including relationship data is available.
Again, there must be a more elegant solution, but this is how I've always handled it.

Thank you @devcircus , it works very well.

I hope that in the future this can be fixed so that it isn't necessary, but for now that will do the trick.

This also happens to me.

Same here, but!

If I use something like this in the channel name, the article 'eager loaded' so I can use it in JS:

public function broadcastOn()
{
    return new Channel('comment.'.$this->comment->article->slug);
}

Now the console.log(comment.article) is working.

But I'd like to eager load the comment->creator as well, but I can't. So I use an other public variable:

public function __construct(Comment $comment)
{
    $this->comment = $comment;
    $this->creator = $comment->creator;
}

The creator in my comment model looks like this:

public function creator()
{
    return $this->belongsTo('App\User', 'user_id')
               ->select(['id', 'name']);
}

But the Event gives back the whole User model, so I can see all the fields not just the id and name.

This could be fixed if we send the data through broadcastWith()

/**
  * Get the data to broadcast.
  *
  * @return array
  */
 public function broadcastWith()
 {
     return [
         'user' => $this->user->load('userable'),
     ];  
 }

Thanks it works.
Note:
In this case the broadcast returns only this array, so the public variable not included. But it's totally fine.

@themsaid I think this should be documented. I mean maybe some note in the docs if the relation not loaded on the public variable or something.

Was this page helpful?
0 / 5 - 0 ratings