How to Force Eager Loading and Prevent N+1 Issues in Laravel
Tags: laravel, php, webdev
Published: 2021-05-22 14:47:30

## 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](https://ashallendesign.co.uk/blog/6-quick-and-easy-ways-to-speed-up-your-laravel-website).

## 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 <a href="https://laravel.com/docs/8.x/eloquent-relationships#eager-loading" target="_blank" rel="noopener noreferrer">eager loading</a>. 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 (<code>Comment</code> and <code>Author</code>) 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:

```php
$comments = Comment::all();

foreach ($comments as $comment ) {
    print_r($comment->author->name);
}
```

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:

```php
$comments = Comment::with(‘authors’)->get();

foreach ($comments as $comment ) {
    print_r($comment->author->name);
}
```

As you can see, this code looks almost the same and is still readable. By adding the <code>::with('authors')</code> 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 <a href="https://laravel.com/docs/8.x/eloquent-relationships#eager-loading" target="_blank" rel="noopener noreferrer">eager loading</a>.

## How to Force Laravel to Use Eager Loading

A <a href="https://github.com/laravel/framework/pull/37363" target="_blank" rel="noopener noreferrer">new feature</a> (added by <a href="https://github.com/themsaid" target="_blank" rel="noopener noreferrer">Mohamed Said</a>) 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`:

```php
Model::preventLazyLoading();
```

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

```php
namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        // ...
        Model::preventLazyLoading();
        // ...
    }
}
```

## 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:

```php
Model::preventLazyLoading(! app()->isProduction());
```

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

```php
namespace App\Providers;

use Illuminate\Support\ServiceProvider;

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

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:

```php
$comments = Comment::all();

foreach ($comments as $comment ) {
    print_r($comment->author->name);
}
```

However, the following snippet would not throw an exception:

```php
$comments = Comment::with(‘authors’)->get();

foreach ($comments as $comment ) {
    print_r($comment->author->name);
}
```

## 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! 🚀