3
3
namespace BookStack \Settings ;
4
4
5
5
use BookStack \Auth \User ;
6
- use Illuminate \Contracts \Cache \Repository as Cache ;
7
6
8
7
/**
9
8
* Class SettingService
10
9
* The settings are a simple key-value database store.
11
10
* 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.
12
12
*/
13
13
class SettingService
14
14
{
15
- protected Setting $ setting ;
16
- protected Cache $ cache ;
17
15
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
- }
25
16
26
17
/**
27
18
* Gets a setting from the database,
28
19
* If not found, Returns default, Which is false by default.
29
20
*/
30
- public function get (string $ key , $ default = null )
21
+ public function get (string $ key , $ default = null ): mixed
31
22
{
32
23
if (is_null ($ default )) {
33
24
$ default = config ('setting-defaults. ' . $ key , false );
34
25
}
35
26
36
- if (isset ($ this ->localCache [$ key ])) {
37
- return $ this ->localCache [$ key ];
38
- }
39
-
40
27
$ 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 );
45
29
}
46
30
47
31
/**
@@ -79,52 +63,78 @@ public function getForCurrentUser(string $key, $default = null)
79
63
}
80
64
81
65
/**
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.
85
68
*/
86
- protected function getValueFromStore (string $ key )
69
+ protected function getValueFromStore (string $ key ): mixed
87
70
{
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 );
93
74
}
94
75
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
+ }
99
78
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
+ }
103
88
104
- $ this ->cache ->forever ($ cacheKey , $ value );
89
+ $ this ->localCache [$ cacheCategory ][$ key ] = $ value ;
90
+ }
105
91
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 ));
107
100
}
108
101
109
- return null ;
102
+ return ' app ' ;
110
103
}
111
104
112
105
/**
113
- * Clear an item from the cache completely .
106
+ * For the given category, load the relevant settings from the database into the local cache .
114
107
*/
115
- protected function clearFromCache (string $ key )
108
+ protected function loadToLocalCache (string $ cacheCategory ): void
116
109
{
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 ;
121
131
}
122
132
}
123
133
124
134
/**
125
135
* Format a settings value.
126
136
*/
127
- protected function formatValue ($ value , $ default )
137
+ protected function formatValue (mixed $ value , mixed $ default ): mixed
128
138
{
129
139
// Change string booleans to actual booleans
130
140
if ($ value === 'true ' ) {
@@ -155,21 +165,22 @@ public function has(string $key): bool
155
165
* Add a setting to the database.
156
166
* Values can be an array or a string.
157
167
*/
158
- public function put (string $ key , $ value ): bool
168
+ public function put (string $ key , mixed $ value ): bool
159
169
{
160
- $ setting = $ this -> setting -> newQuery ()->firstOrNew ([
170
+ $ setting = Setting:: query ()->firstOrNew ([
161
171
'setting_key ' => $ key ,
162
172
]);
173
+
163
174
$ setting ->type = 'string ' ;
175
+ $ setting ->value = $ value ;
164
176
165
177
if (is_array ($ value )) {
166
178
$ setting ->type = 'array ' ;
167
- $ value = $ this ->formatArrayValue ($ value );
179
+ $ setting -> value = $ this ->formatArrayValue ($ value );
168
180
}
169
181
170
- $ setting ->value = $ value ;
171
182
$ setting ->save ();
172
- $ this ->clearFromCache ($ key );
183
+ $ this ->putValueIntoLocalCache ($ key, $ value );
173
184
174
185
return true ;
175
186
}
@@ -209,7 +220,7 @@ public function putUser(User $user, string $key, string $value): bool
209
220
* Can only take string value types since this may use
210
221
* the session which is less flexible to data types.
211
222
*/
212
- public function putForCurrentUser (string $ key , string $ value )
223
+ public function putForCurrentUser (string $ key , string $ value ): bool
213
224
{
214
225
return $ this ->putUser (user (), $ key , $ value );
215
226
}
@@ -231,15 +242,19 @@ public function remove(string $key): void
231
242
if ($ setting ) {
232
243
$ setting ->delete ();
233
244
}
234
- $ this ->clearFromCache ($ key );
245
+
246
+ $ cacheCategory = $ this ->localCacheCategory ($ key );
247
+ if (isset ($ this ->localCache [$ cacheCategory ])) {
248
+ unset($ this ->localCache [$ cacheCategory ][$ key ]);
249
+ }
235
250
}
236
251
237
252
/**
238
253
* Delete settings for a given user id.
239
254
*/
240
- public function deleteUserSettings (string $ userId )
255
+ public function deleteUserSettings (string $ userId ): void
241
256
{
242
- return $ this -> setting -> newQuery ()
257
+ Setting:: query ()
243
258
->where ('setting_key ' , 'like ' , $ this ->userKey ($ userId ) . '% ' )
244
259
->delete ();
245
260
}
@@ -249,7 +264,16 @@ public function deleteUserSettings(string $userId)
249
264
*/
250
265
protected function getSettingObjectByKey (string $ key ): ?Setting
251
266
{
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 = [];
254
278
}
255
279
}
0 commit comments