Completed
Push — master ( 4e98a1...a4a40b )
by Lars
05:38 queued 10s
created

DB::set_convert_null_to_empty_string()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 3
cts 3
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace voku\db;
6
7
use voku\cache\Cache;
8
use voku\db\exceptions\DBConnectException;
9
use voku\db\exceptions\DBGoneAwayException;
10
use voku\db\exceptions\QueryException;
11
use voku\helper\UTF8;
12
13
/**
14
 * DB: This class can handle DB queries via MySQLi.
15
 *
16
 * @package voku\db
17
 */
18
final class DB
19
{
20
21
  /**
22
   * @var int
23
   */
24
  public $query_count = 0;
25
26
  /**
27
   * @var \mysqli|null
28
   */
29
  private $link;
30
31
  /**
32
   * @var bool
33
   */
34
  private $connected = false;
35
36
  /**
37
   * @var array
38
   */
39
  private $mysqlDefaultTimeFunctions;
40
41
  /**
42
   * @var string
43
   */
44
  private $hostname = '';
45
46
  /**
47
   * @var string
48
   */
49
  private $username = '';
50
51
  /**
52
   * @var string
53
   */
54
  private $password = '';
55
56
  /**
57
   * @var string
58
   */
59
  private $database = '';
60
61
  /**
62
   * @var int
63
   */
64
  private $port = 3306;
65
66
  /**
67
   * @var string
68
   */
69
  private $charset = 'utf8';
70
71
  /**
72
   * @var string
73
   */
74
  private $socket = '';
75
76
  /**
77
   * @var bool
78
   */
79
  private $session_to_db = false;
80
81
  /**
82
   * @var bool
83
   */
84
  private $_in_transaction = false;
85
86
  /**
87
   * @var bool
88
   */
89
  private $_convert_null_to_empty_string = false;
90
91
  /**
92
   * @var bool
93
   */
94
  private $_ssl = false;
95
96
  /**
97
   * The path name to the key file
98
   *
99
   * @var string
100
   */
101
  private $_clientkey;
102
103
  /**
104
   * The path name to the certificate file
105
   *
106
   * @var string
107
   */
108
  private $_clientcert;
109
110
  /**
111
   * The path name to the certificate authority file
112
   *
113
   * @var string
114
   */
115
  private $_cacert;
116
117
  /**
118
   * @var Debug
119
   */
120
  private $_debug;
121
122
  /**
123
   * @var null|\Doctrine\DBAL\Connection
124
   */
125
  private $_doctrine_connection;
126
127
  /**
128
   * __construct()
129
   *
130
   * @param string $hostname
131
   * @param string $username
132
   * @param string $password
133
   * @param string $database
134
   * @param int    $port
135
   * @param string $charset
136
   * @param bool   $exit_on_error         <p>Throw a 'Exception' when a query failed, otherwise it will return 'false'.
137
   *                                      Use false to disable it.</p>
138
   * @param bool   $echo_on_error         <p>Echo the error if "checkForDev()" returns true.
139
   *                                      Use false to disable it.</p>
140
   * @param string $logger_class_name
141
   * @param string $logger_level          <p>'TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL'</p>
142
   * @param array  $extra_config          <p>
143
   *                                      'session_to_db' => bool<br>
144
   *                                      'socket'        => 'string (path)'<br>
145
   *                                      'ssl'           => bool<br>
146
   *                                      'clientkey'     => 'string (path)'<br>
147
   *                                      'clientcert'    => 'string (path)'<br>
148
   *                                      'cacert'        => 'string (path)'<br>
149
   *                                      </p>
150
   */
151 65
  private function __construct(string $hostname, string $username, string $password, string $database, $port, string $charset, bool $exit_on_error, bool $echo_on_error, string $logger_class_name, string $logger_level, array $extra_config = [])
152
  {
153 65
    $this->_debug = new Debug($this);
154
155 65
    $this->_loadConfig(
156 65
        $hostname,
157 65
        $username,
158 65
        $password,
159 65
        $database,
160 65
        $port,
161 65
        $charset,
162 65
        $exit_on_error,
163 65
        $echo_on_error,
164 65
        $logger_class_name,
165 65
        $logger_level,
166 65
        $extra_config
167
    );
168
169 62
    $this->connect();
170
171 59
    $this->mysqlDefaultTimeFunctions = [
172
      // Returns the current date.
173
      'CURDATE()',
174
      // CURRENT_DATE	| Synonyms for CURDATE()
175
      'CURRENT_DATE()',
176
      // CURRENT_TIME	| Synonyms for CURTIME()
177
      'CURRENT_TIME()',
178
      // CURRENT_TIMESTAMP | Synonyms for NOW()
179
      'CURRENT_TIMESTAMP()',
180
      // Returns the current time.
181
      'CURTIME()',
182
      // Synonym for NOW()
183
      'LOCALTIME()',
184
      // Synonym for NOW()
185
      'LOCALTIMESTAMP()',
186
      // Returns the current date and time.
187
      'NOW()',
188
      // Returns the time at which the function executes.
189
      'SYSDATE()',
190
      // Returns a UNIX timestamp.
191
      'UNIX_TIMESTAMP()',
192
      // Returns the current UTC date.
193
      'UTC_DATE()',
194
      // Returns the current UTC time.
195
      'UTC_TIME()',
196
      // Returns the current UTC date and time.
197
      'UTC_TIMESTAMP()',
198
    ];
199 59
  }
200
201
  /**
202
   * Prevent the instance from being cloned.
203
   *
204
   * @return void
205
   */
206
  private function __clone()
207
  {
208
  }
209
210
  /**
211
   * __destruct
212
   */
213
  public function __destruct()
214
  {
215
    // close the connection only if we don't save PHP-SESSION's in DB
216
    if ($this->session_to_db === false) {
217
      $this->close();
218
    }
219
  }
220
221
  /**
222
   * @param null|string $sql
223
   * @param array       $bindings
224
   *
225
   * @return bool|int|Result|DB           <p>
226
   *                                      "DB" by "$sql" === null<br />
227
   *                                      "Result" by "<b>SELECT</b>"-queries<br />
228
   *                                      "int" (insert_id) by "<b>INSERT / REPLACE</b>"-queries<br />
229
   *                                      "int" (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
230
   *                                      "true" by e.g. "DROP"-queries<br />
231
   *                                      "false" on error
232
   *                                      </p>
233
   */
234 3
  public function __invoke(string $sql = null, array $bindings = [])
235
  {
236 3
    return null !== $sql ? $this->query($sql, $bindings) : $this;
237
  }
238
239
  /**
240
   * __wakeup
241
   *
242
   * @return void
243
   */
244 3
  public function __wakeup()
245
  {
246 3
    $this->reconnect();
247 3
  }
248
249
  /**
250
   * Load the config from the constructor.
251
   *
252
   * @param string $hostname
253
   * @param string $username
254
   * @param string $password
255
   * @param string $database
256
   * @param int    $port                  <p>default is (int)3306</p>
257
   * @param string $charset               <p>default is 'utf8' or 'utf8mb4' (if supported)</p>
258
   * @param bool   $exit_on_error         <p>Throw a 'Exception' when a query failed, otherwise it will return 'false'.
259
   *                                      Use false to disable it.</p>
260
   * @param bool   $echo_on_error         <p>Echo the error if "checkForDev()" returns true.
261
   *                                      Use false to disable it.</p>
262
   * @param string $logger_class_name
263
   * @param string $logger_level
264
   * @param array  $extra_config          <p>
265
   *                                      'session_to_db' => false|true<br>
266
   *                                      'socket' => 'string (path)'<br>
267
   *                                      'ssl' => 'bool'<br>
268
   *                                      'clientkey' => 'string (path)'<br>
269
   *                                      'clientcert' => 'string (path)'<br>
270
   *                                      'cacert' => 'string (path)'<br>
271
   *                                      </p>
272
   *
273
   * @return bool
274
   */
275 65
  private function _loadConfig(
276
      string $hostname,
277
      string $username,
278
      string $password,
279
      string $database,
280
      $port,
281
      string $charset,
282
      bool $exit_on_error,
283
      bool $echo_on_error,
284
      string $logger_class_name,
285
      string $logger_level,
286
      array $extra_config = []
287
  ): bool
288
  {
289 65
    $this->hostname = $hostname;
290 65
    $this->username = $username;
291 65
    $this->password = $password;
292 65
    $this->database = $database;
293
294 65
    if ($charset) {
295 65
      $this->charset = $charset;
296
    }
297
298 65
    if ($port) {
299 8
      $this->port = (int)$port;
300
    } else {
301
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
302 61
      $this->port = (int)@ini_get('mysqli.default_port');
303
    }
304
305
    // fallback
306 65
    if (!$this->port) {
307
      $this->port = 3306;
308
    }
309
310 65
    if (!$this->socket) {
311
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
312 65
      $this->socket = @ini_get('mysqli.default_socket');
313
    }
314
315 65
    $this->_debug->setExitOnError($exit_on_error);
316 65
    $this->_debug->setEchoOnError($echo_on_error);
317
318 65
    $this->_debug->setLoggerClassName($logger_class_name);
319 65
    $this->_debug->setLoggerLevel($logger_level);
320
321 65
    $this->setConfigExtra($extra_config);
322
323 65
    return $this->showConfigError();
324
  }
325
326
  /**
327
   * Parses arrays with value pairs and generates SQL to use in queries.
328
   *
329
   * @param array  $arrayPair
330
   * @param string $glue <p>This is the separator.</p>
331
   *
332
   * @return string
333
   *
334
   * @internal
335
   */
336 51
  public function _parseArrayPair(array $arrayPair, string $glue = ','): string
337
  {
338
    // init
339 51
    $sql = '';
340
341 51
    if (\count($arrayPair) === 0) {
342
      return '';
343
    }
344
345 51
    $arrayPairCounter = 0;
346 51
    foreach ($arrayPair as $_key => $_value) {
347 51
      $_connector = '=';
348 51
      $_glueHelper = '';
349 51
      $_key_upper = \strtoupper($_key);
350
351 51
      if (\strpos($_key_upper, ' NOT') !== false) {
352 4
        $_connector = 'NOT';
353
      }
354
355 51
      if (\strpos($_key_upper, ' IS') !== false) {
356 2
        $_connector = 'IS';
357
      }
358
359 51
      if (\strpos($_key_upper, ' IS NOT') !== false) {
360 2
        $_connector = 'IS NOT';
361
      }
362
363 51
      if (\strpos($_key_upper, ' IN') !== false) {
364 2
        $_connector = 'IN';
365
      }
366
367 51
      if (\strpos($_key_upper, ' NOT IN') !== false) {
368 2
        $_connector = 'NOT IN';
369
      }
370
371 51
      if (\strpos($_key_upper, ' BETWEEN') !== false) {
372 2
        $_connector = 'BETWEEN';
373
      }
374
375 51
      if (\strpos($_key_upper, ' NOT BETWEEN') !== false) {
376 2
        $_connector = 'NOT BETWEEN';
377
      }
378
379 51
      if (\strpos($_key_upper, ' LIKE') !== false) {
380 4
        $_connector = 'LIKE';
381
      }
382
383 51
      if (\strpos($_key_upper, ' NOT LIKE') !== false) {
384 4
        $_connector = 'NOT LIKE';
385
      }
386
387 51 View Code Duplication
      if (\strpos($_key_upper, ' >') !== false && \strpos($_key_upper, ' =') === false) {
388 6
        $_connector = '>';
389
      }
390
391 51 View Code Duplication
      if (\strpos($_key_upper, ' <') !== false && \strpos($_key_upper, ' =') === false) {
392 2
        $_connector = '<';
393
      }
394
395 51
      if (\strpos($_key_upper, ' >=') !== false) {
396 6
        $_connector = '>=';
397
      }
398
399 51
      if (\strpos($_key_upper, ' <=') !== false) {
400 2
        $_connector = '<=';
401
      }
402
403 51
      if (\strpos($_key_upper, ' <>') !== false) {
404 2
        $_connector = '<>';
405
      }
406
407 51
      if (\strpos($_key_upper, ' OR') !== false) {
408 4
        $_glueHelper = 'OR';
409
      }
410
411 51
      if (\strpos($_key_upper, ' AND') !== false) {
412 2
        $_glueHelper = 'AND';
413
      }
414
415 51
      if (\is_array($_value) === true) {
416 4
        foreach ($_value as $oldKey => $oldValue) {
417 4
          $_value[$oldKey] = $this->secure($oldValue);
418
        }
419
420 4
        if ($_connector === 'NOT IN' || $_connector === 'IN') {
421 2
          $_value = '(' . \implode(',', $_value) . ')';
422 4
        } elseif ($_connector === 'NOT BETWEEN' || $_connector === 'BETWEEN') {
423 4
          $_value = '(' . \implode(' AND ', $_value) . ')';
424
        }
425
426
      } else {
427 51
        $_value = $this->secure($_value);
428
      }
429
430 51
      $quoteString = $this->quote_string(
431 51
          \trim(
432 51
              \str_ireplace(
433
                  [
434 51
                      $_connector,
435 51
                      $_glueHelper,
436
                  ],
437 51
                  '',
438 51
                  $_key
439
              )
440
          )
441
      );
442
443 51
      $_value = (array)$_value;
444
445 51
      if (!$_glueHelper) {
446 51
        $_glueHelper = $glue;
447
      }
448
449 51
      $tmpCounter = 0;
450 51
      foreach ($_value as $valueInner) {
451
452 51
        $_glueHelperInner = $_glueHelper;
453
454 51
        if ($arrayPairCounter === 0) {
455
456 51
          if ($tmpCounter === 0 && $_glueHelper === 'OR') {
457 2
            $_glueHelperInner = '1 = 1 AND ('; // first "OR"-query glue
458 51
          } elseif ($tmpCounter === 0) {
459 51
            $_glueHelperInner = ''; // first query glue e.g. for "INSERT"-query -> skip the first ","
460
          }
461
462 47
        } elseif ($tmpCounter === 0 && $_glueHelper === 'OR') {
463 2
          $_glueHelperInner = 'AND ('; // inner-loop "OR"-query glue
464
        }
465
466 51
        if (\is_string($valueInner) && $valueInner === '') {
467
          $valueInner = "''";
468
        }
469
470 51
        $sql .= ' ' . $_glueHelperInner . ' ' . $quoteString . ' ' . $_connector . ' ' . $valueInner . " \n";
471 51
        $tmpCounter++;
472
      }
473
474 51
      if ($_glueHelper === 'OR') {
475 4
        $sql .= ' ) ';
476
      }
477
478 51
      $arrayPairCounter++;
479
    }
480
481 51
    return $sql;
482
  }
483
484
  /**
485
   * _parseQueryParams
486
   *
487
   * @param string $sql
488
   * @param array  $params
489
   *
490
   * @return array <p>with the keys -> 'sql', 'params'</p>
491
   */
492 15
  private function _parseQueryParams(string $sql, array $params = []): array
493
  {
494 15
    $offset = \strpos($sql, '?');
495
496
    // is there anything to parse?
497
    if (
498 15
        $offset === false
499
        ||
500 15
        \count($params) === 0
501
    ) {
502 12
      return ['sql' => $sql, 'params' => $params];
503
    }
504
505 5
    foreach ($params as $key => $param) {
506
507
      // use this only for not named parameters
508 5
      if (!\is_int($key)) {
509 2
        continue;
510
      }
511
512 5
      if ($offset === false) {
513
        continue;
514
      }
515
516 5
      $replacement = $this->secure($param);
517
518 5
      unset($params[$key]);
519
520 5
      $sql = \substr_replace($sql, $replacement, $offset, 1);
521 5
      $offset = \strpos($sql, '?', $offset + \strlen((string)$replacement));
522
    }
523
524 5
    return ['sql' => $sql, 'params' => $params];
525
  }
526
527
  /**
528
   * Returns the SQL by replacing :placeholders with SQL-escaped values.
529
   *
530
   * @param mixed $sql    <p>The SQL string.</p>
531
   * @param array $params <p>An array of key-value bindings.</p>
532
   *
533
   * @return array <p>with the keys -> 'sql', 'params'</p>
534
   */
535 17
  private function _parseQueryParamsByName(string $sql, array $params = []): array
536
  {
537
    // is there anything to parse?
538
    if (
539 17
        \strpos($sql, ':') === false
540
        ||
541 17
        \count($params) === 0
542
    ) {
543 5
      return ['sql' => $sql, 'params' => $params];
544
    }
545
546 14
    $offset = null;
547 14
    $replacement = null;
548 14
    foreach ($params as $name => $param) {
549
550
      // use this only for named parameters
551 14
      if (\is_int($name)) {
552
        continue;
553
      }
554
555
      // add ":" if needed
556 14
      if (\strpos($name, ':') !== 0) {
557 4
        $nameTmp = ':' . $name;
558
      } else {
559 10
        $nameTmp = $name;
560
      }
561
562 14
      if ($offset === null) {
563 14
        $offset = \strpos($sql, $nameTmp);
564
      } else {
565 13
        $offset = \strpos($sql, $nameTmp, $offset + \strlen((string)$replacement));
566
      }
567
568 14
      if ($offset === false) {
569 2
        continue;
570
      }
571
572 14
      $replacement = $this->secure($param);
573
574 14
      unset($params[$name]);
575
576 14
      $sql = \substr_replace($sql, $replacement, $offset, \strlen($nameTmp));
577
    }
578
579 14
    return ['sql' => $sql, 'params' => $params];
580
  }
581
582
  /**
583
   * Gets the number of affected rows in a previous MySQL operation.
584
   *
585
   * @return int
586
   */
587 20
  public function affected_rows(): int
588
  {
589 20
    return \mysqli_affected_rows($this->link);
590
  }
591
592
  /**
593
   * Begins a transaction, by turning off auto commit.
594
   *
595
   * @return bool <p>This will return true or false indicating success of transaction</p>
596
   */
597 12 View Code Duplication
  public function beginTransaction(): bool
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
598
  {
599 12
    if ($this->_in_transaction === true) {
600 4
      $this->_debug->displayError('Error: mysql server already in transaction!', false);
601
602 4
      return false;
603
    }
604
605 12
    $this->clearErrors(); // needed for "$this->endTransaction()"
606 12
    $this->_in_transaction = true;
607 12
    $return = \mysqli_autocommit($this->link, false);
608 12
    if ($return === false) {
609
      $this->_in_transaction = false;
610
    }
611
612 12
    return $return;
613
  }
614
615
  /**
616
   * Clear the errors in "_debug->_errors".
617
   *
618
   * @return bool
619
   */
620 12
  public function clearErrors(): bool
621
  {
622 12
    return $this->_debug->clearErrors();
623
  }
624
625
  /**
626
   * Closes a previously opened database connection.
627
   *
628
   * @return bool
629
   *              Will return "true", if the connection was closed,
630
   *              otherwise (e.g. if the connection was already closed) "false".
631
   */
632 4
  public function close(): bool
633
  {
634 4
    $this->connected = false;
635
636 4
    if ($this->_doctrine_connection) {
637
638 2
      $connectedBefore = $this->_doctrine_connection->isConnected();
639
640 2
      $this->_doctrine_connection->close();
641 2
      $this->link = null;
642
643 2
      if ($connectedBefore === true) {
644 2
        return !$this->_doctrine_connection->isConnected();
645
      }
646
647 1
      return false;
648
    }
649
650
    if (
651 2
        $this->link
652
        &&
653 2
        $this->link instanceof \mysqli
654
    ) {
655 2
      $result = \mysqli_close($this->link);
656 2
      $this->link = null;
657
658 2
      return $result;
659
    }
660
661 1
    $this->link = null;
662
663 1
    return false;
664
  }
665
666
  /**
667
   * Commits the current transaction and end the transaction.
668
   *
669
   * @return bool <p>Boolean true on success, false otherwise.</p>
670
   */
671 4 View Code Duplication
  public function commit(): bool
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
672
  {
673 4
    if ($this->_in_transaction === false) {
674
      $this->_debug->displayError('Error: mysql server is not in transaction!', false);
675
676
      return false;
677
    }
678
679 4
    $return = \mysqli_commit($this->link);
680 4
    \mysqli_autocommit($this->link, true);
681 4
    $this->_in_transaction = false;
682
683 4
    return $return;
684
  }
685
686
  /**
687
   * Open a new connection to the MySQL server.
688
   *
689
   * @return bool
690
   *
691
   * @throws DBConnectException
692
   */
693 64
  public function connect(): bool
694
  {
695 64
    if ($this->isReady()) {
696 2
      return true;
697
    }
698
699 64
    if ($this->_doctrine_connection) {
700 54
      $this->_doctrine_connection->connect();
701
702 54
      $doctrineMySQLi = $this->_doctrine_connection->getWrappedConnection();
703 54
      if ($doctrineMySQLi instanceof \Doctrine\DBAL\Driver\Mysqli\MysqliConnection) {
704 54
        $this->link = $doctrineMySQLi->getWrappedResourceHandle();
705
706 54
        $this->connected = $this->_doctrine_connection->isConnected();
707
708 54
        if (!$this->connected) {
709
          $error = 'Error connecting to mysql server: ' . $this->_doctrine_connection->errorInfo();
710
          $this->_debug->displayError($error, false);
711
          throw new DBConnectException($error, 101);
712
        }
713
714 54
        $this->set_charset($this->charset);
715
716 54
        return $this->isReady();
717
      }
718
    }
719
720 14
    $flags = null;
721
722 14
    \mysqli_report(MYSQLI_REPORT_STRICT);
723
    try {
724 14
      $this->link = \mysqli_init();
0 ignored issues
show
Documentation Bug introduced by
It seems like \mysqli_init() of type object<mysql> is incompatible with the declared type object<mysqli>|null of property $link.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
725
726 14
      if (Helper::isMysqlndIsUsed() === true) {
727 14
        \mysqli_options($this->link, MYSQLI_OPT_INT_AND_FLOAT_NATIVE, true);
728
      }
729
730 14
      if ($this->_ssl === true) {
731
732
        if (empty($this->clientcert)) {
0 ignored issues
show
Bug introduced by
The property clientcert does not seem to exist. Did you mean _clientcert?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
733
          throw new DBConnectException('Error connecting to mysql server: clientcert not defined');
734
        }
735
736
        if (empty($this->clientkey)) {
0 ignored issues
show
Bug introduced by
The property clientkey does not seem to exist. Did you mean _clientkey?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
737
          throw new DBConnectException('Error connecting to mysql server: clientkey not defined');
738
        }
739
740
        if (empty($this->cacert)) {
0 ignored issues
show
Bug introduced by
The property cacert does not seem to exist. Did you mean _cacert?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
741
          throw new DBConnectException('Error connecting to mysql server: cacert not defined');
742
        }
743
744
        \mysqli_options($this->link, MYSQLI_OPT_SSL_VERIFY_SERVER_CERT, true);
745
746
        /** @noinspection PhpParamsInspection */
747
        \mysqli_ssl_set(
748
            $this->link,
749
            $this->_clientkey,
750
            $this->_clientcert,
751
            $this->_cacert,
752
            null,
753
            null
754
        );
755
756
        $flags = MYSQLI_CLIENT_SSL;
757
      }
758
759
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
760 14
      $this->connected = @\mysqli_real_connect(
761 14
          $this->link,
762 14
          $this->hostname,
763 14
          $this->username,
764 14
          $this->password,
765 14
          $this->database,
766 14
          $this->port,
767 14
          $this->socket,
768 14
          (int)$flags
769
      );
770
771 6
    } catch (\Exception $e) {
772 6
      $error = 'Error connecting to mysql server: ' . $e->getMessage();
773 6
      $this->_debug->displayError($error, false);
774 6
      throw new DBConnectException($error, 100, $e);
775
    }
776 8
    \mysqli_report(MYSQLI_REPORT_OFF);
777
778 8
    $errno = \mysqli_connect_errno();
779 8
    if (!$this->connected || $errno) {
780
      $error = 'Error connecting to mysql server: ' . \mysqli_connect_error() . ' (' . $errno . ')';
781
      $this->_debug->displayError($error, false);
782
      throw new DBConnectException($error, 101);
783
    }
784
785 8
    $this->set_charset($this->charset);
786
787 8
    return $this->isReady();
788
  }
789
790
  /**
791
   * Execute a "delete"-query.
792
   *
793
   * @param string       $table
794
   * @param string|array $where
795
   * @param string|null  $databaseName <p>Use <strong>null</strong> if you will use the current database.</p>
796
   *
797
   * @return false|int <p>false on error</p>
798
   *
799
   * @throws QueryException
800
   */
801 3 View Code Duplication
  public function delete(string $table, $where, string $databaseName = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
802
  {
803
    // init
804 3
    $table = \trim($table);
805
806 3
    if ($table === '') {
807 2
      $this->_debug->displayError('Invalid table name, table name in empty.', false);
808
809 2
      return false;
810
    }
811
812 3
    if (\is_string($where)) {
813 2
      $WHERE = $this->escape($where, false);
814 3
    } elseif (\is_array($where)) {
815 3
      $WHERE = $this->_parseArrayPair($where, 'AND');
816
    } else {
817 2
      $WHERE = '';
818
    }
819
820 3
    if ($databaseName) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $databaseName of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
821
      $databaseName = $this->quote_string(\trim($databaseName)) . '.';
822
    }
823
824 3
    $sql = 'DELETE FROM ' . $databaseName . $this->quote_string($table) . " WHERE ($WHERE)";
825
826 3
    return $this->query($sql);
827
  }
828
829
  /**
830
   * Ends a transaction and commits if no errors, then ends autocommit.
831
   *
832
   * @return bool <p>This will return true or false indicating success of transactions.</p>
833
   */
834 8 View Code Duplication
  public function endTransaction(): bool
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
835
  {
836 8
    if ($this->_in_transaction === false) {
837
      $this->_debug->displayError('Error: mysql server is not in transaction!', false);
838
839
      return false;
840
    }
841
842 8
    if (!$this->errors()) {
843 2
      $return = \mysqli_commit($this->link);
844
    } else {
845 6
      $this->rollback();
846 6
      $return = false;
847
    }
848
849 8
    \mysqli_autocommit($this->link, true);
850 8
    $this->_in_transaction = false;
851
852 8
    return $return;
853
  }
854
855
  /**
856
   * Get all errors from "$this->_errors".
857
   *
858
   * @return array|false <p>false === on errors</p>
859
   */
860 8
  public function errors()
861
  {
862 8
    $errors = $this->_debug->getErrors();
863
864 8
    return \count($errors) > 0 ? $errors : false;
865
  }
866
867
  /**
868
   * Escape: Use "mysqli_real_escape_string" and clean non UTF-8 chars + some extra optional stuff.
869
   *
870
   * @param mixed     $var           boolean: convert into "integer"<br />
871
   *                                 int: int (don't change it)<br />
872
   *                                 float: float (don't change it)<br />
873
   *                                 null: null (don't change it)<br />
874
   *                                 array: run escape() for every key => value<br />
875
   *                                 string: run UTF8::cleanup() and mysqli_real_escape_string()<br />
876
   * @param bool      $stripe_non_utf8
877
   * @param bool      $html_entity_decode
878
   * @param bool|null $convert_array <strong>false</strong> => Keep the array.<br />
879
   *                                 <strong>true</strong> => Convert to string var1,var2,var3...<br />
880
   *                                 <strong>null</strong> => Convert the array into null, every time.
881
   *
882
   * @return mixed
883
   */
884 87
  public function escape($var = '', bool $stripe_non_utf8 = true, bool $html_entity_decode = false, $convert_array = false)
885
  {
886
    // [empty]
887 87
    if ($var === '') {
888 4
      return '';
889
    }
890
891
    // ''
892 87
    if ($var === "''") {
893
      return "''";
894
    }
895
896
    // check the type
897 87
    $type = \gettype($var);
898
899 87
    if ($type === 'object') {
900 6
      if ($var instanceof \DateTime) {
901 6
        $var = $var->format('Y-m-d H:i:s');
902 6
        $type = 'string';
903 4
      } elseif (\method_exists($var, '__toString')) {
904 4
        $var = (string)$var;
905 4
        $type = 'string';
906
      }
907
    }
908
909
    switch ($type) {
910 87
      case 'boolean':
911 6
        $var = (int)$var;
912 6
        break;
913
914 87
      case 'double':
915 87
      case 'integer':
916 51
        break;
917
918 82
      case 'string':
919 82
        if ($stripe_non_utf8 === true) {
920 17
          $var = UTF8::cleanup($var);
921
        }
922
923 82
        if ($html_entity_decode === true) {
924 2
          $var = UTF8::html_entity_decode($var);
925
        }
926
927 82
        $var = \get_magic_quotes_gpc() ? \stripslashes($var) : $var;
928
929 82
        $var = \mysqli_real_escape_string($this->link, $var);
930
931 82
        break;
932
933 6
      case 'array':
934 4
        if ($convert_array === null) {
935
936 2
          if ($this->_convert_null_to_empty_string === true) {
937
            $var = "''";
938
          } else {
939 2
            $var = 'NULL';
940
          }
941
942
        } else {
943
944 4
          $varCleaned = [];
945 4
          foreach ((array)$var as $key => $value) {
946
947 4
            $key = $this->escape($key, $stripe_non_utf8, $html_entity_decode);
948 4
            $value = $this->escape($value, $stripe_non_utf8, $html_entity_decode);
949
950
            /** @noinspection OffsetOperationsInspection */
951 4
            $varCleaned[$key] = $value;
952
          }
953
954 4 View Code Duplication
          if ($convert_array === true) {
955 2
            $varCleaned = \implode(',', $varCleaned);
956
957 2
            $var = $varCleaned;
958
          } else {
959 4
            $var = $varCleaned;
960
          }
961
962
        }
963 4
        break;
964
965 6
      case 'NULL':
966 4
        if ($this->_convert_null_to_empty_string === true) {
967
          $var = "''";
968
        } else {
969 4
          $var = 'NULL';
970
        }
971 4
        break;
972
973
      default:
974 4
        throw new \InvalidArgumentException(sprintf('Not supported value "%s" of type %s.', print_r($var, true), $type));
975
        break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
976
    }
977
978 87
    return $var;
979
  }
980
981
  /**
982
   * Execute select/insert/update/delete sql-queries.
983
   *
984
   * @param string  $query    <p>sql-query</p>
985
   * @param bool    $useCache optional <p>use cache?</p>
986
   * @param int     $cacheTTL optional <p>cache-ttl in seconds</p>
987
   * @param DB|null $db       optional <p>the database connection</p>
988
   *
989
   * @return mixed "array" by "<b>SELECT</b>"-queries<br />
990
   *               "int" (insert_id) by "<b>INSERT</b>"-queries<br />
991
   *               "int" (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
992
   *               "true" by e.g. "DROP"-queries<br />
993
   *               "false" on error
994
   *
995
   * @throws QueryException
996
   */
997 6
  public static function execSQL(string $query, bool $useCache = false, int $cacheTTL = 3600, self $db = null)
998
  {
999
    // init
1000 6
    $cacheKey = null;
1001 6
    if (!$db) {
1002 6
      $db = self::getInstance();
1003
    }
1004
1005 6 View Code Duplication
    if ($useCache === true) {
1006 2
      $cache = new Cache(null, null, false, $useCache);
1007 2
      $cacheKey = 'sql-' . \md5($query);
1008
1009
      if (
1010 2
          $cache->getCacheIsReady() === true
1011
          &&
1012 2
          $cache->existsItem($cacheKey)
1013
      ) {
1014 2
        return $cache->getItem($cacheKey);
1015
      }
1016
1017
    } else {
1018 6
      $cache = false;
1019
    }
1020
1021 6
    $result = $db->query($query);
1022
1023 6
    if ($result instanceof Result) {
1024
1025 2
      $return = $result->fetchAllArray();
1026
1027
      // save into the cache
1028 View Code Duplication
      if (
1029 2
          $cacheKey !== null
1030
          &&
1031 2
          $useCache === true
1032
          &&
1033 2
          $cache instanceof Cache
1034
          &&
1035 2
          $cache->getCacheIsReady() === true
1036
      ) {
1037 2
        $cache->setItem($cacheKey, $return, $cacheTTL);
1038
      }
1039
1040
    } else {
1041 4
      $return = $result;
1042
    }
1043
1044 6
    return $return;
1045
  }
1046
1047
  /**
1048
   * Get all table-names via "SHOW TABLES".
1049
   *
1050
   * @return array
1051
   */
1052 2
  public function getAllTables(): array
1053
  {
1054 2
    $query = 'SHOW TABLES';
1055 2
    $result = $this->query($query);
1056
1057 2
    return $result->fetchAllArray();
1058
  }
1059
1060
  /**
1061
   * @return Debug
1062
   */
1063 9
  public function getDebugger(): Debug
1064
  {
1065 9
    return $this->_debug;
1066
  }
1067
1068
  /**
1069
   * @return \Doctrine\DBAL\Connection|null
1070
   */
1071
  public function getDoctrineConnection()
1072
  {
1073
    return $this->_doctrine_connection;
1074
  }
1075
1076
  /**
1077
   * Get errors from "$this->_errors".
1078
   *
1079
   * @return array
1080
   */
1081
  public function getErrors(): array
1082
  {
1083
    return $this->_debug->getErrors();
1084
  }
1085
1086
  /**
1087
   * getInstance()
1088
   *
1089
   * @param string $hostname
1090
   * @param string $username
1091
   * @param string $password
1092
   * @param string $database
1093
   * @param int    $port                 <p>default is (int)3306</p>
1094
   * @param string $charset              <p>default is 'utf8' or 'utf8mb4' (if supported)</p>
1095
   * @param bool   $exit_on_error        <p>Throw a 'Exception' when a query failed, otherwise it will return 'false'.
1096
   *                                     Use false to disable it.</p>
1097
   * @param bool   $echo_on_error        <p>Echo the error if "checkForDev()" returns true.
1098
   *                                     Use false to disable it.</p>
1099
   * @param string $logger_class_name
1100
   * @param string $logger_level         <p>'TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL'</p>
1101
   * @param array  $extra_config         <p>
1102
   *                                     're_connect'    => bool<br>
1103
   *                                     'session_to_db' => bool<br>
1104
   *                                     'doctrine'      => \Doctrine\DBAL\Connection<br>
1105
   *                                     'socket'        => 'string (path)'<br>
1106
   *                                     'ssl'           => bool<br>
1107
   *                                     'clientkey'     => 'string (path)'<br>
1108
   *                                     'clientcert'    => 'string (path)'<br>
1109
   *                                     'cacert'        => 'string (path)'<br>
1110
   *                                     </p>
1111
   *
1112
   * @return self
1113
   */
1114 173
  public static function getInstance(
1115
      string $hostname = '',
1116
      string $username = '',
1117
      string $password = '',
1118
      string $database = '',
1119
      $port = 3306,
1120
      string $charset = 'utf8',
1121
      bool $exit_on_error = true,
1122
      bool $echo_on_error = true,
1123
      string $logger_class_name = '',
1124
      string $logger_level = '',
1125
      array $extra_config = []
1126
  ): self
1127
  {
1128
    /**
1129
     * @var $instance self[]
1130
     */
1131 173
    static $instance = [];
1132
1133
    /**
1134
     * @var $firstInstance self
1135
     */
1136 173
    static $firstInstance = null;
1137
1138
    // fallback
1139 173
    if (!$charset) {
1140 65
      $charset = 'utf8';
1141
    }
1142
1143
    if (
1144 173
        $hostname . $username . $password . $database . $port . $charset == '3306utf8'
1145
        &&
1146 173
        null !== $firstInstance
1147
    ) {
1148 40
      if (isset($extra_config['re_connect']) && $extra_config['re_connect'] === true) {
1149
        $firstInstance->reconnect(true);
1150
      }
1151
1152 40
      return $firstInstance;
1153
    }
1154
1155 173
    $extra_config_string = '';
1156 173
    foreach ($extra_config as $extra_config_key => $extra_config_value) {
1157 54
      if (\is_object($extra_config_value)) {
1158 54
        $extra_config_value_tmp = \spl_object_hash($extra_config_value);
1159
      } else {
1160
        $extra_config_value_tmp = (string)$extra_config_value;
1161
      }
1162 54
      $extra_config_string .= $extra_config_key . $extra_config_value_tmp;
1163
    }
1164
1165 173
    $connection = \md5(
1166 173
        $hostname . $username . $password . $database . $port . $charset . (int)$exit_on_error . (int)$echo_on_error . $logger_class_name . $logger_level . $extra_config_string
1167
    );
1168
1169 173
    if (!isset($instance[$connection])) {
1170 65
      $instance[$connection] = new self(
1171 65
          $hostname,
1172 65
          $username,
1173 65
          $password,
1174 65
          $database,
1175 65
          $port,
1176 65
          $charset,
1177 65
          $exit_on_error,
1178 65
          $echo_on_error,
1179 65
          $logger_class_name,
1180 65
          $logger_level,
1181 65
          $extra_config
1182
      );
1183
1184 59
      if (null === $firstInstance) {
1185 1
        $firstInstance = $instance[$connection];
1186
      }
1187
    }
1188
1189 173
    if (isset($extra_config['re_connect']) && $extra_config['re_connect'] === true) {
1190
      $instance[$connection]->reconnect(true);
1191
    }
1192
1193 173
    return $instance[$connection];
1194
  }
1195
1196
  /**
1197
   * Get the mysqli-link (link identifier returned by mysqli-connect).
1198
   *
1199
   * @return \mysqli
1200
   */
1201 39
  public function getLink(): \mysqli
1202
  {
1203 39
    return $this->link;
1204
  }
1205
1206
  /**
1207
   * Get the current charset.
1208
   *
1209
   * @return string
1210
   */
1211 2
  public function get_charset(): string
1212
  {
1213 2
    return $this->charset;
1214
  }
1215
1216
  /**
1217
   * Check if we are in a transaction.
1218
   *
1219
   * @return bool
1220
   */
1221
  public function inTransaction(): bool
1222
  {
1223
    return $this->_in_transaction;
1224
  }
1225
1226
  /**
1227
   * Execute a "insert"-query.
1228
   *
1229
   * @param string      $table
1230
   * @param array       $data
1231
   * @param string|null $databaseName <p>Use <strong>null</strong> if you will use the current database.</p>
1232
   *
1233
   * @return false|int <p>false on error</p>
1234
   *
1235
   * @throws QueryException
1236
   */
1237 51
  public function insert(string $table, array $data = [], string $databaseName = null)
1238
  {
1239
    // init
1240 51
    $table = \trim($table);
1241
1242 51
    if ($table === '') {
1243 4
      $this->_debug->displayError('Invalid table name, table name in empty.', false);
1244
1245 4
      return false;
1246
    }
1247
1248 49
    if (\count($data) === 0) {
1249 6
      $this->_debug->displayError('Invalid data for INSERT, data is empty.', false);
1250
1251 6
      return false;
1252
    }
1253
1254 45
    $SET = $this->_parseArrayPair($data);
1255
1256 45
    if ($databaseName) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $databaseName of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1257
      $databaseName = $this->quote_string(\trim($databaseName)) . '.';
1258
    }
1259
1260 45
    $sql = 'INSERT INTO ' . $databaseName . $this->quote_string($table) . " SET $SET";
1261
1262 45
    return $this->query($sql);
1263
  }
