This is a common "gotcha" when customizing nested eager loads in Eloquent. The issue is that when you use a closure to constrain a nested relationship (like deliverables.timeEntries), Eloquent will only include related models that match the constraint. If a parent deliverable has no timeEntries in the given $range, the timeEntries relationship will be an empty collection for that deliverable.
Why does this happen?
When you use a constraint on a nested relationship, Eloquent does not filter out the parent models (deliverables), but it will only eager load the related timeEntries that match the constraint. If none match, the relationship will be empty.
Example
Suppose you have:
->with([
'deliverables.timeEntries' => fn ($query) => $query->whereBetween('timeEntries.date', $range)
])
If a deliverable has no timeEntries in $range, then $deliverable->timeEntries will be an empty collection.
What if you want to filter out parent models with no related children?
If you want to only include deliverables that have at least one timeEntry in the range, you need to use whereHas:
->whereHas('deliverables.timeEntries', function ($query) use ($range) {
$query->whereBetween('timeEntries.date', $range);
})
->with([
'deliverables' => function ($q) use ($range) {
$q->whereHas('timeEntries', function ($query) use ($range) {
$query->whereBetween('timeEntries.date', $range);
});
},
'deliverables.timeEntries' => function ($query) use ($range) {
$query->whereBetween('timeEntries.date', $range);
}
])
Summary
- Using a closure with
with()on a nested relationship does not filter out the parent; it only filters the related models. - If you want to filter out parents with no matching children, use
whereHas()in addition to your eager loading.
TL;DR
If you want to only get parents with children in the range, use both whereHas and a constrained with:
->whereHas('deliverables.timeEntries', fn ($q) => $q->whereBetween('timeEntries.date', $range))
->with([
'deliverables.timeEntries' => fn ($q) => $q->whereBetween('timeEntries.date', $range)
])
If you just want to eager load the relationship, but allow it to be empty, your original code is correct.
Let me know if you need a more specific example based on your models!