Search code examples
phpactiverecordormlaravellaravel-3

How to list out all items in a nested table in Laravel


I am using Laravel's Eloquent ORM and I'm having trouble eager loading items for display.

Here is the scenario:

  • Users follow Blogs
  • Blogs have Posts

I have a database table named Relationships, this table is used to store the User ID and the Blog ID to show which User is following which Blog. I have a table for Blogs describing the Blog and I have a table for Posts. The Relationships table would be my pivot table to connect the Users with the Blogs tables together. Now, I need to list out all the posts from all the Blogs the User follows in a list.

Here is my User model:

public function following() {
    return $this->has_many_and_belongs_to('Blog', 'relationships', 'user_id', 'blog_id');
}

Here is my Blog model:

public function followers() {
    return $this->has_many_and_belongs_to('User', 'relationships', 'blog_id', 'user_id');
}
public function posts() {
    return $this->has_many('Post');
}

This is how I am trying to retrieve the posts in a list:

$posts = User::with(array('following', 'following.posts'))
            ->find($user->id)
            ->following()
            ->take($count)
            ->get();

This code only lists out the actual Blogs, I need their Posts.

Thank you for your help, please let me know if you need any more details.

SOLUTION:

I slightly modified the accepted answer below, I decided to use the JOIN to reduce the amount of SQL calls to simply 1 call. Here it is:

$posts = Post::join('blogs', 'posts.blog_id', '=', 'blogs.id')
    ->join('relationships', 'blogs.id', '=', 'relationships.blog_id')
    ->select('posts.*')
    ->where('relationships.user_id', '=', $user->id)
    ->order_by('posts.id', 'desc')
    ->take($count)
    ->get();

Solution

  • This is not achievable by native Eloquent methods. But you can use a bit of Fluent methods to join those tables. For instance:

    Edit here: I've added the eager loading to Post query.

    $user = User::find(1);
    $posts = Post::with('blog') // Eager loads the blog this post belongs to
        ->join('blogs', 'blogs.id', '=', 'posts.blog_id')
        ->join('relationships', 'relationships.blog_id', '=', 'blogs.id')
        ->where('relationships.user_id', '=', $user->id)
        ->order_by('posts.id', 'desc') // Latest post first.
        ->limit(10) // Gets last 10 posts
        ->get('posts.*');
    
    foreach ($posts as $post) {
        print($post->title);
    }
    

    If you also need a list of all blogs that such user is following to show on a sidebar, for instance. You can DYI instead of relying on Eloquent, which should be faster and more customizable. For instance:

    $user = User::with('following')->find(1);
    
    // This creates a dictionary for faster performance further ahead
    $dictionary = array();
    foreach ($user->following as $blog) {
        $dictionary[$blog->id] = $blog;
    }
    
    // Retrieves latest 10 posts from these blogs that he follows
    // Obs: Notice the array_keys here
    $posts = Post::where_in('blog_id', array_keys($blog_ids))
        ->order_by('posts.id', 'desc')
        ->limit(10)
        ->get();
    
    // Hydrates all posts with their owning blogs.
    // This avoids loading the blogs twice and has no effect
    // on database records. It's just a helper for views.
    foreach ($posts as $post) {
        $post->relationships['blog'] = $dictionary[$post->blog_id];
    }
    

    On view:

    foreach ($user->following as $blog) {
        print($blog->title);
    }
    
    foreach ($posts as $post) {
        print($post->title . ' @'. $post->blog->title);
    }