3

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();
Karl
  • 595
  • 1
  • 10
  • 31

1 Answers1

4

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);
}
vFragosop
  • 5,705
  • 1
  • 29
  • 31
  • What about eager loading a table? Because when I eager load User::with(array('following', 'following.posts')), the array has the posts nested further inside, I just can't get to them. – Karl Apr 30 '13 at 19:49
  • When you eager load like that, Eloquent is querying all subscribed blogs and of its posts from database, which may become thousands soon. If you wish to show posts from blogs that an user has subscribed to, let's say like tumblr's dashboard, the best way would be the one I suggested. Although, You could do an inverse eager loading like `Posts::with('blog')`. I've edited the answer with a few suggestions on how to solve that. – vFragosop Apr 30 '13 at 20:46
  • Thanks Vinicius! I tried both your solutions but preferred the JOIN to avoid making multiple SQL queries. You didn't need to eager load the blog relationship since we were going to JOIN the table. This solution gave me the best performance. I added my modified version of your solution to my answer. Thanks again! Reputation awarded! – Karl May 03 '13 at 18:39