I’ve been working on a Laravel project that requires numerous database calls, so I have started implementing caching to try to improve performance and reduce database queries.
To start with, I was just using Laravel’s Cache
facade, like below:
Cache::remember(
'unique-key',
60, // cache lives for 60 seconds
fn () => $this->operationToBeCached()
);
Cache::remember
is really nice because it will pull from the cache if it exists or regenerate the value using the closure, put it in the cache, and return it.
Let’s say this cache is for a user and when the data involved changes, I need to forget the cache. It’s not too hard, just use the forget
method.
Cache::forget('unique-key');
The problem I started running into though is trying to remember that unique key. I don’t like having magic strings used throughout the app to set or remove items from the cache. It gets even more difficult when the unique key needs to include additional data like model IDs.
To solve this, I created a CacheHelper
class.
<?php
namespace App\Cache;
use Illuminate\Support\Facades\Cache;
abstract class CacheHelper
{
protected string $key; // Cache key
protected int $ttl = 60; // Time to live
public function getKey(): string
{
return $this->key;
}
public function value(): mixed // Store and fetch value from cache
{
return Cache::remember(
$this->getKey(),
$this->ttl,
fn () => $this->generate()
);
}
public function forget(): bool // Forget value from the cache
{
return Cache::forget($this->getKey());
}
abstract protected function generate(): mixed; // Abstract method for generating cache value
}
The CacheHelper
class is abstract, so it must be extended and include a generate
method. I also created a trait that allows easily setting unique keys using an ID.
<?php
namespace App\Cache;
trait CacheIdentifier
{
protected int|string|null $identifier = null;
public function getKey(): string
{
return sprintf($this->key, $this->identifier); // Attach an identifier to the cache key.
}
}
Here’s an example below on how this can be extended.
<?php
namespace App\Cache;
use App\Models\User;
use Illuminate\Support\Collection;
class UserExpensiveComputationCache extends CacheHelper
{
use CacheIdentifier;
protected string $key = 'expensive_computation_:%s';
protected int $ttl = 60;
public function __construct(protected readonly User $user)
{
$this->identifier = $user->id; // The identifier is the user ID and is attached to the key.
}
public static function make(User $user): static
{
return new static($user); // Simple helper to create a new instance
}
protected function generate(): Collection
{
return $this->user->expensiveComputation();
}
}
With this set, whenever I need to access the expensive computation, I can use my cache class:
<?php
$user = User::find(1);
$value = UserExpensiveComputationCache::make($user);
The UserExpensiveComputationCache
class will remember the value for the user I passed to it.
When it comes time to clear the cache because a related record was modified, I can just do the following:
<?php
UserExpensiveComputationCache::make($user)->forget();
I no longer need to remember a magic string throughout my application. I just have a simple class that I can instantiate and use it to handle my unique keys, setting, and forgetting the cache. If I have to change the key for whatever reason, it only happens in this one place in the application and I don’t have to search around for other instances of the key.
I was able to throw this CacheHelper
class together pretty quickly, and it solved my problem of avoiding magic strings across my application and being able to easily manage forgetting the cache when needed. Using dedicated classes to cache items isn’t something I have seen used before. Please let me know if you’ve seen something similar or have other ideas and strategies for managing cache. Thanks for reading!