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?
Eager loading is the process of getting all the required data from the database in one go up front to avoid multiple unnecessary trips to the database.
It's the opposite of lazy loading whereby we make multiple trips to the database to get the required data as and when needed.
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 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:
1$comments = Comment::all();2 3foreach ($comments as $comment) {4 print_r($comment->author->name);5}
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:
1$comments = Comment::with('author')->get();2 3foreach ($comments as $comment) {4 print_r($comment->author->name);5}
As you can see, this code looks almost the same and is still readable. By adding the ::with('author')
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!
You may even want to take this a step further to optimise the code and improve the performance more by only eager loading the fields you need. For instance, if we're only going to be acting on the authors' id
and name
fields, we might want to write the query like so:
1$comments = Comment::with('author:id,name')->get();2 3foreach ($comments as $comment) {4 print_r($comment->author->name);5}
By doing this, only the id
and name
fields will be available to use on the author
relationship and none of the other fields in the authors
table will have been fetched from the database. As a result, this can have some performance benefits due to the reduced data being transferred from the database.
For more information, check out the Laravel documentation on eager loading.
Prevent Lazy Loading in Laravel
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 disable lazy loading in your Laravel application. All we need to do is add the following line to the boot()
method of our AppServiceProvider
:
1Model::preventLazyLoading();
So, in our AppServiceProvider
, it would look a bit like this:
1namespace App\Providers; 2 3use Illuminate\Support\ServiceProvider; 4 5class AppServiceProvider extends ServiceProvider 6{ 7 public function boot(): void 8 { 9 // ...10 Model::preventLazyLoading();11 // ...12 }13}
Allowing Lazy 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:
1Model::preventLazyLoading(! app()->isProduction());
So, in our AppServiceProvider
, it could look like this:
1namespace App\Providers; 2 3use Illuminate\Support\ServiceProvider; 4 5class AppServiceProvider extends ServiceProvider 6{ 7 public function boot(): void 8 { 9 // ...10 Model::preventLazyLoading(! app()->isProduction());11 // ...12 }13}
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 (and we have disabled lazy loading in our Laravel project) 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:
1$comments = Comment::all();2 3foreach ($comments as $comment) {4 print_r($comment->author->name);5}
However, the following snippet would not throw an exception:
1$comments = Comment::with('author')->get();2 3foreach ($comments as $comment) {4 print_r($comment->author->name);5}
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! 🚀