Source Code: SmartFactory/RuntimeSettingsManager.php

  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