This is a question and my own answer (solution I figured out by chance). Laravel documentation does not mention this and it brought me hours of programming suffering.
Let's say we have Posts with Comments and Votes (for comments). Laravel's favorite example. Models and relationships are Textbook (from Laravel's docs). Posts have comments, comments have votes.
So,
$comments_ids = [1,6,7,22];
Post::where('id', $post_id)
->with(['comments' => function($query) use ($comments_ids) {
$query->whereIn('id', $comments_ids);
}])
->with('comments.votes')
->first();
So, I should expect Post with comments which, ids are 1,6,7,22 and votes eager loaded.
But not so fast! I get ALL COMMENTS! ALL OF THEM! ...why?
Here is answer to that question:
Because, we eager load comments then we load votes, the votes forces all comments to load.
This:
$comments_ids = [1,6,7,22];
Post::where('id', $post_id)
->with(['comments' => function($query) use ($comments_ids) {
$query->whereIn('id', $comments_ids);
}])
->with('comments.votes') //this forces Builder to completely ignore whereIn clause above.
->first();
Should be written as following:
$comments_ids = [1,6,7,22];
Post::where('id', $post_id)
->with(['comments' => function($query) use ($comments_ids) {
$query->whereIn('id', $comments_ids)
->with('votes'); //eager load votes with filtered comments
}])
->first();
Then you will get the comments with ids specified in $comments_ids variable. And votes eager loaded with them.
This little nuance has caused much headaches.