From 77eebbd35dd831a9e0216ba30f6a618a98505a61 Mon Sep 17 00:00:00 2001 From: Bubka <858858+Bubka@users.noreply.github.com> Date: Fri, 10 Feb 2023 14:16:40 +0100 Subject: [PATCH] Use Laravel Cache to optimize access to user Settings --- app/Services/SettingService.php | 34 ++++++++++- tests/Feature/Services/SettingServiceTest.php | 58 ++++++++++++++++++- 2 files changed, 86 insertions(+), 6 deletions(-) diff --git a/app/Services/SettingService.php b/app/Services/SettingService.php index 530605ca..54a0ede2 100644 --- a/app/Services/SettingService.php +++ b/app/Services/SettingService.php @@ -7,10 +7,10 @@ use Exception; use Illuminate\Support\Arr; use Illuminate\Support\Collection; -use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\Crypt; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Log; +use Illuminate\Support\Facades\Cache; use Throwable; class SettingService @@ -22,12 +22,27 @@ class SettingService */ private Collection $settings; + /** + * Cache duration + * + * @var int $minutes + */ + private int $minutes = 10; + + /** + * Name of the cache item where options are persisted + */ + public const CACHE_ITEM_NAME = 'userOptions'; + /** * Constructor */ public function __construct() { - self::build(); + $this->settings = Cache::remember(self::CACHE_ITEM_NAME, now()->addMinutes($this->minutes), function () { + self::build(); + return $this->settings; + }); } /** @@ -74,7 +89,7 @@ public function set($setting, $value = null) : void Log::info(sprintf('Setting %s is now %s', var_export($setting, true), var_export($this->restoreType($value), true))); } - self::build(); + self::buildAndCache(); } /** @@ -86,6 +101,8 @@ public function delete(string $name) : void { Option::where('key', $name)->delete(); Log::info(sprintf('Setting %s deleted', var_export($name, true))); + + self::buildAndCache(); } /** @@ -121,6 +138,17 @@ private function build() $this->settings = $settings; } + /** + * Build and cache the options collection + * + * @return void + */ + private function buildAndCache() + { + self::build(); + Cache::put(self::CACHE_ITEM_NAME, $this->settings, now()->addMinutes($this->minutes)); + } + /** * Replaces boolean by a patterned string as appstrack/laravel-options package does not support var type * diff --git a/tests/Feature/Services/SettingServiceTest.php b/tests/Feature/Services/SettingServiceTest.php index e6470b35..232ab365 100644 --- a/tests/Feature/Services/SettingServiceTest.php +++ b/tests/Feature/Services/SettingServiceTest.php @@ -3,8 +3,10 @@ namespace Tests\Feature\Services; use App\Facades\Settings; +use App\Services\SettingService; use App\Models\TwoFAccount; use Illuminate\Support\Facades\Crypt; +use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\DB; use Tests\FeatureTestCase; @@ -172,14 +174,17 @@ public function test_all_returns_native_and_user_settings() /** * @test */ - public function test_set_setting_persist_correct_value() + public function test_set_setting_persist_correct_value_in_db_and_cache() { $value = Settings::set(self::SETTING_NAME, self::SETTING_VALUE_STRING); + $cached = Cache::get(SettingService::CACHE_ITEM_NAME); // returns a Collection $this->assertDatabaseHas('options', [ self::KEY => self::SETTING_NAME, self::VALUE => self::SETTING_VALUE_STRING, ]); + + $this->assertEquals($cached->get(self::SETTING_NAME), self::SETTING_VALUE_STRING); } /** @@ -278,6 +283,7 @@ public function test_set_array_of_settings_persist_correct_values() self::SETTING_NAME => self::SETTING_VALUE_STRING, self::SETTING_NAME_ALT => self::SETTING_VALUE_INT, ]); + $cached = Cache::get(SettingService::CACHE_ITEM_NAME); // returns a Collection $this->assertDatabaseHas('options', [ self::KEY => self::SETTING_NAME, @@ -288,6 +294,9 @@ public function test_set_array_of_settings_persist_correct_values() self::KEY => self::SETTING_NAME_ALT, self::VALUE => self::SETTING_VALUE_INT, ]); + + $this->assertEquals($cached->get(self::SETTING_NAME), self::SETTING_VALUE_STRING); + $this->assertEquals($cached->get(self::SETTING_NAME_ALT), self::SETTING_VALUE_INT); } /** @@ -319,18 +328,20 @@ public function test_set_false_setting_persist_transformed_boolean() /** * @test */ - public function test_del_remove_setting_from_db() + public function test_del_remove_setting_from_db_and_cache() { DB::table('options')->insert( [self::KEY => self::SETTING_NAME, self::VALUE => strval(self::SETTING_VALUE_STRING)] ); - $value = Settings::delete(self::SETTING_NAME); + Settings::delete(self::SETTING_NAME); + $cached = Cache::get(SettingService::CACHE_ITEM_NAME); // returns a Collection $this->assertDatabaseMissing('options', [ self::KEY => self::SETTING_NAME, self::VALUE => self::SETTING_VALUE_STRING, ]); + $this->assertFalse($cached->has(self::SETTING_NAME)); } /** @@ -354,4 +365,45 @@ public function test_isUserDefined_returns_false() $this->assertFalse(Settings::isUserDefined('showTokenAsDot')); } + + /** + * @test + */ + public function test_cache_is_requested_at_instanciation() + { + Cache::shouldReceive('remember') + ->andReturn(collect([])); + + $settingService = new SettingService(); + + Cache::shouldHaveReceived('remember'); + } + + /** + * + */ + public function test_cache_is_updated_when_setting_is_set() + { + Cache::shouldReceive('remember', 'put') + ->andReturn(collect([]), true); + + $settingService = new SettingService(); + $settingService->set(self::SETTING_NAME, self::SETTING_VALUE_STRING); + + Cache::shouldHaveReceived('put'); + } + + /** + * + */ + public function test_cache_is_updated_when_setting_is_deleted() + { + Cache::shouldReceive('remember', 'put') + ->andReturn(collect([]), true); + + $settingService = new SettingService(); + $settingService->delete(self::SETTING_NAME); + + Cache::shouldHaveReceived('put'); + } }