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:
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.