ahmadbadpey's avatar

first() and take() do not work correctly in Eager Load laravel

I have a Post Model that have a hasMany relation to post_pics and belongsToMany relation to Category Model.

I want to fetch All Posts and All their Categories but First post_pics of each Post.

For that I write this Eager Load Constraints :

Post::with([
                'post_pics'  => function ($query) {
                    $query->select(['pic_id', 'pic_name', 'post_id'])->first()->get();
                },
                'categories' => function ($query) {
                    $query->select(['categories.cat_id', 'name']);
                }
            ])
                ->take(12)->orderBy('created_at', 'desc')
                ->get(['post_id', 'post_title', 'post_alias', 'post_content', 'comments_count', 'created_at']);

        return $latestPosts;

When I remove first() method after select() method all things works and returns all pictures of each Post but when I use first() method, only returns first picture of Models that have more than one picture.

I try to take(1) Constraints but it does not work too.

What is Problem and How Can I do that?

0 likes
7 replies
pmall's avatar
pmall
Best Answer
Level 56

You must not use first or get in a eager loading constraint. It is just made for adding clauses to the relationship.

If you want to eager load the first pic of each post you should create a new relationship in post model :

public function first_post_pic ()
{
    return $this->hasOne(PostPicture::class)->orderBy('id', 'asc'); // Order it by anything you want
}

Then you can eager load it :

$posts = Post::with('first_post_pic', 'categories')->take(12)->latest() // latest == created_at desc

Also I see you select only some columns. Be careful you have to select all the keys of the relationships in order to make eager loading work.

If you do this for formatting the output you should use something like the fractal package instead of filtering the columns you select.

5 likes
ahmadbadpey's avatar

Thnaks All.

I added This method to Post Model :

public function latestPicture ()
        {
            return $this->hasOne('App\PostPics')->orderBy('pic_id','asc');
        }

And this is my Controller:

class HomeController extends Controller
    {
        public function index ()
        {

            $latestPosts =
                Post::with([
                    'latestPicture',
                    'categories' => function ($query) {
                        $query->select(['categories.cat_id', 'name']);
                    }
                ])
                    ->take(12)->orderBy('created_at', 'desc')
                    ->get(['post_id', 'post_title', 'post_alias', 'post_content', 'comments_count', 'created_at']);
//          dd($latestPosts) ;
            return view('main.pages.home', ['latestPosts' => $latestPosts]);
        }
    }

For use latestPosts in My View I write this:

@if (!$latestPosts->isEmpty())
        @foreach($latestPosts as $post)
            @if( key($latestPosts) <3)
                <!-- Some HTML -->
            @endif
        @endforeach
    @endif

but I faced this Error :

Trying to get property of non-object (View: D:\wamp\www\TC\resources\views\main\pages\home.blade.php)

What is Problem? How Can I access to that Collection in the View ?

1 like
vguerrerobosch's avatar

I was trying to solve this very same problem of eager loading relationships when only the last related model was actually needed. Although the solution provided by @pmall actually works and solves de N+1 problem, it cause a possible memory issue since the actual query running behind loads all the records of the relationship, not only the last one, even though these are not hydrated to Eloquent model afterwards. All this is perfectly explained in this article by Jonathan Reinink and an optimal solution is provided.

https://reinink.ca/articles/dynamic-relationships-in-laravel-using-subqueries#can-this-be-done-with-a-has-one

ajsheldon93's avatar

Just a note, this issue was fixed in Laravel 11. You can now do this:

$posts = Post::with([
		'post_pics' => function($query) {
				$query->take(1);
		}
])->get();

This will now give you all of your posts with a single post_pic on each post.

Please or to participate in this conversation.