Constrained Eager Loading in Laravel

• 1 min read

Solving the dreaded n+1-problem in Laravel is relatively easy with eager loading. But did you know, you can apply query conditions in your eager loading statements? You can read more about "constraining eager load" in the Laravel documentation.

I've added this in an application I'm working on and could see signification improvements in the SQL query execution time.

In my case, the app has Categories which in turn have many Posts. The Models look like this.

namespace App;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class Category extends Model
{
    public function posts(): HasMany
    {
        return $this->hasMany(Post::class);
    }

}
namespace App;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;

class Post extends Model
{
    public function scopeIsPublished($query): void
    {
        $query->whereNotNull('published_at');
    }

    public function category(): BelongsTo
    {
        return $this->belongsTo(Category::class);
    }

}

On one page, we list all available categories and show the 5 most recent published posts underneath the category name. As categories in our app have thousands of posts, eager loading the entire posts-relationship doesn't make much sense.

By setting constraints in the eager loading statement, we can fine tune which posts should be eager loaded. In our case only published posts. The Controller method might look like this.

public function index()
{
    $categories = Category::query()

        // Will *only* eager load posts
        // which have been published
        ->with(['posts' => function ($query) {
            return $query->isPublished();
        }])


        ->whereHas('posts', function ($q) {
            return $q->isPublished();
        })
        ->get();

    return view('categories.index', compact('categories'));
}