1264
1265
  /**
1266
   * Returns the auto generated id used in the last query.
1267
   *
1268
   * @return int|string
1269
   */
1270 81
  public function insert_id()
1271
  {
1272 81
    return \mysqli_insert_id($this->link);
1273
  }
1274
1275
  /**
1276
   * Check if db-connection is ready.
1277
   *
1278
   * @return boolean
1279
   */
1280 160
  public function isReady(): bool
1281
  {
1282 160
    return $this->connected ? true : false;
1283
  }
1284
1285
  /**
1286
   * Get the last sql-error.
1287
   *
1288
   * @return string|false <p>false === there was no error</p>
1289
   */
1290
  public function lastError()
1291
  {
1292
    $errors = $this->_debug->getErrors();
1293
1294
    return \count($errors) > 0 ? end($errors) : false;
1295
  }
1296
1297
  /**
1298
   * Execute a sql-multi-query.
1299
   *
1300
   * @param string $sql
1301
   *
1302
   * @return false|Result[] "Result"-Array by "<b>SELECT</b>"-queries<br />
1303
   *                        "boolean" by only "<b>INSERT</b>"-queries<br />
1304
   *                        "boolean" by only (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
1305
   *                        "boolean" by only by e.g. "DROP"-queries<br />
1306
   *
1307
   * @throws QueryException
1308
   */
