1: <?php
2: /**
3: * This file contains the implementation of the interface ISettingsManager
4: * in the class RuntimeSettingsManager for management of the
5: * application settings.
6: *
7: * @package System
8: *
9: * @author Oleg Schildt
10: */
11:
12: namespace SmartFactory;
13:
14: use \SmartFactory\Interfaces\ISettingsManager;
15: use \SmartFactory\DatabaseWorkers\DBWorker;
16: use SmartFactory\Interfaces\ISettingsValidator;
17:
18: /**
19: * Class for management of the application settings.
20: *
21: * This settings manager supports the multistep wizard like processing. That means
22: * that you do not have to put all settings in one big form and save them in one action.
23: * You can collect them over multiple steps (requests), go forward ad back, validate them
24: * on each step and finally save them after the final step.
25: *
26: * You do not need any preliminary special settings name definitions. When you introduce
27: * a new setting, just start saving and getting it. But you need provide a settings table
28: * with a column where the settings data will be saved.
29: *
30: * @see ConfigSettingsManager
31: * @see UserSettingsManager
32: *
33: * @uses DatabaseWorkers\DBWorker
34: *
35: * @author Oleg Schildt
36: */
37: class RuntimeSettingsManager implements ISettingsManager
38: {
39: /**
40: * Internal variable for storing the dbworker.
41: *
42: * @var ?DatabaseWorkers\DBWorker
43: *
44: * @author Oleg Schildt
45: */
46: protected ?DatabaseWorkers\DBWorker $dbworker = null;
47:
48: /**
49: * Internal variable for storing the target settings table name.
50: *
51: * @var string
52: *
53: * @author Oleg Schildt
54: */
55: protected string $settings_table;
56:
57: /**
58: * Internal variable for storing the target column.
59: *
60: * @var string
61: *
62: * @author Oleg Schildt
63: */
64: protected string $settings_column;
65:
66: /**
67: * Internal variable for storing the current context.
68: *
69: * @var string
70: *
71: * @see RuntimeSettingsManager::getContext()
72: * @see RuntimeSettingsManager::setContext()
73: *
74: * @author Oleg Schildt
75: */
76: protected string $context = "default";
77:
78: /**
79: * Internal variable for storing the validator.
80: *
81: * @var ?Interfaces\ISettingsValidator
82: *
83: * @see RuntimeSettingsManager::getValidator()
84: * @see RuntimeSettingsManager::setValidator()
85: *
86: * @author Oleg Schildt
87: */
88: protected ?Interfaces\ISettingsValidator $validator = null;
89:
90: /**
91: * Internal variable for storing the array of the settings values.
92: *
93: * @var array
94: *
95: * @author Oleg Schildt
96: */
97: protected array $settings = [];
98:
99: /**
100: * This is internal auxiliary function for checking that the settings
101: * manager is initialized correctly.
102: *
103: * @return void
104: *
105: * @throws \Exception
106: * It might throw an exception in the case of any errors:
107: *
108: * - if some parameters are missing.
109: * - if dbworker does not extend {@see \SmartFactory\DatabaseWorkers\DBWorker}.
110: *
111: * @author Oleg Schildt
112: */
113: protected function validateParameters(): void
114: {
115: if (empty($this->dbworker)) {
116: throw new \Exception("The 'dbworker' is not specified!");
117: }
118:
119: if (!$this->dbworker instanceof DBWorker) {
120: throw new \Exception(sprintf("The 'dbworker' does not extends the class '%s'!", DBWorker::class));
121: }
122:
123: if (empty($this->settings_table)) {
124: throw new \Exception("The 'settings_table' is not specified!");
125: }
126:
127: if (empty($this->settings_column)) {
128: throw new \Exception("The 'settings_column' is not specified!");
129: }
130: } // validateParameters
131:
132: /**
133: * This is internal auxiliary function for converting the settings to JSON and storing it
134: * to the target table defined by the initialization.
135: *
136: * @param array &$data
137: * The array with the settings values to be saved.
138: *
139: * @return void
140: *
141: * @throws \Exception
142: * It might throw an exception in the case of any errors:
143: *
144: * - if some parameters are missing.
145: * - if dbworker does not extend {@see \SmartFactory\DatabaseWorkers\DBWorker}.
146: * - if the query fails or if some object names are invalid.
147: *
148: * @see RuntimeSettingsManager::loadJSON()
149: *
150: * @author Oleg Schildt
151: */
152: protected function saveJSON(array &$data): void
153: {
154: $this->validateParameters();
155:
156: $json = array_to_json($data);
157:
158: // check existance
159:
160: $query = "SELECT 1 FROM " . $this->settings_table;
161:
162: $this->dbworker->execute_query($query);
163:
164: $must_insert = true;
165:
166: if ($this->dbworker->fetch_row()) {
167: $must_insert = false;
168: }
169:
170: $this->dbworker->free_result();
171:
172: $json = $this->dbworker->escape($json);
173:
174: if ($must_insert) {
175: $query = "insert into " . $this->settings_table . "(" . $this->settings_column . ")\n";
176: $query .= "values ('$json')";
177: } else {
178: $query = "update " . $this->settings_table . " set " . $this->settings_column . " = '$json'";
179: }
180:
181: $this->dbworker->execute_query($query);
182: } // saveJSON
183:
184: /**
185: * This is internal auxiliary function for loading the settings from the target table
186: * defined by the initialization.
187: *
188: * @param array &$data
189: * The target array with the settings values to be loaded.
190: *
191: * @return void
192: *
193: * @throws \Exception
194: * It might throw an exception in the case of any errors:
195: *
196: * - if some parameters are missing.
197: * - if dbworker does not extend {@see \SmartFactory\DatabaseWorkers\DBWorker}.
198: * - if the query fails or if some object names are invalid.
199: * - if the json is invalid.
200: *
201: * @see RuntimeSettingsManager::saveJSON()
202: *
203: * @author Oleg Schildt
204: */
205: protected function loadJSON(array &$data): void
206: {
207: $this->validateParameters();
208:
209: $query = "select " . $this->settings_column . " from " . $this->settings_table;
210:
211: $this->dbworker->execute_query($query);
212:
213: $json = "";
214:
215: if ($this->dbworker->fetch_row()) {
216: $json = $this->dbworker->field_by_name($this->settings_column);
217: }
218:
219: $this->dbworker->free_result();
220:
221: if (empty($json)) {
222: return;
223: }
224:
225: try {
226: json_to_array($json, $data);
227: } catch (\Throwable $ex) {
228: throw new \Exception("JSON parse error: " . $ex->getMessage());
229: }
230: } // loadJSON
231:
232: /**
233: * Initializes the settings manager parameters.
234: *
235: * @param array $parameters
236: * Settings for saving and loading as an associative array in the form key => value:
237: *
238: * - $parameters["dbworker"] - the dbworker to used for loading and storing settings.
239: *
240: * - $parameters["settings_table"] - the name of the table for the storing of the settings.
241: *
242: * - $parameters["settings_column"] - the name of the column for the storing of the settings.
243: *
244: * @return void
245: *
246: * @throws \Exception
247: * It might throw an exception in the case of any errors:
248: *
249: * - if some parameters are missing.
250: * - if dbworker does not extend {@see \SmartFactory\DatabaseWorkers\DBWorker}.
251: *
252: * @author Oleg Schildt
253: */
254: public function init(array $parameters): void
255: {
256: if (!empty($parameters["dbworker"])) {
257: $this->dbworker = $parameters["dbworker"];
258: }
259:
260: if (!empty($parameters["settings_table"])) {
261: $this->settings_table = $parameters["settings_table"];
262: }
263:
264: if (!empty($parameters["settings_column"])) {
265: $this->settings_column = $parameters["settings_column"];
266: }
267:
268: $this->validateParameters();
269: } // init
270:
271: /**
272: * Sets the validator for the settings.
273: *
274: * @param ISettingsValidator $validator
275: * The settings validator.
276: *
277: * @return void
278: *
279: * @see RuntimeSettingsManager::getValidator()
280: * @see RuntimeSettingsManager::validateSettings()
281: *
282: * @author Oleg Schildt
283: */
284: public function setValidator(ISettingsValidator $validator): void
285: {
286: $this->validator = $validator;
287: } // setValidator
288:
289: /**
290: * Returns the validator for the settings.
291: *
292: * @return ?Interfaces\ISettingsValidator
293: * Returns the validator for the settings or null if none is defined.
294: *
295: * @see RuntimeSettingsManager::setValidator()
296: * @see RuntimeSettingsManager::validateSettings()
297: *
298: * @author Oleg Schildt
299: */
300: public function getValidator(): ?Interfaces\ISettingsValidator
301: {
302: return $this->validator;
303: } // getValidator
304:
305: /**
306: * Sets the settings context.
307: *
308: * Settings might be edited not in one dialog,
309: * There can be several masks for different type of settings,
310: * or the settings can be configured in a wizard.
311: * In this case, only a subset of settings has to be
312: * saved and validated.
313: * To be able to write a flexible validator for the subsets,
314: * the $context is used. It can be - step1, step1, server_settings,
315: * db_connection_settings etc.
316: *
317: * @param string $context
318: * The settings context.
319: *
320: * @return void
321: *
322: * @see RuntimeSettingsManager::getContext()
323: *
324: * @author Oleg Schildt
325: */
326: public function setContext(string $context = "default"): void
327: {
328: $this->context = $context;
329: } // setContext
330:
331: /**
332: * Returns the current settings context.
333: *
334: * Settings might be edited not in one dialog,
335: * There can be several masks for different type of settings,
336: * or the settings can be configured in a wizard.
337: * In this case, only a subset of settings has to be
338: * saved and validated.
339: * To be able to write a flexible validator for the subsets,
340: * the $context is used. It can be - step1, step1, server_settings,
341: * db_connection_settings etc.
342: *
343: * @return string
344: * Returns the current settings context.
345: *
346: * @see RuntimeSettingsManager::setContext()
347: *
348: * @author Oleg Schildt
349: */
350: public function getContext(): string
351: {
352: return $this->context;
353: } // getContext
354:
355: /**
356: * Sets a settings parameter.
357: *
358: * @param string $name
359: * The name of the settings parameter.
360: *
361: * @param mixed $value
362: * The value of the settings parameter.
363: *
364: * @return void
365: *
366: * @throws \Exception
367: * It might throw an exception in the case of any errors:
368: *
369: * - if some parameters are missing.
370: * - if dbworker does not extend {@see \SmartFactory\DatabaseWorkers\DBWorker}.
371: * - if the query fails or if some object names are invalid.
372: *
373: * @see RuntimeSettingsManager::getParameter()
374: * @see RuntimeSettingsManager::setParameters()
375: *
376: * @author Oleg Schildt
377: */
378: public function setParameter(string $name, mixed $value): void
379: {
380: if (empty($this->settings)) {
381: $this->loadSettings();
382: }
383:
384: $this->settings[$name] = $value;
385: } // setParameter
386:
387:
388: /**
389: * Sets settings parameters from an array.
390: *
391: * @param array $parameters
392: * Array of parameters in the form key => value.
393: *
394: * @param bool $force_creation
395: * Flag which defines whether the parameter should be created
396: * if not exists. If false, only existing parameters are updated.
397: *
398: * @return void
399: *
400: * @throws \Exception
401: * It might throw an exception in the case of any errors:
402: *
403: * - if some parameters are missing.
404: * - if dbworker does not extend {@see \SmartFactory\DatabaseWorkers\DBWorker}.
405: * - if the query fails or if some object names are invalid.
406: * - if the config file is not readable.
407: *
408: * @see RuntimeSettingsManager::getParameter()
409: * @see RuntimeSettingsManager::setParameter()
410: *
411: * @author Oleg Schildt
412: */
413: public function setParameters(array $parameters, bool $force_creation = false): void
414: {
415: if (empty($this->settings)) {
416: $this->loadSettings();
417: }
418:
419: foreach ($parameters as $key => $val) {
420: if (!array_key_exists($key, $this->settings) && !$force_creation) {
421: continue;
422: }
423:
424: $this->settings[$key] = $val;
425: }
426: } // setParameters
427:
428: /**
429: * Returns the value of a settings parameter.
430: *
431: * @param string $name
432: * The name of the settings parameter.
433: *
434: * @param mixed|null $default
435: * The default value of the settings parameter if it is not set yet.
436: * The parameter is a conformable way to pre-set a parameter
437: * to a default value if its value is not set yet.
438: *
439: * @return mixed
440: * Returns the value of the settings parameter.
441: *
442: * @throws \Exception
443: * It might throw an exception in the case of any errors:
444: *
445: * - if some parameters are missing.
446: * - if dbworker does not extend {@see \SmartFactory\DatabaseWorkers\DBWorker}.
447: * - if the query fails or if some object names are invalid.
448: *
449: * @see RuntimeSettingsManager::setParameter()
450: * @see RuntimeSettingsManager::setParameters()
451: *
452: * @author Oleg Schildt
453: */
454: public function getParameter(string $name, mixed $default = null): mixed
455: {
456: if (empty($this->settings)) {
457: $this->loadSettings();
458: }
459:
460: if (!isset($this->settings[$name])) {
461: return $default;
462: }
463:
464: return $this->settings[$name];
465: } // getParameter
466:
467: /**
468: * Validates the current settings values.
469: *
470: * It should be called after settings the new values of the parameters
471: * and before their saving.
472: *
473: * @return void
474: *
475: * @throws \Exception
476: * It might throw exceptions in the case of any errors.
477: *
478: * @uses \SmartFactory\Interfaces\ISettingsValidator
479: *
480: * @see RuntimeSettingsManager::getValidator()
481: * @see RuntimeSettingsManager::setValidator()
482: *
483: * @author Oleg Schildt
484: */
485: public function validateSettings(): void
486: {
487: if (empty($this->validator)) {
488: return;
489: }
490:
491: $this->validator->validate($this, $this->context);
492: } // validateSettings
493:
494: /**
495: * Loads the settings from the target table.
496: *
497: * @return void
498: *
499: * @throws \Exception
500: * It might throw an exception in the case of any errors:
501: *
502: * - if some parameters are missing.
503: * - if dbworker does not extend {@see \SmartFactory\DatabaseWorkers\DBWorker}.
504: * - if the query fails or if some object names are invalid.
505: *
506: * @see RuntimeSettingsManager::saveSettings()
507: *
508: * @author Oleg Schildt
509: */
510: public function loadSettings(): void
511: {
512: $this->loadJSON($this->settings);
513: } // loadSettings
514:
515: /**
516: * Saves the settings from to the target table.
517: *
518: * @return void
519: *
520: * @throws \Exception
521: * It might throw an exception in the case of any errors:
522: *
523: * - if some parameters are missing.
524: * - if dbworker does not extend {@see \SmartFactory\DatabaseWorkers\DBWorker}.
525: * - if the query fails or if some object names are invalid.
526: *
527: * @see RuntimeSettingsManager::loadSettings()
528: *
529: * @author Oleg Schildt
530: */
531: public function saveSettings(): void
532: {
533: if (empty($this->settings)) {
534: $this->loadSettings();
535: }
536:
537: $this->saveJSON($this->settings);
538: } // saveSettings
539: } // RuntimeSettingsManager