Laravel: Caching your Query with only one method

Behold! The power of… macros!

Italo Baeza Cabrera
3 min readJan 6, 2020

As I wrote earlier, the power of Macros in Laravel is quite big, since allows you to extend a class at runtime with your own methods. You can add useful methods that can transform verbose code into expressive one-liners that makes sense. One of these comes out of the box, and is the Request Validation:

public function (Request $request)
{
$request->validate([
'email' => 'required|unique:users',
]);
// ...
}

Among the list of classes that implements the Macroable trait, which enables the macro functionality in the class, are the Query Builder and Eloquent Builder. Both of these are used to make queries directly to the database.

One of the things I constantly do when I need some expensive SQL computation is to save the result in the Cache, which makes the code very bloated:

$votes = 100;$users = Cache::remember('users', 60, function () use ($votes) {
return Users::has('comments')
->where('votes', '>', $votes)
->get();
});

Wouldn’t be nice to do something in just one line instead of guessing what the f*ck did I wrote?

Of course!

The Plan, The Class and the Macro

I want to remember the results using this simple syntax; this is what I want to write in my application:

$results = User::where('something')->remember(60)->get();

For this to work, adding the remember() method as a Macro to the Query Builder won’t suffice. If we do only that, using another other method like get() (or any other retrieval method) it will return the query result bypassing any remembering logic.

To put it in other words: the cache and the query result are not connected.

So I ended making a very simple package following the diagram below. There is a tiny class that, once you call remember(), will intermediate between the Builder and the Cache.

To better explain it, when you use the remember() method, the Eloquent or Query Builder instance will be encapsulated inside a class that will pass all your methods to the Builder… except when the Builder returns a database result — basically anything except the Builder instance itself.

This is a very good way to make expensive calls to the Database without making verbose logic o use closures. Let’s go back to our first example:

$votes = 100;$users = Cache::remember('users', 60, function () use ($votes) {
return Users::has('comments')
->where('votes', '>', $votes)
->get();
});

Okay, we know we are using the Cache. A Closure is inside the Cache logic that actually calls the Database, while importing a variable from outside. Now compare that to this expressive line:

$users = Users::has('comments')->where('votes', '>', $votes)
->remember()
->get();

The good part? The cache key handling is being made automatically. It basically hashes the query SQL statement along with the bindings, making every statement unique. In other words, the logic may be the same, but if you change a variable like the number of votes from 100 to 99, the hash will be different, and the result will be executed and saved to the Cache.

That’s it. Give it a go and tell me what you think.

--

--

Italo Baeza Cabrera

Graphic Designer graduate. Full Stack Web Developer. Retired Tech & Gaming Editor. https://italobc.com