DEV Community

Cover image for Laravel Cache Classes
Sean Kegel
Sean Kegel

Posted on • Originally published at seankegel.com on

Laravel Cache Classes

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()
);
Enter fullscreen mode Exit fullscreen mode

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');
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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.
    }
}
Enter fullscreen mode Exit fullscreen mode

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();
    }
}
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

The UserExpensiveComputationCacheclass 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();
Enter fullscreen mode Exit fullscreen mode

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!

Top comments (0)