33namespace BookStack \Settings ;
44
55use BookStack \Auth \User ;
6- use Illuminate \Contracts \Cache \Repository as Cache ;
76
87/**
98 * Class SettingService
109 * The settings are a simple key-value database store.
1110 * For non-authenticated users, user settings are stored via the session instead.
11+ * A local array-based cache is used to for setting accesses across a request.
1212 */
1313class SettingService
1414{
15- protected Setting $ setting ;
16- protected Cache $ cache ;
1715 protected array $ localCache = [];
18- protected string $ cachePrefix = 'setting- ' ;
19-
20- public function __construct (Setting $ setting , Cache $ cache )
21- {
22- $ this ->setting = $ setting ;
23- $ this ->cache = $ cache ;
24- }
2516
2617 /**
2718 * Gets a setting from the database,
2819 * If not found, Returns default, Which is false by default.
2920 */
30- public function get (string $ key , $ default = null )
21+ public function get (string $ key , $ default = null ): mixed
3122 {
3223 if (is_null ($ default )) {
3324 $ default = config ('setting-defaults. ' . $ key , false );
3425 }
3526
36- if (isset ($ this ->localCache [$ key ])) {
37- return $ this ->localCache [$ key ];
38- }
39-
4027 $ value = $ this ->getValueFromStore ($ key ) ?? $ default ;
41- $ formatted = $ this ->formatValue ($ value , $ default );
42- $ this ->localCache [$ key ] = $ formatted ;
43-
44- return $ formatted ;
28+ return $ this ->formatValue ($ value , $ default );
4529 }
4630
4731 /**
@@ -79,52 +63,78 @@ public function getForCurrentUser(string $key, $default = null)
7963 }
8064
8165 /**
82- * Gets a setting value from the cache or database.
83- * Looks at the system defaults if not cached or in database.
84- * Returns null if nothing is found.
66+ * Gets a setting value from the local cache.
67+ * Will load the local cache if not previously loaded.
8568 */
86- protected function getValueFromStore (string $ key )
69+ protected function getValueFromStore (string $ key ): mixed
8770 {
88- // Check the cache
89- $ cacheKey = $ this ->cachePrefix . $ key ;
90- $ cacheVal = $ this ->cache ->get ($ cacheKey , null );
91- if ($ cacheVal !== null ) {
92- return $ cacheVal ;
71+ $ cacheCategory = $ this ->localCacheCategory ($ key );
72+ if (!isset ($ this ->localCache [$ cacheCategory ])) {
73+ $ this ->loadToLocalCache ($ cacheCategory );
9374 }
9475
95- // Check the database
96- $ settingObject = $ this ->getSettingObjectByKey ($ key );
97- if ($ settingObject !== null ) {
98- $ value = $ settingObject ->value ;
76+ return $ this ->localCache [$ cacheCategory ][$ key ] ?? null ;
77+ }
9978
100- if ($ settingObject ->type === 'array ' ) {
101- $ value = json_decode ($ value , true ) ?? [];
102- }
79+ /**
80+ * Put the given value into the local cached under the given key.
81+ */
82+ protected function putValueIntoLocalCache (string $ key , mixed $ value ): void
83+ {
84+ $ cacheCategory = $ this ->localCacheCategory ($ key );
85+ if (!isset ($ this ->localCache [$ cacheCategory ])) {
86+ $ this ->loadToLocalCache ($ cacheCategory );
87+ }
10388
104- $ this ->cache ->forever ($ cacheKey , $ value );
89+ $ this ->localCache [$ cacheCategory ][$ key ] = $ value ;
90+ }
10591
106- return $ value ;
92+ /**
93+ * Get the category for the given setting key.
94+ * Will return 'app' for a general app setting otherwise 'user:<user_id>' for a user setting.
95+ */
96+ protected function localCacheCategory (string $ key ): string
97+ {
98+ if (str_starts_with ($ key , 'user: ' )) {
99+ return implode (': ' , array_slice (explode (': ' , $ key ), 0 , 2 ));
107100 }
108101
109- return null ;
102+ return ' app ' ;
110103 }
111104
112105 /**
113- * Clear an item from the cache completely .
106+ * For the given category, load the relevant settings from the database into the local cache .
114107 */
115- protected function clearFromCache (string $ key )
108+ protected function loadToLocalCache (string $ cacheCategory ): void
116109 {
117- $ cacheKey = $ this ->cachePrefix . $ key ;
118- $ this ->cache ->forget ($ cacheKey );
119- if (isset ($ this ->localCache [$ key ])) {
120- unset($ this ->localCache [$ key ]);
110+ $ query = Setting::query ();
111+
112+ if ($ cacheCategory === 'app ' ) {
113+ $ query ->where ('setting_key ' , 'not like ' , 'user:% ' );
114+ } else {
115+ $ query ->where ('setting_key ' , 'like ' , $ cacheCategory . ':% ' );
116+ }
117+ $ settings = $ query ->toBase ()->get ();
118+
119+ if (!isset ($ this ->localCache [$ cacheCategory ])) {
120+ $ this ->localCache [$ cacheCategory ] = [];
121+ }
122+
123+ foreach ($ settings as $ setting ) {
124+ $ value = $ setting ->value ;
125+
126+ if ($ setting ->type === 'array ' ) {
127+ $ value = json_decode ($ value , true ) ?? [];
128+ }
129+
130+ $ this ->localCache [$ cacheCategory ][$ setting ->setting_key ] = $ value ;
121131 }
122132 }
123133
124134 /**
125135 * Format a settings value.
126136 */
127- protected function formatValue ($ value , $ default )
137+ protected function formatValue (mixed $ value , mixed $ default ): mixed
128138 {
129139 // Change string booleans to actual booleans
130140 if ($ value === 'true ' ) {
@@ -155,21 +165,22 @@ public function has(string $key): bool
155165 * Add a setting to the database.
156166 * Values can be an array or a string.
157167 */
158- public function put (string $ key , $ value ): bool
168+ public function put (string $ key , mixed $ value ): bool
159169 {
160- $ setting = $ this -> setting -> newQuery ()->firstOrNew ([
170+ $ setting = Setting:: query ()->firstOrNew ([
161171 'setting_key ' => $ key ,
162172 ]);
173+
163174 $ setting ->type = 'string ' ;
175+ $ setting ->value = $ value ;
164176
165177 if (is_array ($ value )) {
166178 $ setting ->type = 'array ' ;
167- $ value = $ this ->formatArrayValue ($ value );
179+ $ setting -> value = $ this ->formatArrayValue ($ value );
168180 }
169181
170- $ setting ->value = $ value ;
171182 $ setting ->save ();
172- $ this ->clearFromCache ($ key );
183+ $ this ->putValueIntoLocalCache ($ key, $ value );
173184
174185 return true ;
175186 }
@@ -209,7 +220,7 @@ public function putUser(User $user, string $key, string $value): bool
209220 * Can only take string value types since this may use
210221 * the session which is less flexible to data types.
211222 */
212- public function putForCurrentUser (string $ key , string $ value )
223+ public function putForCurrentUser (string $ key , string $ value ): bool
213224 {
214225 return $ this ->putUser (user (), $ key , $ value );
215226 }
@@ -231,15 +242,19 @@ public function remove(string $key): void
231242 if ($ setting ) {
232243 $ setting ->delete ();
233244 }
234- $ this ->clearFromCache ($ key );
245+
246+ $ cacheCategory = $ this ->localCacheCategory ($ key );
247+ if (isset ($ this ->localCache [$ cacheCategory ])) {
248+ unset($ this ->localCache [$ cacheCategory ][$ key ]);
249+ }
235250 }
236251
237252 /**
238253 * Delete settings for a given user id.
239254 */
240- public function deleteUserSettings (string $ userId )
255+ public function deleteUserSettings (string $ userId ): void
241256 {
242- return $ this -> setting -> newQuery ()
257+ Setting:: query ()
243258 ->where ('setting_key ' , 'like ' , $ this ->userKey ($ userId ) . '% ' )
244259 ->delete ();
245260 }
@@ -249,7 +264,16 @@ public function deleteUserSettings(string $userId)
249264 */
250265 protected function getSettingObjectByKey (string $ key ): ?Setting
251266 {
252- return $ this ->setting ->newQuery ()
253- ->where ('setting_key ' , '= ' , $ key )->first ();
267+ return Setting::query ()
268+ ->where ('setting_key ' , '= ' , $ key )
269+ ->first ();
270+ }
271+
272+ /**
273+ * Empty the local setting value cache used by this service.
274+ */
275+ public function flushCache (): void
276+ {
277+ $ this ->localCache = [];
254278 }
255279}
0 commit comments