DEV Community

loading...
Cover image for How to Force Eager Loading and Prevent N+1 Issues in Laravel

How to Force Eager Loading and Prevent N+1 Issues in Laravel

Ash Allen
I am Laravel web developer that specialises in building websites and systems for small businesses
Originally published at ashallendesign.co.uk ・4 min read

Introduction

In a past article, I talked about the performance improvements that you can achieve by using "eager loading" in your Laravel database queries. For anyone who hasn't read it, click here if you want to give it a quick read.

What is Eager Loading?

When you are fetching any models from the database and then doing any type of processing on the model’s relations, it’s important that you use eager loading. Eager loading is super simple using Laravel and basically prevents you from encountering the N+1 problem with your data. This problem is caused by making N+1 queries to the database, where N is the number of items being fetched from the database. To explain this better and give it some context, let's check out the example below.

Imagine that you have two models (Comment and Author) with a one-to-one relationship between them. Now imagine that you have 100 comments and you want to loop through each one of them and output the author’s name.

Without eager loading, your code might look like this:

$comments = Comment::all();

foreach ($comments as $comment ) {
    print_r($comment->author->name);
}
Enter fullscreen mode Exit fullscreen mode

The code above would result in 101 database queries because it the results are "lazy loaded"! The first query would be to fetch all of the comments. The other one hundred queries would come from getting the author’s name in each iteration of the loop. Obviously, this can cause performance issues and slow down your application. So, how would we improve this?

By using eager loading, we could change the code to say:

$comments = Comment::with(authors)->get();

foreach ($comments as $comment ) {
    print_r($comment->author->name);
}
Enter fullscreen mode Exit fullscreen mode

As you can see, this code looks almost the same and is still readable. By adding the ::with('authors') this will fetch all of the comments and then make another query to fetch the authors at once. So, this means that we will have cut down the query from 101 to 2!

For more information, check out the Laravel documentation on eager loading.

How to Force Laravel to Use Eager Loading

A new feature (added by Mohamed Said) has recently been merged into the Laravel codebase that allows you to prevent lazy loading taking place. This feature is incredibly useful because it should help to ensure that the relationships are eager loaded. As a result of this, it will likely help us to improve performance and reduce the amount of queries that are made to the database as shown in the example above.

It's super simple to prevent the lazy loading. All we need to do is add the following line to the boot() method of our AppServiceProvider:

Model::preventLazyLoading();
Enter fullscreen mode Exit fullscreen mode

So, in our AppServiceProvider, it would look a bit like this:

namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        // ...
        Model::preventLazyLoading();
        // ...
    }
}
Enter fullscreen mode Exit fullscreen mode

Allowing Eager Loading in Production Environments

It's possible that you might only want to enable this feature when in your local development environment. By doing that, it can alert you to places in your code that's using lazy loading while building new features, but not completely crash your production website. For this very reason, the preventLazyLoading() method accepts a boolean as an argument, so we could use the following line:

Model::preventLazyLoading(! app()->isProduction());
Enter fullscreen mode Exit fullscreen mode

So, in our AppServiceProvider, it could look like this:

namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        // ...
        Model::preventLazyLoading(! app()->isProduction());
        // ...
    }
}
Enter fullscreen mode Exit fullscreen mode

By doing this, the feature will be disabled if your APP_ENV is production so that any lazy loading queries that slipped through don't cause exceptions to be thrown on your site.

What Happens If We Try to Lazy Load?

If we have the feature enabled in our service provider and we try to lazy load a relationship on a model, an Illuminate\Database\LazyLoadingViolationException exception will be thrown.

To give this a little bit of context, let's use our Comment and Author model examples from above. Let's say that we have the feature enabled.

The following snippet would throw an exception:

$comments = Comment::all();

foreach ($comments as $comment ) {
    print_r($comment->author->name);
}
Enter fullscreen mode Exit fullscreen mode

However, the following snippet would not throw an exception:

$comments = Comment::with(authors)->get();

foreach ($comments as $comment ) {
    print_r($comment->author->name);
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

In my personal opinion, I think this feature is going to be really useful and it will likely help to encourage better model and database query practices. I have a feeling that this is going to be a feature that I use a daily basis and will likely think "How did I use to survive without this?".

I'm hoping that you found this short post useful though. If it's the type of thing that you're interested in hearing more about, feel free to sign up to my newsletter below. You'll get an notified each time that I post a new article about the Laravel world! 🚀

Discussion (0)