Eager Loading In Laravel Route Models
Tags: php, laravel, codequality, codenewbie
Published: 2020-10-19 15:18:47

TLDR: Override the `resolveRouteBinding` method on your Eloquent model in order to eager-load relations that you know will be used.

## The problem: Limits To Route Model Binding

In a Laravel application I'm writing, I have a few dozen routes that all have basically the same format: `/jobs/{job}` as a prefix and then particular actions for that job. In Laravel, we have route model binding, which is to say that by convention, you can use the name of a model (the Job model, in this case) in the braces of your route and when your application reaches the controller method, the model is
sent in as a parameter.

For example, with `/jobs/{job}` this means my controller method signature would look like this:

```
public function show(Job $job){ /* ... */ }
```

This is a very powerful function and it saves us a lot of boilerplate Eloquent code searching for the model from the database explicitly in our controller methods.

I'm working on adding notes to each job, which is something I created using a new model `JobNote` which has a one to many relationship with each `Job`.

Now one thing that I want to display in my sub-navigation for all these Jobs is a count of how many JobNotes each Job has. In Laravel, we can eager-load the count by running:

```
$job->loadCount('notes')
```

on the job model I receive as a parameter.

Here's the issue, *I have 40+ routes that use this same job model prefix* and they _all_ need the count since they all share the same sub-navigation where the count will be used. This would mean I'd have to add that line to 40+ spots in the code (once for each controller method) _as well as_ remember to write it in when I write new controller methods in this route group.

Not my idea of a simple or maintainable coding experience.

## The First Idea: Default Eager Loading

Eloquent has the ability to _always_ load a relationship by default using the `protected $with` variable on my Eloquent model, but there are two issues here:

1. I only need the count, not a collection of all the related notes.
2. The Job index route loads lots of jobs, and doesn't use the single Job model binding, so I don't want to get this relationship there.

That second point got me thinking, "Is there a way to hook into a model when it's being resolved from a route?"

The answer: Yes!

## The Real Solution: resolveRouteBinding

Eloquent models have a function, `resolveRouteBinding`, which can be overriden to add extra functionality.

Since this would only happen when a specific job is being asked for in the route, it would cleanly leave out the job index page, resolving the second problem I had with default eager loading.

The function as I needed it, looks like this:

```
public function resolveRouteBinding($value, $field = null)
{
    return $this->where('id', $value)
              ->withCount('notes')
              ->firstOrFail();
}
```

First we search for the actual model, since the `id` is what's passed in, then we make sure to load it with the count we want, and make sure we only get one model! Now the model is loaded with the field `notes_count` available, with the data we need by default. We haven't loaded all the notes, which solves the 1st problem I had with the default eager loading.

It's a regular PHP block so I could imagine adding more functionality to support extra loads depending on the route.

Going this route meant that instead of having to add the
`$job->loadCount('notes')` to each controller method (40+ times just to start!) I just added a few lines to my model code. Much easier to maintain.

If you like this and found it useful, please let me know on [Twitter](https://twitter.com/camasmetaphor)!