1309 2
  public function multi_query(string $sql)
1310
  {
1311 2
    if (!$this->isReady()) {
1312
      return false;
1313
    }
1314
1315 2 View Code Duplication
    if (!$sql || $sql === '') {
1316 2
      $this->_debug->displayError('Can not execute an empty query.', false);
1317
1318 2
      return false;
1319
    }
1320
1321 2
    $query_start_time = microtime(true);
1322 2
    $resultTmp = \mysqli_multi_query($this->link, $sql);
1323 2
    $query_duration = microtime(true) - $query_start_time;
1324
1325 2
    $this->_debug->logQuery($sql, $query_duration, 0);
1326
1327 2
    $returnTheResult = false;
1328 2
    $result = [];
1329
1330 2
    if ($resultTmp) {
1331
      do {
1332
1333 2
        $resultTmpInner = \mysqli_store_result($this->link);
1334
1335 2
        if ($resultTmpInner instanceof \mysqli_result) {
1336
1337 2
          $returnTheResult = true;
1338 2
          $result[] = new Result($sql, $resultTmpInner);
1339
1340
        } else if (
1341 2
            $resultTmpInner === true
1342
            ||
1343 2
            !\mysqli_errno($this->link)
1344
        ) {
1345
1346 2
          $result[] = true;
1347
1348
        } else {
1349
1350
          $result[] = false;
1351
1352
        }
1353
1354 2
      } while (\mysqli_more_results($this->link) === true ? \mysqli_next_result($this->link) : false);
1355
1356
    } else {
1357
1358
      // log the error query
1359 2
      $this->_debug->logQuery($sql, $query_duration, 0, true);
1360
1361 2
      return $this->queryErrorHandling(\mysqli_error($this->link), \mysqli_errno($this->link), $sql, false, true);
1362
    }
1363
1364
    // return the result only if there was a "SELECT"-query
1365 2
    if ($returnTheResult === true) {
1366 2
      return $result;
1367
    }
1368
1369
    if (
1370 2
        \count($result) > 0
1371
        &&
1372 2
        \in_array(false, $result, true) === false
1373
    ) {
1374 2
      return true;
1375
    }
1376
1377
    return false;
1378
  }
