Search code examples
phplaraveleloquentthreaded-comments

Threaded Comments in Laravel w/Eloquent


I have user profiles that allow users to leave messages for each other. (Think Facebook/MySpace)...

Given a profile_messages table with the following fields id, user_id, author_id, parent_id, and message, how would I efficiently display them in a threaded layout?

Note: The comments will only be 1 level deep.

Currently, I'm fetching all of the relevant comments, then rebuilding the collection to have a $messages with a sub-collection of replies on each item.

$messages = new Collection();
$replySets = [];

foreach ($user->profileMessages as $message)
{
    $parentId = $message->parent_id;

    if ($parentId == 0)
    {
        $messages->put($message->id, $message);
    }
    else
    {
        $replySets[$parentId][] = $message;
    }
}

foreach ($replySets as $parentId => $replies)
{
    $parent = $messages->get($parentId);

    if (isset($parent))
    {
        if ( ! isset($parent->replies))
        {
            $parent->replies = new Collection();
        }

        foreach ($replies as $reply)
        {
            $parent->replies->push($reply);
        }
    }
}

// Pass $messages to the view


This works great. However, I can't help but to think there is a better way of doing this... Is there a better way to do this, or perhaps a way to make use of the relationships to get a result set matching the $profileMessage->replies structure?


Solution

  • From your description I assume you have root messages that have user_id pointing to the user, a message was sent to. And replies to those root messages, that are relevant not to the user_id but to the parent_id.

    So:

    $user->load('messages.replies.author');
    
    foreach ($user->messages as $message)
    {
      $message; // root message
      $message->replies; // collection
      $message->replies->first(); // single reply
      $message->replies->first()->author;
    }
    

    with relations like below:

    // user model
    public function messages()
    {
      return $this->hasMany('Message')->where('parent_id', '=', 0);
      // you could also create RootMessage model with global scope instead of this where
    }
    
    // message model
    public function replies()
    {
      return $this->hasMany('Message', 'parent_id');
      // again, here you can create separate Reply model if you like
    }
    
    public function author()
    {
      return $this->belongsTo('User', 'author_id');
    }
    
    public function user()
    {
      return $this->belongsTo('User');
    }