Source Code: SmartFactory/RecordsetManager.php

   1: <?php
   2: /**
   3:  * This file contains the implementation of the interface IRecordsetManager
   4:  * in the class RecordsetManager for working with record sets.
   5:  *
   6:  * @package Database
   7:  *
   8:  * @author Oleg Schildt
   9:  */
  10: 
  11: namespace SmartFactory;
  12: 
  13: use \SmartFactory\Interfaces\IRecordsetManager;
  14: 
  15: use \SmartFactory\DatabaseWorkers\DBWorker;
  16: use \SmartFactory\DatabaseWorkers\DBWorkerException;
  17: 
  18: /**
  19:  * Class for working with record sets.
  20:  *
  21:  * @uses DatabaseWorkers\DBWorker
  22:  *
  23:  * @author Oleg Schildt
  24:  */
  25: class RecordsetManager implements IRecordsetManager
  26: {
  27:     /**
  28:      * Internal variable for storing the dbworker.
  29:      *
  30:      * @var ?DatabaseWorkers\DBWorker
  31:      *
  32:      * @author Oleg Schildt
  33:      */
  34:     protected ?DatabaseWorkers\DBWorker $dbworker = null;
  35: 
  36:     /**
  37:      * Internal variable for storing the target table name.
  38:      *
  39:      * @var ?string
  40:      *
  41:      * @author Oleg Schildt
  42:      */
  43:     protected ?string $table = null;
  44: 
  45:     /**
  46:      * Internal array for storing the target fields.
  47:      *
  48:      * @var array
  49:      *
  50:      * @author Oleg Schildt
  51:      */
  52:     protected array $fields = [];
  53: 
  54:     /**
  55:      * Internal array for storing the key fields. These are the fields that are used
  56:      * to uniquely identify a record.
  57:      *
  58:      * @var array
  59:      *
  60:      * @author Oleg Schildt
  61:      */
  62:     protected array $key_fields = [];
  63: 
  64:     /**
  65:      * This is internal auxiliary function for checking that the recordset manager
  66:      * is initialized correctly.
  67:      *
  68:      * @param string $type
  69:      * The type of validation - table or query.
  70:      *
  71:      * @return void
  72:      *
  73:      * @throws \Exception
  74:      * It might throw an exception in the case of any errors:
  75:      *
  76:      * - if some parameters are missing.
  77:      * - if dbworker does not extend {@see \SmartFactory\DatabaseWorkers\DBWorker}.
  78:      * - if some parameters are not of the proper type.
  79:      *
  80:      * @author Oleg Schildt
  81:      */
  82:     protected function validateParameters(string $type): void
  83:     {
  84:         if (empty($this->dbworker)) {
  85:             throw new \Exception("The 'dbworker' is not specified!");
  86:         }
  87: 
  88:         if (!$this->dbworker instanceof DBWorker) {
  89:             throw new \Exception(sprintf("The 'dbworker' does not extends the class '%s'!", DBWorker::class));
  90:         }
  91: 
  92:         if ($type == "table" && empty($this->table)) {
  93:             throw new \Exception("The target table is not specified!");
  94:         }
  95: 
  96:         if (empty($this->fields)) {
  97:             throw new \Exception("The target fields are not specified!");
  98:         }
  99: 
 100:         if (!is_array($this->fields)) {
 101:             throw new \Exception("Field definition must be an array - field => type!");
 102:         }
 103: 
 104:         if (!empty($this->key_fields) && !is_array($this->key_fields)) {
 105:             throw new \Exception("Key field definition must be an array!");
 106:         }
 107:     } // validateParameters
 108: 
 109:     /**
 110:      * This is internal auxiliary function for converting an array to a where clause.
 111:      *
 112:      * @param array|string &$where_clause
 113:      * The where clause that should be checked. If an array of keys is passed,
 114:      * the where clause is build based on it.
 115:      *
 116:      * @return void
 117:      *
 118:      * @throws \Exception
 119:      * It might throw an exception in the case if a field for the clause wad not described
 120:      * by the initialization.
 121:      *
 122:      * @author Oleg Schildt
 123:      */
 124:     protected function checkWhereClause(array|string &$where_clause): void
 125:     {
 126:         if (!is_array($where_clause)) {
 127:             return;
 128:         }
 129: 
 130:         $tmp = "";
 131:         foreach ($where_clause as $key_field => $value) {
 132:             if (empty($this->fields[$key_field])) {
 133:                 throw new \Exception(sprintf("The field '%s' is not described!", $key_field));
 134:             }
 135: 
 136:             if (!empty($tmp)) {
 137:                 $tmp .= " and ";
 138:             }
 139: 
 140:             $tmp .= $key_field . " = " . $this->dbworker->prepare_for_query($value, $this->fields[$key_field]);
 141:         }
 142: 
 143:         $where_clause = "where " . $tmp;
 144:     } // checkWhereClause
 145: 
 146:     /**
 147:      * This is internal auxiliary function for saving a record set from an array
 148:      * with key field values as array dimensions.
 149:      *
 150:      * It expands this multidimensional
 151:      * array into the set of flat records suitable for call {@see RecordsetManager::saveRecord()}.
 152:      *
 153:      * @param array $subarray
 154:      * The current subarray ro be processed.
 155:      *
 156:      * @param array $key_fields
 157:      * The array of the key fields. These are the fields that are used
 158:      * to uniquely identify a record.
 159:      *
 160:      * @param array &$parent_values
 161:      * The array of the values of the foreign keys
 162:      * in the form "field_name" => "value".
 163:      *
 164:      * @param array &$record
 165:      * The array where the resulting flat record is built.
 166:      *
 167:      * @param string $identity_field
 168:      * The name of the identity field if exists. If the identity field is specified
 169:      * and the record does not exist yet in the table, the source array is extended
 170:      * with a pair "identity field" => "identity value" issued by the database by this
 171:      * insert operation.
 172:      *
 173:      * @return void
 174:      *
 175:      * @throws \Exception
 176:      * It might throw an exception in the case of any errors:
 177:      *
 178:      * - if some parameters are missing.
 179:      * - if dbworker does not extend {@see \SmartFactory\DatabaseWorkers\DBWorker}.
 180:      * - if some parameters are not of the proper type.
 181:      * - if the query fails or if some object names are invalid.
 182:      *
 183:      * @author Oleg Schildt
 184:      */
 185:     protected function processSubarray(array $subarray, array $key_fields, array &$parent_values, array &$record, string $identity_field): void
 186:     {
 187:         $current_key = array_shift($key_fields);
 188: 
 189:         foreach ($subarray as $key => $value) {
 190:             if (!empty($current_key)) {
 191:                 if (!empty($parent_values[$current_key])) {
 192:                     $record[$current_key] = $parent_values[$current_key];
 193:                 } else {
 194:                     $record[$current_key] = $key;
 195:                 }
 196: 
 197:                 $this->processSubarray($value, $key_fields, $parent_values, $record, $identity_field);
 198:             } else {
 199:                 $record[$key] = $value;
 200:             }
 201:         }
 202: 
 203:         if (empty($current_key)) {
 204:             $where_clause = [];
 205: 
 206:             foreach ($this->key_fields as $field) {
 207:                 if (!empty($record[$field])) {
 208:                     $where_clause[$field] = $record[$field];
 209:                 }
 210:             }
 211: 
 212:             $this->saveRecord($record, $where_clause, $identity_field);
 213:         }
 214:     } // processSubarray
 215: 
 216:     /**
 217:      * Sets the dbworker to be used for working with the database.
 218:      *
 219:      * @param DBWorker $dbworker
 220:      * The dbworker to be used for working with the database.
 221:      *
 222:      * @return void
 223:      *
 224:      * @see RecordsetManager::getDBWorker()
 225:      *
 226:      * @author Oleg Schildt
 227:      */
 228:     public function setDBWorker(DBWorker $dbworker): void
 229:     {
 230:         $this->dbworker = $dbworker;
 231:     } // setDBWorker
 232: 
 233:     /**
 234:      * Returns the dbworker to be used for working with the database.
 235:      *
 236:      * @return ?\SmartFactory\DatabaseWorkers\DBWorker
 237:      * Returns the dbworker to be used for working with the database.
 238:      *
 239:      * @throws DBWorkerException
 240:      * It might throw exceptions in the case of any errors.
 241:      *
 242:      * @author Oleg Schildt
 243:      * @see RecordsetManager::getDBWorker()
 244:      *
 245:      */
 246:     public function getDBWorker(): ?\SmartFactory\DatabaseWorkers\DBWorker
 247:     {
 248:         $this->dbworker?->connect();
 249: 
 250:         return $this->dbworker;
 251:     } // getDBWorker
 252: 
 253:     /**
 254:      * Defines the field mappings for working with record sets based on a table.
 255:      *
 256:      * @param string $table
 257:      * The name of the table.
 258:      *
 259:      * @param array $fields
 260:      * The array of fields in the form "field name" => "field type".
 261:      *
 262:      * @param array $key_fields
 263:      * The array of key fields. These are the fields that are used
 264:      * to uniquely identify a record.
 265:      *
 266:      * @return void
 267:      *
 268:      * @throws \Exception
 269:      * It might throw an exception in the case of any errors:
 270:      *
 271:      * - if some parameters are missing.
 272:      * - if dbworker does not extend {@see \SmartFactory\DatabaseWorkers\DBWorker}.
 273:      * - if the query fails or if some object names are invalid.
 274:      *
 275:      * @see IRecordsetManager::describeTableFieldsQuery()
 276:      *
 277:      * @author Oleg Schildt
 278:      */
 279:     public function describeTableFields(string $table, array $fields, array $key_fields): void
 280:     {
 281:         $this->table = $table;
 282:         $this->fields = $fields;
 283: 
 284:         if (is_array($key_fields)) {
 285:             $this->key_fields = $key_fields;
 286:         } else {
 287:             $this->key_fields = [];
 288:         }
 289: 
 290:         $this->validateParameters("table");
 291:     } // describeTableFields
 292: 
 293:     /**
 294:      * Defines the field mappings for working with record sets based on a query.
 295:      *
 296:      * @param array $fields
 297:      * The array of fields in the form "field name" => "field type".
 298:      *
 299:      * @param array $key_fields
 300:      * The array of key fields. These are the fields that are used
 301:      * to uniquely identify a record.
 302:      *
 303:      * @return void
 304:      *
 305:      * @throws \Exception
 306:      * It might throw an exception in the case of any errors:
 307:      *
 308:      * - if some parameters are missing.
 309:      * - if dbworker does not extend {@see \SmartFactory\DatabaseWorkers\DBWorker}.
 310:      * - if the query fails or if some object names are invalid.
 311:      *
 312:      * @see IRecordsetManager::describeTableFields()
 313:      *
 314:      * @author Oleg Schildt
 315:      */
 316:     public function describeTableFieldsQuery(array $fields, array $key_fields): void
 317:     {
 318:         $this->fields = $fields;
 319: 
 320:         if (is_array($key_fields)) {
 321:             $this->key_fields = $key_fields;
 322:         } else {
 323:             $this->key_fields = [];
 324:         }
 325: 
 326:         $this->validateParameters("query");
 327:     } // describeTableFieldsQuery
 328: 
 329:     /**
 330:      * Deletes records by a given where clause.
 331:      *
 332:      * @param array|string $where_clause
 333:      * The where clause that should restrict the result. If an array of keys is passed,
 334:      * the where clause is build automatically based on it.
 335:      *
 336:      * @return void
 337:      *
 338:      * @throws \Exception
 339:      * It might throw an exception in the case of any errors:
 340:      *
 341:      * - if some parameters are missing.
 342:      * - if dbworker does not extend {@see \SmartFactory\DatabaseWorkers\DBWorker}.
 343:      * - if some parameters are not of the proper type.
 344:      * - if the query fails or if some object names are invalid.
 345:      *
 346:      * @see  RecordsetManager::saveRecord()
 347:      * @see  RecordsetManager::deleteRecordsQuery()
 348:      *
 349:      * @uses \SmartFactory\DatabaseWorkers\DBWorker
 350:      *
 351:      * @author Oleg Schildt
 352:      */
 353:     public function deleteRecords(array|string $where_clause): void
 354:     {
 355:         $this->validateParameters("table");
 356: 
 357:         $this->checkWhereClause($where_clause);
 358: 
 359:         $query = "delete from " . $this->table . "\n";
 360: 
 361:         $query .= $where_clause;
 362: 
 363:         $this->deleteRecordsQuery($query);
 364:     } // deleteRecords
 365: 
 366:     /**
 367:      * Deletes records by a given query.
 368:      *
 369:      * @param string $query
 370:      * The query to be used.
 371:      *
 372:      * @return void
 373:      *
 374:      * @throws \Exception
 375:      * It might throw an exception in the case of any errors:
 376:      *
 377:      * - if some parameters are missing.
 378:      * - if dbworker does not extend {@see \SmartFactory\DatabaseWorkers\DBWorker}.
 379:      * - if some parameters are not of the proper type.
 380:      * - if the query fails or if some object names are invalid.
 381:      *
 382:      * @see  RecordsetManager::deleteRecords()
 383:      *
 384:      * @uses \SmartFactory\DatabaseWorkers\DBWorker
 385:      *
 386:      * @author Oleg Schildt
 387:      */
 388:     public function deleteRecordsQuery(string $query): void
 389:     {
 390:         $this->validateParameters("query");
 391: 
 392:         $this->dbworker->connect();
 393: 
 394:         $this->dbworker->execute_query($query);
 395:     } // deleteRecordsQuery
 396: 
 397:     /**
 398:      * Loads a record into an array in the form "field_name" => "value" based on a table.
 399:      *
 400:      * @param array &$record
 401:      * The target array where the data should be loaded.
 402:      *
 403:      * @param array|string $where_clause
 404:      * The where clause that should restrict the result to one record. If an array of keys is passed,
 405:      * the where clause is build automatically based on it.
 406:      *
 407:      * @return void
 408:      *
 409:      * @throws \Exception
 410:      * It might throw an exception in the case of any errors:
 411:      *
 412:      * - if some parameters are missing.
 413:      * - if dbworker does not extend {@see \SmartFactory\DatabaseWorkers\DBWorker}.
 414:      * - if some parameters are not of the proper type.
 415:      * - if the query fails or if some object names are invalid.
 416:      *
 417:      * @see  RecordsetManager::saveRecord()
 418:      * @see  RecordsetManager::loadRecordSet()
 419:      * @see  RecordsetManager::loadRecordQuery()
 420:      *
 421:      * @uses \SmartFactory\DatabaseWorkers\DBWorker
 422:      *
 423:      * @author Oleg Schildt
 424:      */
 425:     public function loadRecord(array &$record, array|string $where_clause): void
 426:     {
 427:         $this->validateParameters("table");
 428: 
 429:         $this->checkWhereClause($where_clause);
 430: 
 431:         $query = "select\n";
 432: 
 433:         $query .= implode(", ", array_keys($this->fields)) . "\n";
 434: 
 435:         $query .= "from " . $this->table . "\n";
 436: 
 437:         $query .= $where_clause;
 438: 
 439:         $this->loadRecordQuery($record, $query);
 440:     } // loadRecord
 441: 
 442:     /**
 443:      * Loads a record into an array in the form "field_name" => "value".
 444:      *
 445:      * @param array &$record
 446:      * The target array where the data should be loaded.
 447:      *
 448:      * @param string $query
 449:      * The query to be used.
 450:      *
 451:      * @return void
 452:      *
 453:      * @throws \Exception
 454:      * It might throw an exception in the case of any errors:
 455:      *
 456:      * - if some parameters are missing.
 457:      * - if dbworker does not extend {@see \SmartFactory\DatabaseWorkers\DBWorker}.
 458:      * - if some parameters are not of the proper type.
 459:      * - if the query fails or if some object names are invalid.
 460:      *
 461:      * @see  RecordsetManager::loadRecord()
 462:      * @see  RecordsetManager::loadRecordSetQuery()
 463:      *
 464:      * @uses \SmartFactory\DatabaseWorkers\DBWorker
 465:      *
 466:      * @author Oleg Schildt
 467:      */
 468:     public function loadRecordQuery(array &$record, string $query): void
 469:     {
 470:         $this->validateParameters("query");
 471: 
 472:         $this->dbworker->connect();
 473: 
 474:         $this->dbworker->execute_query($query);
 475: 
 476:         if ($this->dbworker->fetch_row()) {
 477:             foreach ($this->fields as $field => $type) {
 478:                 $record[$field] = $this->dbworker->field_by_name($field, $type);
 479:             }
 480:         }
 481: 
 482:         $this->dbworker->free_result();
 483:     } // loadRecordQuery
 484: 
 485:     /**
 486:      * Loads records into an array in the form
 487:      *
 488:      * $records["key_field1"]["key_field2"]["key_fieldN"]["field_name"] = "value".
 489:      *
 490:      * baed on a table.
 491:      *
 492:      * @param array &$records
 493:      * The target array where the data should be loaded.
 494:      *
 495:      * @param array|string $where_clause
 496:      * The where clause that should restrict the result. If an array of keys is passed,
 497:      * the where clause is build automatically based on it.
 498:      *
 499:      * @param string $order_clause
 500:      * The order clause to sort the results.
 501:      *
 502:      * @param int $limit
 503:      * The limit how many records should be loaded. 0 for unlimited.
 504:      *
 505:      * @return void
 506:      *
 507:      * @throws \Exception
 508:      * It might throw an exception in the case of any errors:
 509:      *
 510:      * - if some parameters are missing.
 511:      * - if dbworker does not extend {@see \SmartFactory\DatabaseWorkers\DBWorker}.
 512:      * - if some parameters are not of the proper type.
 513:      * - if the query fails or if some object names are invalid.
 514:      *
 515:      * @see  RecordsetManager::loadRecord()
 516:      * @see  RecordsetManager::saveRecordSet()
 517:      * @see  RecordsetManager::loadRecordSetQuery()
 518:      *
 519:      * @uses \SmartFactory\DatabaseWorkers\DBWorker
 520:      *
 521:      * @author Oleg Schildt
 522:      */
 523:     public function loadRecordSet(array &$records, array|string $where_clause, string $order_clause = "", int $limit = 0): void
 524:     {
 525:         $this->validateParameters("table");
 526: 
 527:         $this->checkWhereClause($where_clause);
 528: 
 529:         $query = $this->dbworker->build_select_query($this->table, array_keys($this->fields), $where_clause, $order_clause, $limit);
 530: 
 531:         $this->loadRecordSetQuery($records, $query);
 532:     } // loadRecordSet
 533: 
 534:     /**
 535:      * Loads records into an array in the form
 536:      *
 537:      * $records["key_field1"]["key_field2"]["key_fieldN"]["field_name"] = "value".
 538:      *
 539:      * baed on a query.
 540:      *
 541:      * @param array &$records
 542:      * The target array where the data should be loaded.
 543:      *
 544:      * @param string $query
 545:      * The query to be used.
 546:      *
 547:      * @return void
 548:      *
 549:      * @throws \Exception
 550:      * It might throw an exception in the case of any errors:
 551:      *
 552:      * - if some parameters are missing.
 553:      * - if dbworker does not extend {@see \SmartFactory\DatabaseWorkers\DBWorker}.
 554:      * - if some parameters are not of the proper type.
 555:      * - if the query fails or if some object names are invalid.
 556:      *
 557:      * @see  RecordsetManager::loadRecordSet()
 558:      * @see  RecordsetManager::loadRecordQuery()
 559:      *
 560:      * @uses \SmartFactory\DatabaseWorkers\DBWorker
 561:      *
 562:      * @author Oleg Schildt
 563:      */
 564:     public function loadRecordSetQuery(array &$records, string $query): void
 565:     {
 566:         $this->validateParameters("query");
 567: 
 568:         $this->dbworker->connect();
 569: 
 570:         $this->dbworker->execute_query($query);
 571: 
 572:         while ($this->dbworker->fetch_row()) {
 573:             $dimensions = [];
 574:             $row = [];
 575: 
 576:             foreach ($this->fields as $field => $type) {
 577:                 $val = $this->dbworker->field_by_name($field, $type);
 578: 
 579:                 $row[$field] = $val;
 580: 
 581:                 if (in_array($field, $this->key_fields)) {
 582:                     $dimensions[$field] = $val;
 583:                 }
 584:             }
 585: 
 586:             if (!empty($dimensions)) {
 587:                 $reference = &$records;
 588: 
 589:                 foreach ($dimensions as $dval) {
 590:                     if (empty($reference[$dval])) {
 591:                         $reference[$dval] = [];
 592:                     }
 593:                     $reference = &$reference[$dval];
 594:                 }
 595: 
 596:                 $reference = $row;
 597: 
 598:                 unset($reference);
 599:             } else {
 600:                 $records[] = $row;
 601:             }
 602:         }
 603: 
 604:         $this->dbworker->free_result();
 605:     } // loadRecordSetQuery
 606: 
 607:     /**
 608:      * Saves a record from an array in the form "field_name" => "value" into the table.
 609:      *
 610:      * @param array &$record
 611:      * The source array with the data to be saved.
 612:      *
 613:      * @param array|string $where_clause
 614:      * The where clause that should be used to define whether a record should be inserted or updated. If an array of keys is passed,
 615:      * the where clause is build automatically based on it.
 616:      *
 617:      * @param string $identity_field
 618:      * The name of the identity field if exists. If the identity field is specified
 619:      * and the record does not exist yet in the table, the source array is extended
 620:      * with a pair "identity field" => "identity value" issued by the database by this
 621:      * insert operation.
 622:      *
 623:      * @return void
 624:      *
 625:      * @throws \Exception
 626:      * It might throw an exception in the case of any errors:
 627:      *
 628:      * - if some parameters are missing.
 629:      * - if dbworker does not extend {@see \SmartFactory\DatabaseWorkers\DBWorker}.
 630:      * - if some parameters are not of the proper type.
 631:      * - if the query fails or if some object names are invalid.
 632:      *
 633:      * @see  RecordsetManager::loadRecord()
 634:      * @see  RecordsetManager::saveRecordSet()
 635:      *
 636:      * @uses DatabaseWorkers\DBWorker
 637:      *
 638:      * @author Oleg Schildt
 639:      */
 640:     public function saveRecord(array &$record, array|string $where_clause, string $identity_field = ""): void
 641:     {
 642:         $this->validateParameters("table");
 643: 
 644:         $this->dbworker->connect();
 645: 
 646:         $must_insert = false;
 647: 
 648:         // key_fields not specified - always insert
 649:         // identity firld exists but its value is not specified - always insert
 650:         if (empty($this->key_fields) || empty($where_clause)) {
 651:             $must_insert = true;
 652:         } else {
 653:             // check existence
 654: 
 655:             $query = "select\n";
 656:             $query .= "1\n";
 657:             $query .= "from " . $this->table . "\n";
 658: 
 659:             $this->checkWhereClause($where_clause);
 660: 
 661:             $query .= $where_clause;
 662: 
 663:             $this->dbworker->execute_query($query);
 664: 
 665:             if (!$this->dbworker->fetch_row()) {
 666:                 $must_insert = true;
 667:             }
 668: 
 669:             $this->dbworker->free_result();
 670:         }
 671: 
 672:         // saving
 673: 
 674:         $update_string = "";
 675:         $insert_fields = "";
 676:         $insert_values = "";
 677: 
 678:         foreach ($this->fields as $field => $type) {
 679:             // if autoincrement
 680:             if ($must_insert && $field == $identity_field) {
 681:                 continue;
 682:             }
 683: 
 684:             // The value for the field is not passed in the record,
 685:             // skip it
 686:             if (!array_key_exists($field, $record)) {
 687:                 continue;
 688:             }
 689: 
 690:             $value = $this->dbworker->prepare_for_query($record[$field] ?? "", $this->fields[$field] ?? "");
 691: 
 692:             $update_string .= $field . " = " . $value . ",\n";
 693:             $insert_fields .= $field . ", ";
 694:             $insert_values .= $value . ", ";
 695:         }
 696: 
 697:         if ($must_insert) {
 698:             $query = "insert into " . $this->table . " (" . trim($insert_fields, ", ") . ")\n";
 699:             $query .= "values (" . trim($insert_values, ", ") . ")\n";
 700: 
 701:             $this->dbworker->execute_query($query);
 702:         } elseif (!empty($update_string)) {
 703:             $query = "update " . $this->table . " set\n";
 704:             $query .= trim($update_string, ",\n") . "\n";
 705:             $query .= $where_clause;
 706: 
 707:             $this->dbworker->execute_query($query);
 708:         }
 709: 
 710:         if ($must_insert && !empty($identity_field)) {
 711:             $record[$identity_field] = $this->dbworker->insert_id();
 712:         }
 713:     } // saveRecord
 714: 
 715:     /**
 716:      * Saves records from an array in the form
 717:      * $records["key_field1"]["key_field2"]["key_fieldN"]["field_name"] = "value" into the table.
 718:      *
 719:      * @param array $records
 720:      * The source array with the data to be saved.
 721:      *
 722:      * @param array $parent_values
 723:      * If this recordset is a child subset of data to be saved, you can set the values of the foreign keys
 724:      * in the form "field_name" => "value".
 725:      *
 726:      * @param string $identity_field
 727:      * The name of the identity field if exists. If the identity field is specified
 728:      * and the record does not exist yet in the table, the source array is extended
 729:      * with a pair "identity field" => "identity value" issued by the database by this
 730:      * insert operation.
 731:      *
 732:      * @return void
 733:      *
 734:      * @throws \Exception
 735:      * It might throw an exception in the case of any errors:
 736:      *
 737:      * - if some parameters are missing.
 738:      * - if dbworker does not extend {@see \SmartFactory\DatabaseWorkers\DBWorker}.
 739:      * - if some parameters are not of the proper type.
 740:      * - if the query fails or if some object names are invalid.
 741:      *
 742:      * @see  RecordsetManager::loadRecordSet()
 743:      * @see  RecordsetManager::saveRecord()
 744:      *
 745:      * @uses DatabaseWorkers\DBWorker
 746:      *
 747:      * @author Oleg Schildt
 748:      */
 749:     public function saveRecordSet(array $records, array $parent_values = [], string $identity_field = ""): void
 750:     {
 751:         $this->validateParameters("table");
 752: 
 753:         $this->dbworker->connect();
 754: 
 755:         $key_fields = $this->key_fields;
 756:         $current_key = array_shift($key_fields);
 757: 
 758:         foreach ($records as $key => $value) {
 759:             $record = [];
 760: 
 761:             if (!empty($parent_values[$current_key])) {
 762:                 $record[$current_key] = $parent_values[$current_key];
 763:             } else {
 764:                 $record[$current_key] = $key;
 765:             }
 766: 
 767:             $this->processSubarray($value, $key_fields, $parent_values, $record, $identity_field);
 768:         }
 769:     } // saveRecordSet
 770: 
 771:     /**
 772:      * Counts records based on the where clause.
 773:      *
 774:      * @param array|string $where_clause
 775:      * The where clause that should restrict the result. If an array of keys is passed,
 776:      * the where clause is build automatically based on it.
 777:      *
 778:      * @return int
 779:      * Returns the number of records.
 780:      *
 781:      * @throws \Exception
 782:      * It might throw exceptions in the case of any errors.
 783:      *
 784:      * @uses \SmartFactory\DatabaseWorkers\DBWorker
 785:      *
 786:      * @author Oleg Schildt
 787:      * @see  IRecordsetManager::countRecordsQuery()
 788:      *
 789:      */
 790:     public function countRecords(array|string $where_clause): int
 791:     {
 792:         $this->validateParameters("table");
 793: 
 794:         $this->checkWhereClause($where_clause);
 795: 
 796:         $query = "select\n";
 797: 
 798:         $query .= "count(*)\n";
 799: 
 800:         $query .= "from " . $this->table . "\n";
 801: 
 802:         if (!empty($where_clause)) {
 803:             $query .= $where_clause . "\n";
 804:         }
 805: 
 806:         return $this->countRecordsQuery($query);
 807:     } // countRecords
 808: 
 809:     /**
 810:      * Counts records based on the query.
 811:      *
 812:      * @param string $query
 813:      * The query to be used.
 814:      *
 815:      * @return int
 816:      * Returns the number of records.
 817:      *
 818:      * @throws \Exception
 819:      * It might throw exceptions in the case of any errors.
 820:      *
 821:      * @see  IRecordsetManager::countRecords()
 822:      *
 823:      * @uses \SmartFactory\DatabaseWorkers\DBWorker
 824:      *
 825:      * @author Oleg Schildt
 826:      */
 827:     public function countRecordsQuery(string $query): int
 828:     {
 829:         $this->validateParameters("query");
 830: 
 831:         $this->dbworker->connect();
 832: 
 833:         $cnt = 0;
 834: 
 835:         $this->dbworker->execute_query($query);
 836: 
 837:         if ($this->dbworker->fetch_row()) {
 838:             $cnt = $this->dbworker->field_by_num(0);
 839:         }
 840: 
 841:         $this->dbworker->free_result();
 842: 
 843:         return $cnt;
 844:     } // countRecordsQuery
 845: 
 846:     /**
 847:      * Starts the translation.
 848:      *
 849:      * @return void
 850:      *
 851:      * @throws DBWorkerException
 852:      * It might throw an exception in the case of any errors.
 853:      *
 854:      * @see RecordsetManager::commit_transaction()
 855:      * @see RecordsetManager::rollback_transaction()
 856:      *
 857:      * @author Oleg Schildt
 858:      */
 859:     public function start_transaction(): void
 860:     {
 861:         $this->dbworker->connect();
 862: 
 863:         $this->dbworker->start_transaction();
 864:     }
 865: 
 866:     /**
 867:      * Commits the translation.
 868:      *
 869:      * @return void
 870:      *
 871:      * @throws DBWorkerException
 872:      * It might throw an exception in the case of any errors.
 873:      *
 874:      * @see RecordsetManager::start_transaction()
 875:      * @see RecordsetManager::rollback_transaction()
 876:      *
 877:      * @author Oleg Schildt
 878:      */
 879:     public function commit_transaction(): void
 880:     {
 881:         $this->dbworker->connect();
 882: 
 883:         $this->dbworker->commit_transaction();
 884:     }
 885: 
 886:     /**
 887:      * Rolls back the translation.
 888:      *
 889:      * @return void
 890:      *
 891:      * @throws DBWorkerException
 892:      * It might throw an exception in the case of any errors.
 893:      *
 894:      * @see RecordsetManager::start_transaction()
 895:      * @see RecordsetManager::commit_transaction()
 896:      *
 897:      * @author Oleg Schildt
 898:      */
 899:     public function rollback_transaction(): void
 900:     {
 901:         $this->dbworker->connect();
 902: 
 903:         $this->dbworker->rollback_transaction();
 904:     }
 905: 
 906:     /**
 907:      * Escapes the string so that it can be used in the query without causing an error.
 908:      *
 909:      * @param string $str
 910:      * The string to be escaped.
 911:      *
 912:      * @return string
 913:      * Returns the escaped string.
 914:      *
 915:      * @see RecordsetManager::format_date()
 916:      * @see RecordsetManager::format_datetime()
 917:      * @see RecordsetManager::quotes_or_null()
 918:      * @see RecordsetManager::number_or_null()
 919:      *
 920:      * @author Oleg Schildt
 921:      */
 922:     public function escape(string $str): string
 923:     {
 924:         return $this->dbworker->escape($str);
 925:     }
 926: 
 927:     /**
 928:      * Escapes the string so that it can be used in the query without causing an error or returns the sstring NULL if the string is empty.
 929:      *
 930:      * @param string $str
 931:      * The string to be escaped.
 932:      *
 933:      * @return string
 934:      * Returns the escaped string.
 935:      *
 936:      * @see RecordsetManager::escape()
 937:      * @see RecordsetManager::format_date()
 938:      * @see RecordsetManager::format_datetime()
 939:      * @see RecordsetManager::number_or_null()
 940:      *
 941:      * @author Oleg Schildt
 942:      */
 943:     function quotes_or_null(string $str): string
 944:     {
 945:         return $this->dbworker->quotes_or_null($str);
 946:     }
 947: 
 948:     /**
 949:      * Checks that the value is a number and returns it, or returns the string NULL if the value is empty.
 950:      *
 951:      * @param string $str
 952:      * The string to be escaped.
 953:      *
 954:      * @return string
 955:      * Returns the escaped string.
 956:      *
 957:      * @throws DBWorkerException
 958:      * It might throw an exception in the case of any errors.
 959:      *
 960:      * @see RecordsetManager::escape()
 961:      * @see RecordsetManager::format_date()
 962:      * @see RecordsetManager::format_datetime()
 963:      * @see RecordsetManager::quotes_or_null()
 964:      *
 965:      * @author Oleg Schildt
 966:      */
 967:     function number_or_null(string $str): string
 968:     {
 969:         return $this->dbworker->number_or_null($str);
 970:     }
 971: 
 972:     /**
 973:      * Formats the date to a string compatible for the corresponding database.
 974:      *
 975:      * @param int $date
 976:      * The date value as timestamp.
 977:      *
 978:      * @return string
 979:      * Returns the string representation of the date compatible for the corresponding database.
 980:      *
 981:      * @see RecordsetManager::escape()
 982:      * @see RecordsetManager::format_datetime()
 983:      * @see RecordsetManager::quotes_or_null()
 984:      * @see RecordsetManager::number_or_null()
 985:      *
 986:      * @author Oleg Schildt
 987:      */
 988:     public function format_date(int $date): string
 989:     {
 990:         return $this->dbworker->format_date($date);
 991:     }
 992: 
 993:     /**
 994:      * Formats the date/time to a string compatible for the corresponding database.
 995:      *
 996:      * @param int $datetime
 997:      * The date/time value as timestamp.
 998:      *
 999:      * @return string
1000:      * Returns the string representation of the date/time compatible for the corresponding database.
1001:      *
1002:      * @see RecordsetManager::escape()
1003:      * @see RecordsetManager::format_date()
1004:      * @see RecordsetManager::quotes_or_null()
1005:      * @see RecordsetManager::number_or_null()
1006:      *
1007:      * @author Oleg Schildt
1008:      */
1009:     public function format_datetime(int $datetime): string
1010:     {
1011:         return $this->dbworker->format_datetime($datetime);
1012:     }
1013: } // RecordsetManager