1379
1380
  /**
1381
   * Count number of rows found matching a specific query.
1382
   *
1383
   * @param string $query
1384
   *
1385
   * @return int
1386
   */
1387 2
  public function num_rows(string $query): int
1388
  {
1389 2
    $check = $this->query($query);
1390
1391
    if (
1392 2
        $check === false
1393
        ||
1394 2
        !$check instanceof Result
1395
    ) {
1396
      return 0;
1397
    }
1398
1399 2
    return $check->num_rows;
1400
  }
1401
1402
  /**
1403
   * Pings a server connection, or tries to reconnect
1404
   * if the connection has gone down.
1405
   *
1406
   * @return boolean
1407
   */
1408 6
  public function ping(): bool
1409
  {
1410 6
    if ($this->_doctrine_connection) {
1411
1412
      // this check is needed, but I don't know why "ping" isn't working?
1413 3
      if (!$this->_doctrine_connection->isConnected()) {
1414 1
        return false;
1415
      }
1416
1417 2
      return $this->_doctrine_connection->ping();
1418
    }
1419
1420
    if (
1421 3
        $this->link
1422
        &&
1423 3
        $this->link instanceof \mysqli
1424
    ) {
1425 2
      return \mysqli_ping($this->link);
1426
    }
1427
1428 1
    return false;
1429
  }
1430
1431
  /**
1432
   * Get a new "Prepare"-Object for your sql-query.
1433
   *
1434
   * @param string $query
1435
   *
1436
   * @return Prepare
1437
   */
1438 2
  public function prepare(string $query): Prepare
1439
  {
1440 2
    return new Prepare($this, $query);
1441
  }
1442
1443
  /**
1444
   * Execute a sql-query and return the result-array for select-statements.
1445
   *
1446
   * @param string $query
1447
   *
1448
   * @return mixed
1449
   * @deprecated
1450
   * @throws \Exception
1451
   */
1452 2
  public static function qry(string $query)
0 ignored issues
show
Unused Code introduced by
The parameter $query is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1453
  {
1454 2
    $db = self::getInstance();
1455
1456 2
    $args = \func_get_args();
1457
    /** @noinspection SuspiciousAssignmentsInspection */
1458 2
    $query = \array_shift($args);
1459 2
    $query = \str_replace('?', '%s', $query);
1460 2
    $args = \array_map(
1461
        [
1462 2
            $db,
1463 2
            'escape',
1464
        ],
1465 2
        $args
1466
    );
1467 2
    \array_unshift($args, $query);
1468 2
    $query = \sprintf(...$args);
1469 2
    $result = $db->query($query);
1470
1471 2
    if ($result instanceof Result) {
1472 2
      return $result->fetchAllArray();
1473
    }
1474
1475 2
    return $result;
1476
  }
