A static property of a class as cache

While developing apps, developers have a case when they need to get data from the same table of DB, which aren't often changed, quite regularly. store settings or user settings are good examples what I mean. If an app is quite complex, the app'll need to get these data a minimum of several times during a request. It might be an optimisation problem for DB. In this article, I'm showing how to solve cases like that.

The background

In the beginning, I need to show you how my code for tests looks.

Migration to create a table in DB:

public function up()
{
    Schema::create('store_settings', function (Blueprint $table) {
        $table->id();
        $table->foreignId('store_id');
        $table->string('key');
        $table->string('value');
        $table->timestamps();
    });
}

Below code is used to simulate getting of settings:

Route::get('/', function () {
    $storeSettingManager = app(StoreSettingManager::class, ['storeId' => 1]);

    logger($storeSettingManager->getValue('store_title'));
    logger($storeSettingManager->getValue('default_currency'));
    logger($storeSettingManager->getValue('guest_can_buy'));
    logger($storeSettingManager->getValue('ssl_enable'));

    return view('welcome');
});

StoreSettingManager:

class StoreSettingManager
{
    public function __construct(private int $storeId)
    {
    }

    public function getValue(string $key):?string {
        return StoreSetting::where('store_id', $this->storeId)
            ->where('key', $key)
            ->first();
    }
}

My StoreSettingManager class fetches a setting from DB when I call getValue from this class. In my example, I want to fetch 4 settings. Please have a look what Clockwork shows:

many-queries-in-clockwork

Clockwork shows 4 queries - for every calls of getValue(). If you need dozens of settings, it'll be an optimisation issue.

The solution

Fortunately, you can fix it quite easily. You can use a static property to save data. A value of this property is common for every instance of a class. If you put data there, it'll be getable globally.

This is my solution:

class StoreSettingManager
{
    private static array $cache = [];

    public function __construct(private int $storeId)
    {
    }

    public function getValue(string $key):?string {
        $values = $this->getCachedValues();

        if (!isset($values[$key])) {
            return null;
        }

        return $values[$key];
    }

    private function getCachedValues(): Collection
    {
        if (!isset(self::$cache[$this->storeId])) {
            self::$cache[$this->storeId] = StoreSetting::where('store_id', $this->storeId)
                ->pluck('value', 'key');
        }

        return self::$cache[$this->storeId];
    }

    public static function clearCache(?int $storeId = null): void
    {
        if (is_null($storeId)) {
            self::$cache = [];
            return;
        }

        if (isset(self::$cache[$storeId])) {
            unset(self::$cache[$storeId]);
        }
    }
}

Now I don't fetch data each every call of getValue. I fetch all settings for the given storeId and save it in the $cache property. If I want to delete cache (for example while saving settings), I just need to call clearCache().

This solution is useful for cases where you often need to get data but it isn't often changed.

Comments

Blog Comments powered by Disqus.

Older Post Newer Post