Introduction

When working with eloquent to fetch models-related data from the database, and then doing any type of processing on the model’s relations, it’s important that we use eager loading. Eager loading is super simple to implement in Laravel and prevents us from encountering the N+1 query problem. This problem is caused by making N+1 (multiple, similar kinds of) queries to the database, where N is the number of items being fetched from the database. To explain this better, let's check out the example below.

Let's say we have two models (User and Post) with a one-to-many relationship between them. Now imagine that we have 100 posts made by the User and we want to loop through each one of them and output the user’s name.

Without eager loading functionality, our code would look like this:


$posts = Post::all();
 
foreach ($posts as $post ) {
    print_r($post->user->name);
}

The code above would result in 101 database queries, since the first query will run to fetch the posts, and another 100 queries to fetch the associated user's name in each iteration of the loop since we have 100 items in the database.

Small negligences like these can cause performance issues and slow down our application, especially when we have a large amount of data and we make queries like these on different parts of the application. So, how would we improve this?

To make use of eager loading, we would update the code like so:


$posts = Post::with(‘user’)->get();
 
foreach ($posts as $post ) {
    print_r($post->user->name);
}

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

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


Force Laravel to Use Eager Loading

new feature has recently been merged into the Laravel codebase that allows us 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();

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();
        // ...
    }
}

Allowing Eager Loading in Production Environments

It's possible that we might only want to enable this feature while in the development phase to prevent lazy loading prior to hosting the app to production. By doing that, it can alert us in places that are using lazy loading while building new features, but not completely crash our 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());

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());
        // ...
    }
}

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


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, an Illuminate\Database\LazyLoadingViolationException exception will be thrown.

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

The following snippet would throw an exception:


$posts = Post::all();
 
foreach ($posts as $post ) {
    print_r($post->user->name);
}

However, the following snippet would not throw an exception:


$posts = Post::with(‘user’)->get();
 
foreach ($posts as $post ) {
    print_r($post->user->name);
}

 

 

Comments