1477
1478
  /**
1479
   * Execute a sql-query.
1480
   *
1481
   * @param string        $sql            <p>The sql query-string.</p>
1482
   *
1483
   * @param array|boolean $params         <p>
1484
   *                                      "array" of sql-query-parameters<br/>
1485
   *                                      "false" if you don't need any parameter (default)<br/>
1486
   *                                      </p>
1487
   *
1488
   * @return bool|int|Result              <p>
1489
   *                                      "Result" by "<b>SELECT</b>"-queries<br />
1490
   *                                      "int" (insert_id) by "<b>INSERT / REPLACE</b>"-queries<br />
1491
   *                                      "int" (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
1492
   *                                      "true" by e.g. "DROP"-queries<br />
1493
   *                                      "false" on error
1494
   *                                      </p>
1495
   *
1496
   * @throws QueryException
1497
   */
1498 130
  public function query(string $sql = '', $params = false)
1499
  {
1500 130
    if (!$this->isReady()) {
1501
      return false;
1502
    }
1503
1504 130 View Code Duplication
    if (!$sql || $sql === '') {
1505 8
      $this->_debug->displayError('Can not execute an empty query.', false);
1506
1507 8
      return false;
1508
    }
1509
1510
    if (
1511 126
        $params !== false
1512
        &&
1513 126
        \is_array($params)
1514
        &&
1515 126
        \count($params) > 0
1516
    ) {
1517 15
      $parseQueryParams = $this->_parseQueryParams($sql, $params);
1518 15
      $parseQueryParamsByName = $this->_parseQueryParamsByName($parseQueryParams['sql'], $parseQueryParams['params']);
1519 15
      $sql = $parseQueryParamsByName['sql'];
1520
    }
1521
1522
    // DEBUG
1523
    // var_dump($params);
1524
    // echo $sql . "\n";
1525
1526 126
    $query_start_time = microtime(true);
1527 126
    $queryException = null;
1528 126
    $query_result_doctrine = false;
1529
1530 126
    if ($this->_doctrine_connection) {
1531
1532
      try {
1533 28
        $query_result_doctrine = $this->_doctrine_connection->prepare($sql);
1534 28
        $query_result = $query_result_doctrine->execute();
1535 28
        $mysqli_field_count = $query_result_doctrine->columnCount();
1536 8
      } catch (\Exception $e) {
1537 8
        $query_result = false;
1538 8
        $mysqli_field_count = null;
1539
1540 28
        $queryException = $e;
1541
      }
1542
1543
    } else {
1544
1545 100
      $query_result = \mysqli_real_query($this->link, $sql);
1546 100
      $mysqli_field_count = \mysqli_field_count($this->link);
1547
1548
    }
1549
1550 126
    $query_duration = microtime(true) - $query_start_time;
1551
1552 126
    $this->query_count++;
1553
1554 126
    if ($mysqli_field_count) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $mysqli_field_count of type null|integer is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
1555
1556 89
      if ($this->_doctrine_connection) {
1557
1558 24
        $result = false;
1559
        if (
1560 24
            $query_result_doctrine
1561
            &&
1562 24
            $query_result_doctrine instanceof \Doctrine\DBAL\Statement
1563
        ) {
1564 24
          $result = $query_result_doctrine;
1565
        }
1566
1567
      } else {
1568
1569 89
        $result = \mysqli_store_result($this->link);
1570
1571
      }
1572
    } else {
1573 91
      $result = $query_result;
1574
    }
1575
1576
    if (
1577 126
        $result instanceof \Doctrine\DBAL\Statement
1578
        &&
1579 126
        $result->columnCount() > 0
1580
    ) {
1581
1582
      // log the select query
1583 24
      $this->_debug->logQuery($sql, $query_duration, $mysqli_field_count);
1584
1585
      // return query result object
1586 24
      return new Result($sql, $result);
0 ignored issues
show
Documentation introduced by
$result is of type object<Doctrine\DBAL\Statement>, but the function expects a object<mysqli_result>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1587
    }
1588
1589 122
    if ($result instanceof \mysqli_result) {
1590
1591
      // log the select query
1592 63
      $this->_debug->logQuery($sql, $query_duration, $mysqli_field_count);
1593
1594
      // return query result object
1595 63
      return new Result($sql, $result);
1596
    }
1597
1598 94
    if ($query_result === true) {
1599
1600
      // "INSERT" || "REPLACE"
1601 87 View Code Duplication
      if (preg_match('/^\s*?(?:INSERT|REPLACE)\s+/i', $sql)) {
1602 81
        $insert_id = (int)$this->insert_id();
1603 81
        $this->_debug->logQuery($sql, $query_duration, $insert_id);
1604
1605 81
        return $insert_id;
1606
      }
1607
1608
      // "UPDATE" || "DELETE"
1609 46 View Code Duplication
      if (preg_match('/^\s*?(?:UPDATE|DELETE)\s+/i', $sql)) {
1610 20
        $affected_rows = $this->affected_rows();
1611 20
        $this->_debug->logQuery($sql, $query_duration, $affected_rows);
1612
1613 20
        return $affected_rows;
1614
      }
1615
1616
      // log the ? query
1617 27
      $this->_debug->logQuery($sql, $query_duration, 0);
1618
1619 27
      return true;
1620
    }
1621
1622
    // log the error query
1623 23
    $this->_debug->logQuery($sql, $query_duration, 0, true);
1624
1625 23
    if ($queryException) {
1626 8
      return $this->queryErrorHandling($queryException->getMessage(), $queryException->getCode(), $sql, $params);
1627
    }
1628
1629 15
    return $this->queryErrorHandling(\mysqli_error($this->link), \mysqli_errno($this->link), $sql, $params);
1630
  }
1631
1632
  /**
1633
   * Error-handling for the sql-query.
1634
   *
1635
   * @param string     $errorMessage
1636
   * @param int        $errorNumber
1637
   * @param string     $sql
1638
   * @param array|bool $sqlParams <p>false if there wasn't any parameter</p>
1639
   * @param bool       $sqlMultiQuery
1640
   *
1641
   * @return mixed|false
1642
   *
1643
   * @throws QueryException
1644
   * @throws DBGoneAwayException
1645
   */
1646 27
  private function queryErrorHandling(string $errorMessage, int $errorNumber, string $sql, $sqlParams = false, bool $sqlMultiQuery = false)
1647
  {
1648
    if (
1649 27
        $errorMessage === 'DB server has gone away'
1650
        ||
1651 25
        $errorMessage === 'MySQL server has gone away'
1652
        ||
1653 27
        $errorNumber === 2006
1654
    ) {
1655 2
      static $RECONNECT_COUNTER;
1656
1657
      // exit if we have more then 3 "DB server has gone away"-errors
1658 2
      if ($RECONNECT_COUNTER > 3) {
1659
        $this->_debug->mailToAdmin('DB-Fatal-Error', $errorMessage . '(' . $errorNumber . ') ' . ":\n<br />" . $sql, 5);
1660
        throw new DBGoneAwayException($errorMessage);
1661
      }
1662
1663 2
      $this->_debug->mailToAdmin('DB-Error', $errorMessage . '(' . $errorNumber . ') ' . ":\n<br />" . $sql);
1664
1665
      // reconnect
1666 2
      $RECONNECT_COUNTER++;
1667 2
      $this->reconnect(true);
1668
1669
      // re-run the current (non multi) query
1670 2
      if ($sqlMultiQuery === false) {
1671 2
        return $this->query($sql, $sqlParams);
1672
      }
1673
1674
      return false;
1675
    }
1676
1677 25
    $this->_debug->mailToAdmin('SQL-Error', $errorMessage . '(' . $errorNumber . ') ' . ":\n<br />" . $sql);
1678
1679 25
    $force_exception_after_error = null; // auto
1680 25
    if ($this->_in_transaction === true) {
1681 8
      $force_exception_after_error = false;
1682
    }
1683
    // this query returned an error, we must display it (only for dev) !!!
1684
1685 25
    $this->_debug->displayError($errorMessage . '(' . $errorNumber . ') ' . ' | ' . $sql, $force_exception_after_error);
1686
1687 25
    return false;
1688
  }
1689
1690
  /**
1691
   * Quote && Escape e.g. a table name string.
1692
   *
1693
   * @param mixed $str
1694
   *
1695
   * @return string
1696
   */
1697 61
  public function quote_string($str): string
1698
  {
1699 61
    $str = \str_replace(
1700 61
        '`',
1701 61
        '``',
1702 61
        \trim(
1703 61
            (string)$this->escape($str, false),
1704 61
            '`'
1705
        )
1706
    );
1707
1708 61
    return '`' . $str . '`';
1709
  }
1710
1711
  /**
1712
   * Reconnect to the MySQL-Server.
1713
   *
1714
   * @param bool $checkViaPing
1715
   *
1716
   * @return bool
1717
   */
1718 5
  public function reconnect(bool $checkViaPing = false): bool
1719
  {
1720 5
    $ping = false;
1721 5
    if ($checkViaPing === true) {
1722 4
      $ping = $this->ping();
1723
    }
1724
1725 5
    if ($ping !== true) {
1726 5
      $this->connected = false;
1727 5
      $this->connect();
1728
    }
1729
1730 5
    return $this->isReady();
1731
  }
1732
1733
  /**
1734
   * Execute a "replace"-query.
1735
   *
1736
   * @param string      $table
1737
   * @param array       $data
1738
   * @param null|string $databaseName <p>Use <strong>null</strong> if you will use the current database.</p>
1739
   *
1740
   * @return false|int <p>false on error</p>
1741
   *
1742
   * @throws QueryException
1743
   */
1744 2
  public function replace(string $table, array $data = [], string $databaseName = null)
1745
  {
1746
    // init
1747 2
    $table = \trim($table);
1748
1749 2
    if ($table === '') {
1750 2
      $this->_debug->displayError('Invalid table name, table name in empty.', false);
1751
1752 2
      return false;
1753
    }
1754
1755 2
    if (\count($data) === 0) {
1756 2
      $this->_debug->displayError('Invalid data for REPLACE, data is empty.', false);
1757
1758 2
      return false;
1759
    }
1760
1761
    // extracting column names
1762 2
    $columns = \array_keys($data);
1763 2
    foreach ($columns as $k => $_key) {
1764
      /** @noinspection AlterInForeachInspection */
1765 2
      $columns[$k] = $this->quote_string($_key);
1766
    }
1767
1768 2
    $columns = \implode(',', $columns);
1769
1770
    // extracting values
1771 2
    foreach ($data as $k => $_value) {
1772
      /** @noinspection AlterInForeachInspection */
1773 2
      $data[$k] = $this->secure($_value);
1774
    }
1775 2
    $values = \implode(',', $data);
1776
1777 2
    if ($databaseName) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $databaseName of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1778
      $databaseName = $this->quote_string(\trim($databaseName)) . '.';
1779
    }
1780
1781 2
    $sql = 'REPLACE INTO ' . $databaseName . $this->quote_string($table) . " ($columns) VALUES ($values)";
1782
1783 2
    return $this->query($sql);
1784
  }
1785
1786
  /**
1787
   * Rollback in a transaction and end the transaction.
1788
   *
1789
   * @return bool <p>Boolean true on success, false otherwise.</p>
1790
   */
1791 8 View Code Duplication
  public function rollback(): bool
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1792
  {
1793 8
    if ($this->_in_transaction === false) {
1794
      $this->_debug->displayError('Error: mysql server is not in transaction!', false);
1795
1796
      return false;
1797
    }
1798
1799 8
    $return = \mysqli_rollback($this->link);
1800 8
    \mysqli_autocommit($this->link, true);
1801 8
    $this->_in_transaction = false;
1802
1803 8
    return $return;
1804
  }
1805
1806
  /**
1807
   * Try to secure a variable, so can you use it in sql-queries.
1808
   *
1809
   * <p>
1810
   * <strong>int:</strong> (also strings that contains only an int-value)<br />
1811
   * 1. parse into "int"
1812
   * </p><br />
1813
   *
1814
   * <p>
1815
   * <strong>float:</strong><br />
1816
   * 1. return "float"
1817
   * </p><br />
1818
   *
1819
   * <p>
1820
   * <strong>string:</strong><br />
1821
   * 1. check if the string isn't a default mysql-time-function e.g. 'CURDATE()'<br />
1822
   * 2. trim '<br />
1823
   * 3. escape the string (and remove non utf-8 chars)<br />
1824
   * 4. trim ' again (because we maybe removed some chars)<br />
1825
   * 5. add ' around the new string<br />
1826
   * </p><br />
1827
   *
1828
   * <p>
1829
   * <strong>array:</strong><br />
1830
   * 1. return null
1831
   * </p><br />
1832
   *
1833
   * <p>
1834
   * <strong>object:</strong><br />
1835
   * 1. return false
1836
   * </p><br />
1837
   *
1838
   * <p>
1839
   * <strong>null:</strong><br />
1840
   * 1. return null
1841
   * </p>
1842
   *
1843
   * @param mixed $var
1844
   * @param bool|null $convert_array <strong>false</strong> => Keep the array.<br />
1845
   *                                 <strong>true</strong> => Convert to string var1,var2,var3...<br />
1846
   *                                 <strong>null</strong> => Convert the array into null, every time.
1847
   *
1848
   * @return mixed
1849
   */
1850 71
  public function secure($var, $convert_array = true)
1851
  {
1852 71
    if (\is_array($var)) {
1853 4
      if ($convert_array === null) {
1854
1855
        if ($this->_convert_null_to_empty_string === true) {
1856
          $var = "''";
1857
        } else {
1858
          $var = 'NULL';
1859
        }
1860
1861
      } else {
1862
1863 4
        $varCleaned = [];
1864 4
        foreach ((array)$var as $key => $value) {
1865
1866 4
          $key = $this->escape($key, false, false, $convert_array);
1867 4
          $value = $this->secure($value);
1868
1869
          /** @noinspection OffsetOperationsInspection */
1870 4
          $varCleaned[$key] = $value;
1871
        }
1872
1873 4 View Code Duplication
        if ($convert_array === true) {
1874 4
          $varCleaned = \implode(',', $varCleaned);
1875
1876 4
          $var = $varCleaned;
1877
        } else {
1878
          $var = $varCleaned;
1879
        }
1880
1881
      }
1882
1883 4
      return $var;
1884
    }
1885
1886 71
    if ($var === '') {
1887 4
      return "''";
1888
    }
1889
1890 71
    if ($var === "''") {
1891 2
      return "''";
1892
    }
1893
1894 71
    if ($var === null) {
1895 3
      if ($this->_convert_null_to_empty_string === true) {
1896 2
        return "''";
1897
      }
1898
1899 3
      return 'NULL';
1900
    }
1901
1902 70
    if (\in_array($var, $this->mysqlDefaultTimeFunctions, true)) {
1903 2
      return $var;
1904
    }
1905
1906 70
    if (\is_string($var)) {
1907 59
      $var = \trim($var, "'");
1908
    }
1909
1910 70
    $var = $this->escape($var, false, false, null);
1911
1912 68
    if (\is_string($var)) {
1913 59
      $var = "'" . \trim($var, "'") . "'";
1914
    }
1915
1916 68
    return $var;
1917
  }
1918
1919
  /**
1920
   * Execute a "select"-query.
1921
   *
1922
   * @param string       $table
1923
   * @param string|array $where
1924
   * @param string|null  $databaseName <p>Use <strong>null</strong> if you will use the current database.</p>
1925
   *
1926
   * @return false|Result <p>false on error</p>
1927
   *
1928
   * @throws QueryException
1929
   */
1930 43 View Code Duplication
  public function select(string $table, $where = '1=1', string $databaseName = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1931
  {
1932
    // init
1933 43
    $table = \trim($table);
1934
1935 43
    if ($table === '') {
1936 2
      $this->_debug->displayError('Invalid table name, table name in empty.', false);
1937
1938 2
      return false;
1939
    }
1940
1941 43
    if (\is_string($where)) {
1942 16
      $WHERE = $this->escape($where, false);
1943 29
    } elseif (\is_array($where)) {
1944 29
      $WHERE = $this->_parseArrayPair($where, 'AND');
1945
    } else {
1946 2
      $WHERE = '';
1947
    }
1948
1949 43
    if ($databaseName) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $databaseName of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1950
      $databaseName = $this->quote_string(\trim($databaseName)) . '.';
1951
    }
1952
1953 43
    $sql = 'SELECT * FROM ' . $databaseName . $this->quote_string($table) . " WHERE ($WHERE)";
1954
1955 43
    return $this->query($sql);
1956
  }
1957
1958
  /**
1959
   * Selects a different database than the one specified on construction.
1960
   *
1961
   * @param string $database <p>Database name to switch to.</p>
1962
   *
1963
   * @return bool <p>Boolean true on success, false otherwise.</p>
1964
   */
1965
  public function select_db(string $database): bool
1966
  {
1967
    if (!$this->isReady()) {
1968
      return false;
1969
    }
1970
1971
    return mysqli_select_db($this->link, $database);
1972
  }
1973
1974
  /**
1975
   * @param array $extra_config           <p>
1976
   *                                      'session_to_db' => false|true<br>
1977
   *                                      'socket' => 'string (path)'<br>
1978
   *                                      'ssl' => 'bool'<br>
1979
   *                                      'clientkey' => 'string (path)'<br>
1980
   *                                      'clientcert' => 'string (path)'<br>
1981
   *                                      'cacert' => 'string (path)'<br>
1982
   *                                      </p>
1983
   */
1984 65
  public function setConfigExtra(array $extra_config)
