77
88/**
99 * Provides database storage for Settings.
10+ * Uses local storage to minimize database calls.
1011 */
1112class DatabaseHandler extends BaseHandler
1213{
1314 /**
14- * Stores our cached settings retrieved
15- * from the database on the first get() call
16- * to reduce the number of database calls
17- * at the expense of a little bit of memory.
15+ * The database table to use.
1816 *
19- * @var array
17+ * @var string
2018 */
21- private $ settings = [] ;
19+ private $ table ;
2220
2321 /**
24- * Have the settings been read and cached
25- * from the database yet?
22+ * Storage for cached general settings.
23+ * Format: ['class' => ['property' => ['value', 'type']]]
2624 *
27- * @var bool
25+ * @var array<string,array<string,array>>|null Will be null until hydrated
2826 */
29- private $ hydrated = false ;
27+ private $ general ;
3028
3129 /**
32- * The settings table
30+ * Storage for cached context settings.
31+ * Format: ['context' => ['class' => ['property' => ['value', 'type']]]]
3332 *
34- * @var string
33+ * @var array< string,array|null>
3534 */
36- private $ table ;
35+ private $ contexts = [];
36+
37+ /**
38+ * Stores the configured database table.
39+ */
40+ public function __construct ()
41+ {
42+ $ this ->table = config ('Settings ' )->database ['table ' ] ?? 'settings ' ;
43+ }
3744
3845 /**
3946 * Checks whether this handler has a value set.
4047 */
4148 public function has (string $ class , string $ property , ?string $ context = null ): bool
4249 {
43- $ this ->hydrate ();
50+ $ this ->hydrate ($ context );
4451
45- if (! isset ($ this ->settings [$ class ][$ property ])) {
46- return false ;
52+ if ($ context === null ) {
53+ return isset ($ this ->general [$ class ])
54+ ? array_key_exists ($ property , $ this ->general [$ class ])
55+ : false ;
4756 }
4857
49- return array_key_exists ($ context ?? 0 , $ this ->settings [$ class ][$ property ]);
58+ return isset ($ this ->contexts [$ context ][$ class ])
59+ ? array_key_exists ($ property , $ this ->contexts [$ context ][$ class ])
60+ : false ;
5061 }
5162
5263 /**
5364 * Attempt to retrieve a value from the database.
5465 * To boost performance, all of the values are
55- * read and stored in $this->settings the first
56- * time, and then used from there the rest of the request .
66+ * read and stored the first call for each contexts
67+ * and then retrieved from storage .
5768 *
5869 * @return mixed|null
5970 */
@@ -63,25 +74,28 @@ public function get(string $class, string $property, ?string $context = null)
6374 return null ;
6475 }
6576
66- return $ this ->parseValue (...$ this ->settings [$ class ][$ property ][$ context ?? 0 ]);
77+ return $ context === null
78+ ? $ this ->parseValue (...$ this ->general [$ class ][$ property ])
79+ : $ this ->parseValue (...$ this ->contexts [$ context ][$ class ][$ property ]);
6780 }
6881
6982 /**
7083 * Stores values into the database for later retrieval.
7184 *
7285 * @param mixed $value
7386 *
87+ * @throws RuntimeException For database failures
88+ *
7489 * @return mixed|void
7590 */
7691 public function set (string $ class , string $ property , $ value = null , ?string $ context = null )
7792 {
78- $ this ->hydrate ();
7993 $ time = Time::now ()->format ('Y-m-d H:i:s ' );
8094 $ type = gettype ($ value );
8195 $ value = $ this ->prepareValue ($ value );
8296
83- // If we found it in our cache, then we need to update
84- if (isset ( $ this ->settings [ $ class][ $ property][ $ context ?? 0 ] )) {
97+ // If it was stored then we need to update
98+ if ($ this ->has ( $ class, $ property, $ context )) {
8599 $ result = db_connect ()->table ($ this ->table )
86100 ->where ('class ' , $ class )
87101 ->where ('key ' , $ property )
@@ -91,6 +105,7 @@ public function set(string $class, string $property, $value = null, ?string $con
91105 'context ' => $ context ,
92106 'updated_at ' => $ time ,
93107 ]);
108+ // ...otherwise insert it
94109 } else {
95110 $ result = db_connect ()->table ($ this ->table )
96111 ->insert ([
@@ -104,19 +119,21 @@ public function set(string $class, string $property, $value = null, ?string $con
104119 ]);
105120 }
106121
107- // Update our cache
122+ // Update storage
108123 if ($ result === true ) {
109- if (! array_key_exists ($ class , $ this ->settings )) {
110- $ this ->settings [$ class ] = [];
124+ if ($ context === null ) {
125+ $ this ->general [$ class ][$ property ] = [
126+ $ value ,
127+ $ type ,
128+ ];
129+ } else {
130+ $ this ->contexts [$ context ][$ class ][$ property ] = [
131+ $ value ,
132+ $ type ,
133+ ];
111134 }
112- if (! array_key_exists ($ property , $ this ->settings [$ class ])) {
113- $ this ->settings [$ class ][$ property ] = [];
114- }
115-
116- $ this ->settings [$ class ][$ property ][$ context ?? 0 ] = [
117- $ value ,
118- $ type ,
119- ];
135+ } else {
136+ throw new RuntimeException (db_connect ()->error ()['message ' ] ?? 'Error writing to the database. ' );
120137 }
121138
122139 return $ result ;
@@ -128,9 +145,9 @@ public function set(string $class, string $property, $value = null, ?string $con
128145 */
129146 public function forget (string $ class , string $ property , ?string $ context = null )
130147 {
131- $ this ->hydrate ();
148+ $ this ->hydrate ($ context );
132149
133- // Delete from persistent storage
150+ // Delete from the database
134151 $ result = db_connect ()->table ($ this ->table )
135152 ->where ('class ' , $ class )
136153 ->where ('key ' , $ property )
@@ -142,46 +159,64 @@ public function forget(string $class, string $property, ?string $context = null)
142159 }
143160
144161 // Delete from local storage
145- unset($ this ->settings [$ class ][$ property ][$ context ?? 0 ]);
162+ if ($ context === null ) {
163+ unset($ this ->general [$ class ][$ property ]);
164+ } else {
165+ unset($ this ->contexts [$ context ][$ class ][$ property ]);
166+ }
146167
147168 return $ result ;
148169 }
149170
150171 /**
151- * Ensures we've pulled all of the values from the database.
172+ * Fetches values from the database in bulk to minimize calls.
173+ * General is always fetched once, contexts are fetched in their
174+ * entirety for each new request.
152175 *
153- * @throws RuntimeException
176+ * @throws RuntimeException For database failures
154177 */
155- private function hydrate ()
178+ private function hydrate (? string $ context )
156179 {
157- if ($ this ->hydrated ) {
158- return ;
159- }
180+ if ($ context === null ) {
181+ // Check for completion
182+ if ($ this ->general !== null ) {
183+ return ;
184+ }
160185
161- $ this ->table = config ('Settings ' )->database ['table ' ] ?? 'settings ' ;
186+ $ this ->general = [];
187+ $ query = db_connect ()->table ($ this ->table )->where ('context ' , null );
188+ } else {
189+ // Check for completion
190+ if (isset ($ this ->contexts [$ context ])) {
191+ return ;
192+ }
162193
163- $ rawValues = db_connect ()->table ($ this ->table )->get ( );
194+ $ query = db_connect ()->table ($ this ->table )->where ( ' context ' , $ context );
164195
165- if (is_bool ($ rawValues )) {
166- throw new RuntimeException (db_connect ()->error ()['message ' ] ?? 'Error reading from database. ' );
167- }
196+ // If general has not been hydrated we will do that at the same time
197+ if ($ this ->general === null ) {
198+ $ this ->general = [];
199+ $ query ->orWhere ('context ' , null );
200+ }
168201
169- $ rawValues = $ rawValues ->getResultObject ();
202+ $ this ->contexts [$ context ] = [];
203+ }
170204
171- foreach ($ rawValues as $ row ) {
172- if (! array_key_exists ($ row ->class , $ this ->settings )) {
173- $ this ->settings [$ row ->class ] = [];
174- }
175- if (! array_key_exists ($ row ->key , $ this ->settings [$ row ->class ])) {
176- $ this ->settings [$ row ->class ][$ row ->key ] = [];
177- }
205+ if (is_bool ($ result = $ query ->get ())) {
206+ throw new RuntimeException (db_connect ()->error ()['message ' ] ?? 'Error reading from database. ' );
207+ }
178208
179- $ this ->settings [$ row ->class ][$ row ->key ][$ row ->context ?? 0 ] = [
209+ foreach ($ result ->getResultObject () as $ row ) {
210+ $ tuple = [
180211 $ row ->value ,
181212 $ row ->type ,
182213 ];
183- }
184214
185- $ this ->hydrated = true ;
215+ if ($ row ->context === null ) {
216+ $ this ->general [$ row ->class ][$ row ->key ] = $ tuple ;
217+ } else {
218+ $ this ->contexts [$ row ->context ][$ row ->class ][$ row ->key ] = $ tuple ;
219+ }
220+ }
186221 }
187222}
0 commit comments