Be Careful $with Laravel

Following a little surprise on a Laravel project I was developing, I figured best to write a short post about it.

Make sure to be very careful when you use the $with property in your models. Whilst this can be a helpful property – it allows the model to always eager load a relationship or nested relationship – use of it without careful reasoning can lead to unexpected, and unnecessary, queries being made to your database, especially if you have the $with property set on a number of models related together in a cascade.

What are you whithering on about?

For example, you have a Company which hasMany branches, each Branch hasMany departments, each Department hasMany staff and each Staff hasOne profile, and hasMany likes.

Whilst you might think, well every time I load my company I need to show its branches, so you do the following:

class Company {

$with = ['branches'];

Then you decide that every time you load your branch index you need to display your departments. Ok, we’ll use $with there too.

class Branch {

$with = ['departments'];

And so on and so forth, you eager load each cascading relationship.

However if you go back to your companies index, you’ll get a big surprise on the number of queries being made. Each company eager loads its branches, which in turn eager loads its departments, which in turn eager loads its staff, which in turn eager loads the profile, likes, and so on.

Suddenly you’ve gone from having 2 queries to potentially hundreds or thousands (it really can multiply exponentially).

Avoiding the problem

Firstly, think very carefully before you declare eager loading on your model. It may have its uses, but for the most part you’re better off eager loading on a per-query basis.

The main use case I’d consider is for whatever reason you have some Object model and you’ve separated out a hasOne Profile. Every single time you’re querying your object you know for certain you must access their profile to get their “Display Name” or some such information. In this case, setting the $with property might make sense. However, ask yourself whether there will be times you have to query your Object and you will not need that profile information. Only if the answer is “I will 100% always need this related model” should you use the $with property.

Additionally, remember that if you are eager loading in your controllers, you can constrain your eager loads, something you can’t do using the $with property. I haven’t actually tried this, but presumably if you only needed to get the “Display Name” from the Profile model, there’s no reason you couldn’t constrain the eager load to have a select statement.

$objects = App\Object::with(['profile' => function ($query) {
    $query->select('display_name');
}])->get();

Secondly, I strongly recommend installing Laravel Debugbar. This used to be included in Laravel 3 by default, but you need to manually set it up per project now. It’s a straightforward install and it will alert you during local development of any hidden query issues you might be experiencing. This really is crucial to ensuring you have a better understanding of how your queries are being constructed and making you fully aware during development of areas that you can be optimising your application and how it interacts with your database. For smaller projects it may not seem that important, but as your application grows and has hundreds or thousands of users accessing it every minute, you really need to ensure you’re keeping the database server load as low as possible and your application as fast as possible.

Interestingly, I can no longer find any mention of the $with property in the Laravel docs, perhaps it was decided that this process of always eager loading was causing a lot more problems than it solved.

Make sure to read the eager loading docs on Laravel in full!

Comments