1985
  {
1986 65
    if (isset($extra_config['session_to_db'])) {
1987
      $this->session_to_db = (boolean)$extra_config['session_to_db'];
1988
    }
1989
1990
    if (
1991 65
        isset($extra_config['doctrine'])
1992
        &&
1993 65
        $extra_config['doctrine'] instanceof \Doctrine\DBAL\Connection
1994
    ) {
1995 54
      $this->_doctrine_connection = $extra_config['doctrine'];
1996
    }
1997
1998 65
    if (isset($extra_config['socket'])) {
1999
      $this->socket = $extra_config['socket'];
2000
    }
2001
2002 65
    if (isset($extra_config['ssl'])) {
2003
      $this->_ssl = $extra_config['ssl'];
2004
    }
2005
2006 65
    if (isset($extra_config['clientkey'])) {
2007
      $this->_clientkey = $extra_config['clientkey'];
2008
    }
2009
2010 65
    if (isset($extra_config['clientcert'])) {
2011
      $this->_clientcert = $extra_config['clientcert'];
2012
    }
2013
2014 65
    if (isset($extra_config['cacert'])) {
2015
      $this->_cacert = $extra_config['cacert'];
2016
    }
2017 65
  }
2018
2019
  /**
2020
   * Set the current charset.
2021
   *
2022
   * @param string $charset
2023
   *
2024
   * @return bool
2025
   */
2026 62
  public function set_charset(string $charset): bool
2027
  {
2028 62
    $charsetLower = strtolower($charset);
2029 62
    if ($charsetLower === 'utf8' || $charsetLower === 'utf-8') {
2030 60
      $charset = 'utf8';
2031
    }
2032 62
    if ($charset === 'utf8' && Helper::isUtf8mb4Supported($this) === true) {
2033 60
      $charset = 'utf8mb4';
2034
    }
2035
2036 62
    $this->charset = $charset;
2037
2038 62
    $return = mysqli_set_charset($this->link, $charset);
2039
    /** @noinspection PhpUsageOfSilenceOperatorInspection */
2040 62
    @\mysqli_query($this->link, 'SET CHARACTER SET ' . $charset);
2041
    /** @noinspection PhpUsageOfSilenceOperatorInspection */
2042 62
    @\mysqli_query($this->link, "SET NAMES '" . $charset . "'");
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
2043
2044 62
    return $return;
2045
  }
2046
2047
  /**
2048
   * Set the option to convert null to "''" (empty string).
2049
   *
2050
   * Used in secure() => select(), insert(), update(), delete()
2051
   *
2052
   * @deprecated It's not recommended to convert NULL into an empty string!
2053
   *
2054
   * @param bool $bool
2055
   *
2056
   * @return self
2057
   */
2058 2
  public function set_convert_null_to_empty_string(bool $bool): self
2059
  {
2060 2
    $this->_convert_null_to_empty_string = $bool;
2061
2062 2
    return $this;
2063
  }
2064
2065
  /**
2066
   * Enables or disables internal report functions
2067
   *
2068
   * @link http://php.net/manual/en/function.mysqli-report.php
2069
   *
2070
   * @param int $flags <p>
2071
   *                   <table>
2072
   *                   Supported flags
2073
   *                   <tr valign="top">
2074
   *                   <td>Name</td>
2075
   *                   <td>Description</td>
2076
   *                   </tr>
2077
   *                   <tr valign="top">
2078
   *                   <td><b>MYSQLI_REPORT_OFF</b></td>
2079
   *                   <td>Turns reporting off</td>
2080
   *                   </tr>
2081
   *                   <tr valign="top">
2082
   *                   <td><b>MYSQLI_REPORT_ERROR</b></td>
2083
   *                   <td>Report errors from mysqli function calls</td>
2084
   *                   </tr>
2085
   *                   <tr valign="top">
2086
   *                   <td><b>MYSQLI_REPORT_STRICT</b></td>
2087
   *                   <td>
2088
   *                   Throw <b>mysqli_sql_exception</b> for errors
2089
   *                   instead of warnings
2090
   *                   </td>
2091
   *                   </tr>
2092
   *                   <tr valign="top">
2093
   *                   <td><b>MYSQLI_REPORT_INDEX</b></td>
2094
   *                   <td>Report if no index or bad index was used in a query</td>
2095
   *                   </tr>
2096
   *                   <tr valign="top">
2097
   *                   <td><b>MYSQLI_REPORT_ALL</b></td>
2098
   *                   <td>Set all options (report all)</td>
2099
   *                   </tr>
2100
   *                   </table>
2101
   *                   </p>
2102
   *
2103
   * @return bool
2104
   */
2105
  public function set_mysqli_report(int $flags): bool
2106
  {
2107
    return \mysqli_report($flags);
2108
  }
2109
2110
  /**
2111
   * Show config errors by throw exceptions.
2112
   *
2113
   * @return bool
2114
   *
2115
   * @throws \InvalidArgumentException
2116
   */
2117 65
  public function showConfigError(): bool
2118
  {
2119
    // check if a doctrine connection is already open
2120
    if (
2121 65
        $this->_doctrine_connection
2122
        &&
2123 65
        $this->_doctrine_connection->isConnected()
2124
    ) {
2125 54
      return true;
2126
    }
2127
2128
    if (
2129 17
        !$this->hostname
2130
        ||
2131 15
        !$this->username
2132
        ||
2133 17
        !$this->database
2134
    ) {
2135
2136 6
      if (!$this->hostname) {
2137 2
        throw new \InvalidArgumentException('no-sql-hostname');
2138
      }
2139
2140 4
      if (!$this->username) {
2141 2
        throw new \InvalidArgumentException('no-sql-username');
2142
      }
2143
2144 2
      if (!$this->database) {
2145 2
        throw new \InvalidArgumentException('no-sql-database');
2146
      }
2147
2148
      return false;
2149
    }
2150
2151 11
    return true;
2152
  }
2153
2154
  /**
2155
   * alias: "beginTransaction()"
2156
   */
2157 2
  public function startTransaction(): bool
2158
  {
2159 2
    return $this->beginTransaction();
2160
  }
2161
2162
  /**
2163
   * Determine if database table exists
2164
   *
2165
   * @param string $table
2166
   *
2167
   * @return bool
2168
   */
2169 2
  public function table_exists(string $table): bool
2170
  {
2171 2
    $check = $this->query('SELECT 1 FROM ' . $this->quote_string($table));
2172
2173 2
    return $check !== false
2174
           &&
2175 2
           $check instanceof Result
2176
           &&
2177 2
           $check->num_rows > 0;
2178
  }
2179
2180
  /**
2181
   * Execute a callback inside a transaction.
2182
   *
2183
   * @param callback $callback <p>The callback to run inside the transaction, if it's throws an "Exception" or if it's
2184
   *                           returns "false", all SQL-statements in the callback will be rollbacked.</p>
2185
   *
2186
   * @return bool <p>Boolean true on success, false otherwise.</p>
2187
   */
2188 2
  public function transact($callback): bool
2189
  {
2190
    try {
2191
2192 2
      $beginTransaction = $this->beginTransaction();
2193 2
      if ($beginTransaction === false) {
2194 2
        $this->_debug->displayError('Error: transact -> can not start transaction!', false);
2195
2196 2
        return false;
2197
      }
2198
2199 2
      $result = $callback($this);
2200 2
      if ($result === false) {
2201
        /** @noinspection ThrowRawExceptionInspection */
2202 2
        throw new \Exception('call_user_func [' . $callback . '] === false');
2203
      }
2204
2205 2
      return $this->commit();
2206
2207 2
    } catch (\Exception $e) {
2208
2209 2
      $this->rollback();
2210
2211 2
      return false;
2212
    }
2213
  }
2214
2215
  /**
2216
   * Execute a "update"-query.
2217
   *
2218
   * @param string       $table
2219
   * @param array        $data
2220
   * @param array|string $where
2221
   * @param null|string  $databaseName <p>Use <strong>null</strong> if you will use the current database.</p>
2222
   *
2223
   * @return false|int <p>false on error</p>
2224
   *
2225
   * @throws QueryException
2226
   */
2227 14
  public function update(string $table, array $data = [], $where = '1=1', string $databaseName = null)
2228
  {
2229
    // init
2230 14
    $table = \trim($table);
2231
2232 14
    if ($table === '') {
2233 2
      $this->_debug->displayError('Invalid table name, table name in empty.', false);
2234
2235 2
      return false;
2236
    }
2237
2238 14
    if (\count($data) === 0) {
2239 4
      $this->_debug->displayError('Invalid data for UPDATE, data is empty.', false);
2240
2241 4
      return false;
2242
    }
2243
2244 14
    $SET = $this->_parseArrayPair($data);
2245
2246 14
    if (\is_string($where)) {
2247 4
      $WHERE = $this->escape($where, false);
2248 12
    } elseif (\is_array($where)) {
2249 10
      $WHERE = $this->_parseArrayPair($where, 'AND');
2250
    } else {
2251 2
      $WHERE = '';
2252
    }
2253
2254 14
    if ($databaseName) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $databaseName of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
2255
      $databaseName = $this->quote_string(\trim($databaseName)) . '.';
2256
    }
2257
2258 14
    $sql = 'UPDATE ' . $databaseName . $this->quote_string($table) . " SET $SET WHERE ($WHERE)";
2259
2260 14
    return $this->query($sql);
2261
  }
2262
2263
}
2264