Catching N+1 query issues with `preventLazyLoading()` in Laravel

Laravel devs, here's a gem for you: πŸ’Ž Use preventLazyLoading() to catch N+1 query issues during development. Improve your app's performance by addressing these queries early! In this blog post, we'll explore how to use preventLazyLoading() and provide a real-life example to demonstrate its benefits.

Why Use preventLazyLoading()?

  • Detect N+1 Issues: Easily catch N+1 query issues during development.
  • Improve Performance: Addressing these issues early can significantly improve your application's performance.
  • Better Debugging: Get clear exceptions when lazy loading is detected, making it easier to debug and optimize your queries.

Step-by-Step Implementation

Let's walk through the process of setting up and using preventLazyLoading() in a Laravel application.

Step 1: Setting Up the Model

Ensure you have models with relationships. In this example, we'll use User and Post models.

// app/Models/User.php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

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

// app/Models/Post.php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    public function user()
    {
        return $this->belongsTo(User::class);
    }
}

Step 2: Enabling preventLazyLoading()

Enable lazy loading prevention in your AppServiceProvider or any other service provider.

// app/Providers/AppServiceProvider.php

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function boot()
    {
        Model::preventLazyLoading();
    }
}

Step 3: Creating the Controller Method

Create a controller method that retrieves users and their posts.

// app/Http/Controllers/UserController.php

namespace App\Http\Controllers;

use App\Models\User;
use Illuminate\Http\Request;

class UserController extends Controller
{
    public function index()
    {
        $users = User::all();

        foreach ($users as $user) {
            // This will throw an exception if 'posts' relationship is not eager loaded
            $posts = $user->posts;
        }

        return view('users.index', ['users' => $users]);
    }
}

Step 4: Setting Up the Route

Define a route that points to the controller method.

// routes/web.php

use App\Http\Controllers\UserController;

Route::get('/users', [UserController::class, 'index']);

Step 5: Creating the View

Create a view to display the users and their posts.

<!-- resources/views/users/index.blade.php -->

<!DOCTYPE html>
<html>
<head>
    <title>Users and Posts</title>
</head>
<body>
    <h1>Users and Their Posts</h1>
    <ul>
        @foreach ($users as $user)
            <li>
                {{ $user->name }} ({{ $user->email }})
                <ul>
                    @foreach ($user->posts as $post)
                        <li>{{ $post->title }}</li>
                    @endforeach
                </ul>
            </li>
        @endforeach
    </ul>
</body>
</html>

Real-Life Example: Detecting N+1 Issues

In a real-life scenario, you might retrieve users and their posts without eager loading the posts. This can lead to N+1 query issues, which can significantly slow down your application. By enabling preventLazyLoading(), you can catch these issues early.

Creating Dummy Data

Let's generate some dummy data to work with.

php artisan tinker

// Inside Tinker
User::factory()->count(10)->create()->each(function ($user) {
    $user->posts()->saveMany(Post::factory()->count(5)->make());
});

This command will generate dummy users, each with several posts.

Viewing the Users and Posts

Access the /users route in your browser to see the list of users and their posts.

Conclusion

Using preventLazyLoading() in Laravel is a powerful way to detect and address N+1 query issues during development. By following the steps outlined in this blog post, you can ensure your application performs optimally by catching and fixing these issues early.

Found this helpful?

If this guide was helpful to you, subscribe to my daily newsletter and give me a follow on X/Twitter. It helps a lot!

Subscribe to Harris Raftopoulos

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe