Completed
Push — master ( ace86f...569799 )
by Lars
02:21
created

DB::_loadConfig()   B

Complexity

Conditions 6
Paths 32

Size

Total Lines 59
Code Lines 36

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 6.027

Importance

Changes 0
Metric Value
dl 0
loc 59
ccs 20
cts 22
cp 0.9091
rs 8.7117
c 0
b 0
f 0
cc 6
eloc 36
nc 32
nop 11
crap 6.027

How to fix   Long Method    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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
    if (\is_array($extra_config) === true) {
322
323 65
      $this->setConfigExtra($extra_config);
324
325
    } else {
326
327
      // only for backward compatibility
328
      $this->session_to_db = (boolean)$extra_config;
329
330
    }
331
332 65
    return $this->showConfigError();
333
  }
334
335
  /**
336
   * Parses arrays with value pairs and generates SQL to use in queries.
337
   *
338
   * @param array  $arrayPair
339
   * @param string $glue <p>This is the separator.</p>
340
   *
341
   * @return string
342
   *
343
   * @internal
344
   */
345 51
  public function _parseArrayPair(array $arrayPair, string $glue = ','): string
346
  {
347
    // init
348 51
    $sql = '';
349
350 51
    if (\count($arrayPair) === 0) {
351
      return '';
352
    }
353
354 51
    $arrayPairCounter = 0;
355 51
    foreach ($arrayPair as $_key => $_value) {
356 51
      $_connector = '=';
357 51
      $_glueHelper = '';
358 51
      $_key_upper = \strtoupper($_key);
359
360 51
      if (\strpos($_key_upper, ' NOT') !== false) {
361 4
        $_connector = 'NOT';
362
      }
363
364 51
      if (\strpos($_key_upper, ' IS') !== false) {
365 2
        $_connector = 'IS';
366
      }
367
368 51
      if (\strpos($_key_upper, ' IS NOT') !== false) {
369 2
        $_connector = 'IS NOT';
370
      }
371
372 51
      if (\strpos($_key_upper, ' IN') !== false) {
373 2
        $_connector = 'IN';
374
      }
375
376 51
      if (\strpos($_key_upper, ' NOT IN') !== false) {
377 2
        $_connector = 'NOT IN';
378
      }
379
380 51
      if (\strpos($_key_upper, ' BETWEEN') !== false) {
381 2
        $_connector = 'BETWEEN';
382
      }
383
384 51
      if (\strpos($_key_upper, ' NOT BETWEEN') !== false) {
385 2
        $_connector = 'NOT BETWEEN';
386
      }
387
388 51
      if (\strpos($_key_upper, ' LIKE') !== false) {
389 4
        $_connector = 'LIKE';
390
      }
391
392 51
      if (\strpos($_key_upper, ' NOT LIKE') !== false) {
393 4
        $_connector = 'NOT LIKE';
394
      }
395
396 51 View Code Duplication
      if (\strpos($_key_upper, ' >') !== false && \strpos($_key_upper, ' =') === false) {
397 6
        $_connector = '>';
398
      }
399
400 51 View Code Duplication
      if (\strpos($_key_upper, ' <') !== false && \strpos($_key_upper, ' =') === false) {
401 2
        $_connector = '<';
402
      }
403
404 51
      if (\strpos($_key_upper, ' >=') !== false) {
405 6
        $_connector = '>=';
406
      }
407
408 51
      if (\strpos($_key_upper, ' <=') !== false) {
409 2
        $_connector = '<=';
410
      }
411
412 51
      if (\strpos($_key_upper, ' <>') !== false) {
413 2
        $_connector = '<>';
414
      }
415
416 51
      if (\strpos($_key_upper, ' OR') !== false) {
417 4
        $_glueHelper = 'OR';
418
      }
419
420 51
      if (\strpos($_key_upper, ' AND') !== false) {
421 2
        $_glueHelper = 'AND';
422
      }
423
424 51
      if (\is_array($_value) === true) {
425 4
        foreach ($_value as $oldKey => $oldValue) {
426 4
          $_value[$oldKey] = $this->secure($oldValue);
427
        }
428
429 4
        if ($_connector === 'NOT IN' || $_connector === 'IN') {
430 2
          $_value = '(' . \implode(',', $_value) . ')';
431 4
        } elseif ($_connector === 'NOT BETWEEN' || $_connector === 'BETWEEN') {
432 4
          $_value = '(' . \implode(' AND ', $_value) . ')';
433
        }
434
435
      } else {
436 51
        $_value = $this->secure($_value);
437
      }
438
439 51
      $quoteString = $this->quote_string(
440 51
          \trim(
441 51
              \str_ireplace(
442
                  [
443 51
                      $_connector,
444 51
                      $_glueHelper,
445
                  ],
446 51
                  '',
447 51
                  $_key
448
              )
449
          )
450
      );
451
452 51
      $_value = (array)$_value;
453
454 51
      if (!$_glueHelper) {
455 51
        $_glueHelper = $glue;
456
      }
457
458 51
      $tmpCounter = 0;
459 51
      foreach ($_value as $valueInner) {
460
461 51
        $_glueHelperInner = $_glueHelper;
462
463 51
        if ($arrayPairCounter === 0) {
464
465 51
          if ($tmpCounter === 0 && $_glueHelper === 'OR') {
466 2
            $_glueHelperInner = '1 = 1 AND ('; // first "OR"-query glue
467 51
          } elseif ($tmpCounter === 0) {
468 51
            $_glueHelperInner = ''; // first query glue e.g. for "INSERT"-query -> skip the first ","
469
          }
470
471 47
        } elseif ($tmpCounter === 0 && $_glueHelper === 'OR') {
472 2
          $_glueHelperInner = 'AND ('; // inner-loop "OR"-query glue
473
        }
474
475 51
        if (\is_string($valueInner) && $valueInner === '') {
476
          $valueInner = "''";
477
        }
478
479 51
        $sql .= ' ' . $_glueHelperInner . ' ' . $quoteString . ' ' . $_connector . ' ' . $valueInner . " \n";
480 51
        $tmpCounter++;
481
      }
482
483 51
      if ($_glueHelper === 'OR') {
484 4
        $sql .= ' ) ';
485
      }
486
487 51
      $arrayPairCounter++;
488
    }
489
490 51
    return $sql;
491
  }
492
493
  /**
494
   * _parseQueryParams
495
   *
496
   * @param string $sql
497
   * @param array  $params
498
   *
499
   * @return array <p>with the keys -> 'sql', 'params'</p>
500
   */
501 15
  private function _parseQueryParams(string $sql, array $params = []): array
502
  {
503 15
    $offset = \strpos($sql, '?');
504
505
    // is there anything to parse?
506
    if (
507 15
        $offset === false
508
        ||
509 15
        \count($params) === 0
510
    ) {
511 12
      return ['sql' => $sql, 'params' => $params];
512
    }
513
514 5
    foreach ($params as $key => $param) {
515
516
      // use this only for not named parameters
517 5
      if (!is_int($key)) {
518 2
        continue;
519
      }
520
521 5
      if ($offset === false) {
522
        continue;
523
      }
524
525 5
      $replacement = $this->secure($param);
526
527 5
      unset($params[$key]);
528
529 5
      $sql = \substr_replace($sql, $replacement, $offset, 1);
530 5
      $offset = \strpos($sql, '?', $offset + \strlen((string)$replacement));
531
    }
532
533 5
    return ['sql' => $sql, 'params' => $params];
534
  }
535
536
  /**
537
   * Returns the SQL by replacing :placeholders with SQL-escaped values.
538
   *
539
   * @param mixed $sql    <p>The SQL string.</p>
540
   * @param array $params <p>An array of key-value bindings.</p>
541
   *
542
   * @return array <p>with the keys -> 'sql', 'params'</p>
543
   */
544 17
  private function _parseQueryParamsByName(string $sql, array $params = []): array
545
  {
546
    // is there anything to parse?
547
    if (
548 17
        \strpos($sql, ':') === false
549
        ||
550 17
        \count($params) === 0
551
    ) {
552 5
      return ['sql' => $sql, 'params' => $params];
553
    }
554
555 14
    $offset = null;
556 14
    $replacement = null;
557 14
    foreach ($params as $name => $param) {
558
559
      // use this only for named parameters
560 14
      if (is_int($name)) {
561
        continue;
562
      }
563
564
      // add ":" if needed
565 14
      if (\strpos($name, ':') !== 0) {
566 4
        $nameTmp = ':' . $name;
567
      } else {
568 10
        $nameTmp = $name;
569
      }
570
571 14
      if ($offset === null) {
572 14
        $offset = \strpos($sql, $nameTmp);
573
      } else {
574 13
        $offset = \strpos($sql, $nameTmp, $offset + \strlen((string)$replacement));
575
      }
576
577 14
      if ($offset === false) {
578 2
        continue;
579
      }
580
581 14
      $replacement = $this->secure($param);
582
583 14
      unset($params[$name]);
584
585 14
      $sql = \substr_replace($sql, $replacement, $offset, \strlen($nameTmp));
586
    }
587
588 14
    return ['sql' => $sql, 'params' => $params];
589
  }
590
591
  /**
592
   * Gets the number of affected rows in a previous MySQL operation.
593
   *
594
   * @return int
595
   */
596 20
  public function affected_rows(): int
597
  {
598 20
    return \mysqli_affected_rows($this->link);
599
  }
600
601
  /**
602
   * Begins a transaction, by turning off auto commit.
603
   *
604
   * @return bool <p>This will return true or false indicating success of transaction</p>
605
   */
606 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...
607
  {
608 12
    if ($this->_in_transaction === true) {
609 4
      $this->_debug->displayError('Error: mysql server already in transaction!', false);
610
611 4
      return false;
612
    }
613
614 12
    $this->clearErrors(); // needed for "$this->endTransaction()"
615 12
    $this->_in_transaction = true;
616 12
    $return = \mysqli_autocommit($this->link, false);
617 12
    if ($return === false) {
618
      $this->_in_transaction = false;
619
    }
620
621 12
    return $return;
622
  }
623
624
  /**
625
   * Clear the errors in "_debug->_errors".
626
   *
627
   * @return bool
628
   */
629 12
  public function clearErrors(): bool
630
  {
631 12
    return $this->_debug->clearErrors();
632
  }
633
634
  /**
635
   * Closes a previously opened database connection.
636
   */
637 4
  public function close(): bool
638
  {
639 4
    $this->connected = false;
640
641 4
    if ($this->_doctrine_connection) {
642
643 2
      $connectedBefore = $this->_doctrine_connection->isConnected();
644
645 2
      $this->_doctrine_connection->close();
646 2
      $this->link = null;
647
648 2
      return ($connectedBefore === true ? !$this->_doctrine_connection->isConnected() : false);
649
    }
650
651
    if (
652 2
        $this->link
653
        &&
654 2
        $this->link instanceof \mysqli
655
    ) {
656 2
      $result = \mysqli_close($this->link);
657 2
      $this->link = null;
658
659 2
      return $result;
660
    }
661
662 1
    $this->link = null;
663
664 1
    return false;
665
  }
666
667
  /**
668
   * Commits the current transaction and end the transaction.
669
   *
670
   * @return bool <p>Boolean true on success, false otherwise.</p>
671
   */
672 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...
673
  {
674 4
    if ($this->_in_transaction === false) {
675
      $this->_debug->displayError('Error: mysql server is not in transaction!', false);
676
677
      return false;
678
    }
679
680 4
    $return = \mysqli_commit($this->link);
681 4
    \mysqli_autocommit($this->link, true);
682 4
    $this->_in_transaction = false;
683
684 4
    return $return;
685
  }
686
687
  /**
688
   * Open a new connection to the MySQL server.
689
   *
690
   * @return bool
691
   *
692
   * @throws DBConnectException
693
   */
694 64
  public function connect(): bool
695
  {
696 64
    if ($this->isReady()) {
697 2
      return true;
698
    }
699
700 64
    if ($this->_doctrine_connection) {
701 54
      $this->_doctrine_connection->connect();
702
703 54
      if (method_exists($this->_doctrine_connection, 'getWrappedConnection')) {
704 54
        $doctrineMySQLi = $this->_doctrine_connection->getWrappedConnection();
705 54
        if ($doctrineMySQLi instanceof \Doctrine\DBAL\Driver\Mysqli\MysqliConnection) {
706 54
          $this->link = $doctrineMySQLi->getWrappedResourceHandle();
707
708 54
          $this->connected = $this->_doctrine_connection->isConnected();
709
710 54
          if (!$this->connected) {
711
            $error = 'Error connecting to mysql server: ' . $this->_doctrine_connection->errorInfo();
712
            $this->_debug->displayError($error, false);
713
            throw new DBConnectException($error, 101);
714
          }
715
716 54
          $this->set_charset($this->charset);
717
718 54
          return $this->isReady();
719
        }
720
      }
721
    }
722
723 14
    $flags = null;
724
725 14
    \mysqli_report(MYSQLI_REPORT_STRICT);
726
    try {
727 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...
728
729 14
      if (Helper::isMysqlndIsUsed() === true) {
730 14
        \mysqli_options($this->link, MYSQLI_OPT_INT_AND_FLOAT_NATIVE, true);
731
      }
732
733 14
      if ($this->_ssl === true) {
734
735
        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...
736
          throw new DBConnectException('Error connecting to mysql server: clientcert not defined');
737
        }
738
739
        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...
740
          throw new DBConnectException('Error connecting to mysql server: clientkey not defined');
741
        }
742
743
        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...
744
          throw new DBConnectException('Error connecting to mysql server: cacert not defined');
745
        }
746
747
        \mysqli_options($this->link, MYSQLI_OPT_SSL_VERIFY_SERVER_CERT, true);
748
749
        /** @noinspection PhpParamsInspection */
750
        \mysqli_ssl_set(
751
            $this->link,
752
            $this->_clientkey,
753
            $this->_clientcert,
754
            $this->_cacert,
755
            null,
756
            null
757
        );
758
759
        $flags = MYSQLI_CLIENT_SSL;
760
      }
761
762
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
763 14
      $this->connected = @\mysqli_real_connect(
764 14
          $this->link,
765 14
          $this->hostname,
766 14
          $this->username,
767 14
          $this->password,
768 14
          $this->database,
769 14
          $this->port,
770 14
          $this->socket,
771 14
          (int)$flags
772
      );
773
774 6
    } catch (\Exception $e) {
775 6
      $error = 'Error connecting to mysql server: ' . $e->getMessage();
776 6
      $this->_debug->displayError($error, false);
777 6
      throw new DBConnectException($error, 100, $e);
778
    }
779 8
    \mysqli_report(MYSQLI_REPORT_OFF);
780
781 8
    $errno = \mysqli_connect_errno();
782 8
    if (!$this->connected || $errno) {
783
      $error = 'Error connecting to mysql server: ' . \mysqli_connect_error() . ' (' . $errno . ')';
784
      $this->_debug->displayError($error, false);
785
      throw new DBConnectException($error, 101);
786
    }
787
788 8
    $this->set_charset($this->charset);
789
790 8
    return $this->isReady();
791
  }
792
793
  /**
794
   * Execute a "delete"-query.
795
   *
796
   * @param string       $table
797
   * @param string|array $where
798
   * @param string|null  $databaseName <p>Use <strong>null</strong> if you will use the current database.</p>
799
   *
800
   * @return false|int <p>false on error</p>
801
   *
802
   * @throws QueryException
803
   */
804 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...
805
  {
806
    // init
807 3
    $table = \trim($table);
808
809 3
    if ($table === '') {
810 2
      $this->_debug->displayError('Invalid table name, table name in empty.', false);
811
812 2
      return false;
813
    }
814
815 3
    if (\is_string($where)) {
816 2
      $WHERE = $this->escape($where, false);
817 3
    } elseif (\is_array($where)) {
818 3
      $WHERE = $this->_parseArrayPair($where, 'AND');
819
    } else {
820 2
      $WHERE = '';
821
    }
822
823 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...
824
      $databaseName = $this->quote_string(\trim($databaseName)) . '.';
825
    }
826
827 3
    $sql = 'DELETE FROM ' . $databaseName . $this->quote_string($table) . " WHERE ($WHERE)";
828
829 3
    return $this->query($sql);
830
  }
831
832
  /**
833
   * Ends a transaction and commits if no errors, then ends autocommit.
834
   *
835
   * @return bool <p>This will return true or false indicating success of transactions.</p>
836
   */
837 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...
838
  {
839 8
    if ($this->_in_transaction === false) {
840
      $this->_debug->displayError('Error: mysql server is not in transaction!', false);
841
842
      return false;
843
    }
844
845 8
    if (!$this->errors()) {
846 2
      $return = \mysqli_commit($this->link);
847
    } else {
848 6
      $this->rollback();
849 6
      $return = false;
850
    }
851
852 8
    \mysqli_autocommit($this->link, true);
853 8
    $this->_in_transaction = false;
854
855 8
    return $return;
856
  }
857
858
  /**
859
   * Get all errors from "$this->_errors".
860
   *
861
   * @return array|false <p>false === on errors</p>
862
   */
863 8
  public function errors()
864
  {
865 8
    $errors = $this->_debug->getErrors();
866
867 8
    return \count($errors) > 0 ? $errors : false;
868
  }
869
870
  /**
871
   * Escape: Use "mysqli_real_escape_string" and clean non UTF-8 chars + some extra optional stuff.
872
   *
873
   * @param mixed     $var           boolean: convert into "integer"<br />
874
   *                                 int: int (don't change it)<br />
875
   *                                 float: float (don't change it)<br />
876
   *                                 null: null (don't change it)<br />
877
   *                                 array: run escape() for every key => value<br />
878
   *                                 string: run UTF8::cleanup() and mysqli_real_escape_string()<br />
879
   * @param bool      $stripe_non_utf8
880
   * @param bool      $html_entity_decode
881
   * @param bool|null $convert_array <strong>false</strong> => Keep the array.<br />
882
   *                                 <strong>true</strong> => Convert to string var1,var2,var3...<br />
883
   *                                 <strong>null</strong> => Convert the array into null, every time.
884
   *
885
   * @return mixed
886
   */
887 87
  public function escape($var = '', bool $stripe_non_utf8 = true, bool $html_entity_decode = false, $convert_array = false)
888
  {
889
    // [empty]
890 87
    if ($var === '') {
891 4
      return '';
892
    }
893
894
    // ''
895 87
    if ($var === "''") {
896
      return "''";
897
    }
898
899
    // check the type
900 87
    $type = gettype($var);
901
902 87
    if ($type === 'object') {
903 6
      if ($var instanceof \DateTime) {
904 6
        $var = $var->format('Y-m-d H:i:s');
905 6
        $type = 'string';
906 4
      } elseif (\method_exists($var, '__toString')) {
907 4
        $var = (string)$var;
908 4
        $type = 'string';
909
      }
910
    }
911
912 87
    switch ($type) {
913 87
      case 'boolean':
914 6
        $var = (int)$var;
915 6
        break;
916
917 87
      case 'double':
918 87
      case 'integer':
919 51
        break;
920
921 82
      case 'string':
922 82
        if ($stripe_non_utf8 === true) {
923 17
          $var = UTF8::cleanup($var);
924
        }
925
926 82
        if ($html_entity_decode === true) {
927 2
          $var = UTF8::html_entity_decode($var);
928
        }
929
930 82
        $var = \get_magic_quotes_gpc() ? \stripslashes($var) : $var;
931
932 82
        $var = \mysqli_real_escape_string($this->link, $var);
933
934 82
        break;
935
936 8
      case 'array':
937 8
        if ($convert_array === null) {
938
939 6
          if ($this->_convert_null_to_empty_string === true) {
940
            $var = "''";
941
          } else {
942 6
            $var = 'NULL';
943
          }
944
945
        } else {
946
947 4
          $varCleaned = [];
948 4
          foreach ((array)$var as $key => $value) {
949
950 4
            $key = $this->escape($key, $stripe_non_utf8, $html_entity_decode);
951 4
            $value = $this->escape($value, $stripe_non_utf8, $html_entity_decode);
952
953
            /** @noinspection OffsetOperationsInspection */
954 4
            $varCleaned[$key] = $value;
955
          }
956
957 4
          if ($convert_array === true) {
958 2
            $varCleaned = \implode(',', $varCleaned);
959
960 2
            $var = $varCleaned;
961
          } else {
962 4
            $var = $varCleaned;
963
          }
964
965
        }
966 8
        break;
967
968 6
      case 'NULL':
969 4
        if ($this->_convert_null_to_empty_string === true) {
970
          $var = "''";
971
        } else {
972 4
          $var = 'NULL';
973
        }
974 4
        break;
975
976
      default:
977 4
        throw new \InvalidArgumentException(sprintf('Not supported value "%s" of type %s.', print_r($var, true), $type));
978
        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...
979
    }
980
981 87
    return $var;
982
  }
983
984
  /**
985
   * Execute select/insert/update/delete sql-queries.
986
   *
987
   * @param string  $query    <p>sql-query</p>
988
   * @param bool    $useCache optional <p>use cache?</p>
989
   * @param int     $cacheTTL optional <p>cache-ttl in seconds</p>
990
   * @param DB|null $db       optional <p>the database connection</p>
991
   *
992
   * @return mixed "array" by "<b>SELECT</b>"-queries<br />
993
   *               "int" (insert_id) by "<b>INSERT</b>"-queries<br />
994
   *               "int" (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
995
   *               "true" by e.g. "DROP"-queries<br />
996
   *               "false" on error
997
   *
998
   * @throws QueryException
999
   */
1000 6
  public static function execSQL(string $query, bool $useCache = false, int $cacheTTL = 3600, self $db = null)
1001
  {
1002
    // init
1003 6
    $cacheKey = null;
1004 6
    if (!$db) {
1005 6
      $db = self::getInstance();
1006
    }
1007
1008 6 View Code Duplication
    if ($useCache === true) {
1009 2
      $cache = new Cache(null, null, false, $useCache);
1010 2
      $cacheKey = 'sql-' . \md5($query);
1011
1012
      if (
1013 2
          $cache->getCacheIsReady() === true
1014
          &&
1015 2
          $cache->existsItem($cacheKey)
1016
      ) {
1017 2
        return $cache->getItem($cacheKey);
1018
      }
1019
1020
    } else {
1021 6
      $cache = false;
1022
    }
1023
1024 6
    $result = $db->query($query);
1025
1026 6
    if ($result instanceof Result) {
1027
1028 2
      $return = $result->fetchAllArray();
1029
1030
      // save into the cache
1031 View Code Duplication
      if (
1032 2
          $cacheKey !== null
1033
          &&
1034 2
          $useCache === true
1035
          &&
1036 2
          $cache instanceof Cache
1037
          &&
1038 2
          $cache->getCacheIsReady() === true
1039
      ) {
1040 2
        $cache->setItem($cacheKey, $return, $cacheTTL);
1041
      }
1042
1043
    } else {
1044 4
      $return = $result;
1045
    }
1046
1047 6
    return $return;
1048
  }
1049
1050
  /**
1051
   * Get all table-names via "SHOW TABLES".
1052
   *
1053
   * @return array
1054
   */
1055 2
  public function getAllTables(): array
1056
  {
1057 2
    $query = 'SHOW TABLES';
1058 2
    $result = $this->query($query);
1059
1060 2
    return $result->fetchAllArray();
1061
  }
1062
1063
  /**
1064
   * @return Debug
1065
   */
1066 9
  public function getDebugger(): Debug
1067
  {
1068 9
    return $this->_debug;
1069
  }
1070
1071
  /**
1072
   * @return \Doctrine\DBAL\Connection|null
1073
   */
1074
  public function getDoctrineConnection()
1075
  {
1076
    return $this->_doctrine_connection;
1077
  }
1078
1079
  /**
1080
   * Get errors from "$this->_errors".
1081
   *
1082
   * @return array
1083
   */
1084
  public function getErrors(): array
1085
  {
1086
    return $this->_debug->getErrors();
1087
  }
1088
1089
  /**
1090
   * getInstance()
1091
   *
1092
   * @param string $hostname
1093
   * @param string $username
1094
   * @param string $password
1095
   * @param string $database
1096
   * @param int    $port                 <p>default is (int)3306</p>
1097
   * @param string $charset              <p>default is 'utf8' or 'utf8mb4' (if supported)</p>
1098
   * @param bool   $exit_on_error        <p>Throw a 'Exception' when a query failed, otherwise it will return 'false'.
1099
   *                                     Use false to disable it.</p>
1100
   * @param bool   $echo_on_error        <p>Echo the error if "checkForDev()" returns true.
1101
   *                                     Use false to disable it.</p>
1102
   * @param string $logger_class_name
1103
   * @param string $logger_level         <p>'TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL'</p>
1104
   * @param array  $extra_config         <p>
1105
   *                                     're_connect'    => bool<br>
1106
   *                                     'session_to_db' => bool<br>
1107
   *                                     'doctrine'      => \Doctrine\DBAL\Connection<br>
1108
   *                                     'socket'        => 'string (path)'<br>
1109
   *                                     'ssl'           => bool<br>
1110
   *                                     'clientkey'     => 'string (path)'<br>
1111
   *                                     'clientcert'    => 'string (path)'<br>
1112
   *                                     'cacert'        => 'string (path)'<br>
1113
   *                                     </p>
1114
   *
1115
   * @return self
1116
   */
1117 173
  public static function getInstance(
1118
      string $hostname = '',
1119
      string $username = '',
1120
      string $password = '',
1121
      string $database = '',
1122
      $port = 3306,
1123
      string $charset = 'utf8',
1124
      bool $exit_on_error = true,
1125
      bool $echo_on_error = true,
1126
      string $logger_class_name = '',
1127
      string $logger_level = '',
1128
      array $extra_config = []
1129
  ): self
1130
  {
1131
    /**
1132
     * @var $instance self[]
1133
     */
1134 173
    static $instance = [];
1135
1136
    /**
1137
     * @var $firstInstance self
1138
     */
1139 173
    static $firstInstance = null;
1140
1141
    // fallback
1142 173
    if (!$charset) {
1143 65
      $charset = 'utf8';
1144
    }
1145
1146
    if (
1147 173
        $hostname . $username . $password . $database . $port . $charset == '3306utf8'
1148
        &&
1149 173
        null !== $firstInstance
1150
    ) {
1151 40
      if (isset($extra_config['re_connect']) && $extra_config['re_connect'] === true) {
1152
        $firstInstance->reconnect(true);
1153
      }
1154
1155 40
      return $firstInstance;
1156
    }
1157
1158 173
    $extra_config_string = '';
1159 173
    if (\is_array($extra_config) === true) {
1160 173
      foreach ($extra_config as $extra_config_key => $extra_config_value) {
1161 54
        if (\is_object($extra_config_value)) {
1162 54
          $extra_config_value_tmp = \spl_object_hash($extra_config_value);
1163
        } else {
1164
          $extra_config_value_tmp = (string)$extra_config_value;
1165
        }
1166 173
        $extra_config_string .= $extra_config_key . $extra_config_value_tmp;
1167
      }
1168
    } else {
1169
      // only for backward compatibility
1170
      $extra_config_string = (int)$extra_config;
1171
    }
1172
1173 173
    $connection = \md5(
1174 173
        $hostname . $username . $password . $database . $port . $charset . (int)$exit_on_error . (int)$echo_on_error . $logger_class_name . $logger_level . $extra_config_string
1175
    );
1176
1177 173
    if (!isset($instance[$connection])) {
1178 65
      $instance[$connection] = new self(
1179 65
          $hostname,
1180 65
          $username,
1181 65
          $password,
1182 65
          $database,
1183 65
          $port,
1184 65
          $charset,
1185 65
          $exit_on_error,
1186 65
          $echo_on_error,
1187 65
          $logger_class_name,
1188 65
          $logger_level,
1189 65
          $extra_config
1190
      );
1191
1192 59
      if (null === $firstInstance) {
1193 1
        $firstInstance = $instance[$connection];
1194
      }
1195
    }
1196
1197 173
    if (isset($extra_config['re_connect']) && $extra_config['re_connect'] === true) {
1198
      $instance[$connection]->reconnect(true);
1199
    }
1200
1201 173
    return $instance[$connection];
1202
  }
1203
1204
  /**
1205
   * Get the mysqli-link (link identifier returned by mysqli-connect).
1206
   *
1207
   * @return \mysqli
1208
   */
1209 38
  public function getLink(): \mysqli
1210
  {
1211 38
    return $this->link;
1212
  }
1213
1214
  /**
1215
   * Get the current charset.
1216
   *
1217
   * @return string
1218
   */
1219 2
  public function get_charset(): string
1220
  {
1221 2
    return $this->charset;
1222
  }
1223
1224
  /**
1225
   * Check if we are in a transaction.
1226
   *
1227
   * @return bool
1228
   */
1229
  public function inTransaction(): bool
1230
  {
1231
    return $this->_in_transaction;
1232
  }
1233
1234
  /**
1235
   * Execute a "insert"-query.
1236
   *
1237
   * @param string      $table
1238
   * @param array       $data
1239
   * @param string|null $databaseName <p>Use <strong>null</strong> if you will use the current database.</p>
1240
   *
1241
   * @return false|int <p>false on error</p>
1242
   *
1243
   * @throws QueryException
1244
   */
1245 51
  public function insert(string $table, array $data = [], string $databaseName = null)
1246
  {
1247
    // init
1248 51
    $table = \trim($table);
1249
1250 51
    if ($table === '') {
1251 4
      $this->_debug->displayError('Invalid table name, table name in empty.', false);
1252
1253 4
      return false;
1254
    }
1255
1256 49
    if (\count($data) === 0) {
1257 6
      $this->_debug->displayError('Invalid data for INSERT, data is empty.', false);
1258
1259 6
      return false;
1260
    }
1261
1262 45
    $SET = $this->_parseArrayPair($data);
1263
1264 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...
1265
      $databaseName = $this->quote_string(\trim($databaseName)) . '.';
1266
    }
1267
1268 45
    $sql = 'INSERT INTO ' . $databaseName . $this->quote_string($table) . " SET $SET";
1269
1270 45
    return $this->query($sql);
1271
  }
1272
1273
  /**
1274
   * Returns the auto generated id used in the last query.
1275
   *
1276
   * @return int|string
1277
   */
1278 81
  public function insert_id()
1279
  {
1280 81
    return \mysqli_insert_id($this->link);
1281
  }
1282
1283
  /**
1284
   * Check if db-connection is ready.
1285
   *
1286
   * @return boolean
1287
   */
1288 160
  public function isReady(): bool
1289
  {
1290 160
    return $this->connected ? true : false;
1291
  }
1292
1293
  /**
1294
   * Get the last sql-error.
1295
   *
1296
   * @return string|false <p>false === there was no error</p>
1297
   */
1298
  public function lastError()
1299
  {
1300
    $errors = $this->_debug->getErrors();
1301
1302
    return \count($errors) > 0 ? end($errors) : false;
1303
  }
1304
1305
  /**
1306
   * Execute a sql-multi-query.
1307
   *
1308
   * @param string $sql
1309
   *
1310
   * @return false|Result[] "Result"-Array by "<b>SELECT</b>"-queries<br />
1311
   *                        "boolean" by only "<b>INSERT</b>"-queries<br />
1312
   *                        "boolean" by only (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
1313
   *                        "boolean" by only by e.g. "DROP"-queries<br />
1314
   *
1315
   * @throws QueryException
1316
   */
1317 2
  public function multi_query(string $sql)
1318
  {
1319 2
    if (!$this->isReady()) {
1320
      return false;
1321
    }
1322
1323 2 View Code Duplication
    if (!$sql || $sql === '') {
1324 2
      $this->_debug->displayError('Can not execute an empty query.', false);
1325
1326 2
      return false;
1327
    }
1328
1329 2
    $query_start_time = microtime(true);
1330 2
    $resultTmp = \mysqli_multi_query($this->link, $sql);
1331 2
    $query_duration = microtime(true) - $query_start_time;
1332
1333 2
    $this->_debug->logQuery($sql, $query_duration, 0);
1334
1335 2
    $returnTheResult = false;
1336 2
    $result = [];
1337
1338 2
    if ($resultTmp) {
1339
      do {
1340
1341 2
        $resultTmpInner = \mysqli_store_result($this->link);
1342
1343 2
        if ($resultTmpInner instanceof \mysqli_result) {
1344
1345 2
          $returnTheResult = true;
1346 2
          $result[] = new Result($sql, $resultTmpInner);
1347
1348
        } else {
1349
1350
          // is the query successful
1351 2
          if ($resultTmpInner === true || !\mysqli_errno($this->link)) {
1352 2
            $result[] = true;
1353
          } else {
1354
            $result[] = false;
1355
          }
1356
1357
        }
1358
1359 2
      } while (\mysqli_more_results($this->link) === true ? \mysqli_next_result($this->link) : false);
1360
1361
    } else {
1362
1363
      // log the error query
1364 2
      $this->_debug->logQuery($sql, $query_duration, 0, true);
1365
1366 2
      return $this->queryErrorHandling(\mysqli_error($this->link), \mysqli_errno($this->link), $sql, false, true);
1367
    }
1368
1369
    // return the result only if there was a "SELECT"-query
1370 2
    if ($returnTheResult === true) {
1371 2
      return $result;
1372
    }
1373
1374
    if (
1375 2
        \count($result) > 0
1376
        &&
1377 2
        \in_array(false, $result, true) === false
1378
    ) {
1379 2
      return true;
1380
    }
1381
1382
    return false;
1383
  }
1384
1385
  /**
1386
   * Count number of rows found matching a specific query.
1387
   *
1388
   * @param string $query
1389
   *
1390
   * @return int
1391
   */
1392 2
  public function num_rows(string $query): int
1393
  {
1394 2
    $check = $this->query($query);
1395
1396
    if (
1397 2
        $check === false
1398
        ||
1399 2
        !$check instanceof Result
1400
    ) {
1401
      return 0;
1402
    }
1403
1404 2
    return $check->num_rows;
1405
  }
1406
1407
  /**
1408
   * Pings a server connection, or tries to reconnect
1409
   * if the connection has gone down.
1410
   *
1411
   * @return boolean
1412
   */
1413 6
  public function ping(): bool
1414
  {
1415 6
    if ($this->_doctrine_connection) {
1416
1417
      // this check is needed, but I don't know why "ping" isn't working?
1418 3
      if (!$this->_doctrine_connection->isConnected()) {
1419 1
        return false;
1420
      }
1421
1422 2
      return $this->_doctrine_connection->ping();
1423
    }
1424
1425
    if (
1426 3
        $this->link
1427
        &&
1428 3
        $this->link instanceof \mysqli
1429
    ) {
1430
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
1431 2
      return (bool)@\mysqli_ping($this->link);
1432
    }
1433
1434 1
    return false;
1435
  }
1436
1437
  /**
1438
   * Get a new "Prepare"-Object for your sql-query.
1439
   *
1440
   * @param string $query
1441
   *
1442
   * @return Prepare
1443
   */
1444 2
  public function prepare(string $query): Prepare
1445
  {
1446 2
    return new Prepare($this, $query);
1447
  }
1448
1449
  /**
1450
   * Execute a sql-query and return the result-array for select-statements.
1451
   *
1452
   * @param string $query
1453
   *
1454
   * @return mixed
1455
   * @deprecated
1456
   * @throws \Exception
1457
   */
1458 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...
1459
  {
1460 2
    $db = self::getInstance();
1461
1462 2
    $args = \func_get_args();
1463
    /** @noinspection SuspiciousAssignmentsInspection */
1464 2
    $query = \array_shift($args);
1465 2
    $query = \str_replace('?', '%s', $query);
1466 2
    $args = \array_map(
1467
        [
1468 2
            $db,
1469 2
            'escape',
1470
        ],
1471 2
        $args
1472
    );
1473 2
    \array_unshift($args, $query);
1474 2
    $query = \sprintf(...$args);
1475 2
    $result = $db->query($query);
1476
1477 2
    if ($result instanceof Result) {
1478 2
      return $result->fetchAllArray();
1479
    }
1480
1481 2
    return $result;
1482
  }
1483
1484
  /**
1485
   * Execute a sql-query.
1486
   *
1487
   * @param string        $sql            <p>The sql query-string.</p>
1488
   *
1489
   * @param array|boolean $params         <p>
1490
   *                                      "array" of sql-query-parameters<br/>
1491
   *                                      "false" if you don't need any parameter (default)<br/>
1492
   *                                      </p>
1493
   *
1494
   * @return bool|int|Result              <p>
1495
   *                                      "Result" by "<b>SELECT</b>"-queries<br />
1496
   *                                      "int" (insert_id) by "<b>INSERT / REPLACE</b>"-queries<br />
1497
   *                                      "int" (affected_rows) by "<b>UPDATE / DELETE</b>"-queries<br />
1498
   *                                      "true" by e.g. "DROP"-queries<br />
1499
   *                                      "false" on error
1500
   *                                      </p>
1501
   *
1502
   * @throws QueryException
1503
   */
1504 130
  public function query(string $sql = '', $params = false)
1505
  {
1506 130
    if (!$this->isReady()) {
1507
      return false;
1508
    }
1509
1510 130 View Code Duplication
    if (!$sql || $sql === '') {
1511 8
      $this->_debug->displayError('Can not execute an empty query.', false);
1512
1513 8
      return false;
1514
    }
1515
1516
    if (
1517 126
        $params !== false
1518
        &&
1519 126
        \is_array($params)
1520
        &&
1521 126
        \count($params) > 0
1522
    ) {
1523 15
      $parseQueryParams = $this->_parseQueryParams($sql, $params);
1524 15
      $parseQueryParamsByName = $this->_parseQueryParamsByName($parseQueryParams['sql'], $parseQueryParams['params']);
1525 15
      $sql = $parseQueryParamsByName['sql'];
1526
    }
1527
1528
    // DEBUG
1529
    // var_dump($params);
1530
    // echo $sql . "\n";
1531
1532 126
    $query_start_time = microtime(true);
1533 126
    $queryException = null;
1534 126
    $query_result_doctrine = false;
1535
1536 126
    if ($this->_doctrine_connection) {
1537
1538
      try {
1539 28
        $query_result_doctrine = $this->_doctrine_connection->prepare($sql);
1540 28
        $query_result = $query_result_doctrine->execute();
1541 28
        $mysqli_field_count = $query_result_doctrine->columnCount();
1542 8
      } catch (\Exception $e) {
1543 8
        $query_result = false;
1544 8
        $mysqli_field_count = null;
1545
1546 28
        $queryException = $e;
1547
      }
1548
1549
    } else {
1550
1551 100
      $query_result = \mysqli_real_query($this->link, $sql);
1552 100
      $mysqli_field_count = \mysqli_field_count($this->link);
1553
1554
    }
1555
1556 126
    $query_duration = microtime(true) - $query_start_time;
1557
1558 126
    $this->query_count++;
1559
1560 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...
1561
1562 89
      if ($this->_doctrine_connection) {
1563
1564 24
        $result = false;
1565
        if (
1566 24
            $query_result_doctrine
1567
            &&
1568 24
            $query_result_doctrine instanceof \Doctrine\DBAL\Statement
1569
        ) {
1570 24
          $result = $query_result_doctrine;
1571
        }
1572
1573
      } else {
1574
1575 89
        $result = \mysqli_store_result($this->link);
1576
1577
      }
1578
    } else {
1579 91
      $result = $query_result;
1580
    }
1581
1582
    if (
1583 126
        $result instanceof \Doctrine\DBAL\Statement
1584
        &&
1585 126
        $result->columnCount() > 0
1586
    ) {
1587
1588
      // log the select query
1589 24
      $this->_debug->logQuery($sql, $query_duration, $mysqli_field_count);
1590
1591
      // return query result object
1592 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...
1593
    }
1594
1595 122
    if ($result instanceof \mysqli_result) {
1596
1597
      // log the select query
1598 63
      $this->_debug->logQuery($sql, $query_duration, $mysqli_field_count);
1599
1600
      // return query result object
1601 63
      return new Result($sql, $result);
1602
    }
1603
1604 93
    if ($query_result === true) {
1605
1606
      // "INSERT" || "REPLACE"
1607 87 View Code Duplication
      if (preg_match('/^\s*?(?:INSERT|REPLACE)\s+/i', $sql)) {
1608 81
        $insert_id = (int)$this->insert_id();
1609 81
        $this->_debug->logQuery($sql, $query_duration, $insert_id);
1610
1611 81
        return $insert_id;
1612
      }
1613
1614
      // "UPDATE" || "DELETE"
1615 46 View Code Duplication
      if (preg_match('/^\s*?(?:UPDATE|DELETE)\s+/i', $sql)) {
1616 20
        $affected_rows = $this->affected_rows();
1617 20
        $this->_debug->logQuery($sql, $query_duration, $affected_rows);
1618
1619 20
        return $affected_rows;
1620
      }
1621
1622
      // log the ? query
1623 27
      $this->_debug->logQuery($sql, $query_duration, 0);
1624
1625 27
      return true;
1626
    }
1627
1628
    // log the error query
1629 22
    $this->_debug->logQuery($sql, $query_duration, 0, true);
1630
1631 22
    if ($queryException) {
1632 8
      return $this->queryErrorHandling($queryException->getMessage(), $queryException->getCode(), $sql, $params);
1633
    }
1634
1635 14
    return $this->queryErrorHandling(\mysqli_error($this->link), \mysqli_errno($this->link), $sql, $params);
1636
  }
1637
1638
  /**
1639
   * Error-handling for the sql-query.
1640
   *
1641
   * @param string     $errorMessage
1642
   * @param int        $errorNumber
1643
   * @param string     $sql
1644
   * @param array|bool $sqlParams <p>false if there wasn't any parameter</p>
1645
   * @param bool       $sqlMultiQuery
1646
   *
1647
   * @return mixed|false
1648
   *
1649
   * @throws QueryException
1650
   * @throws DBGoneAwayException
1651
   */
1652 26
  private function queryErrorHandling(string $errorMessage, int $errorNumber, string $sql, $sqlParams = false, bool $sqlMultiQuery = false)
1653
  {
1654
    if (
1655 26
        $errorMessage === 'DB server has gone away'
1656
        ||
1657 24
        $errorMessage === 'MySQL server has gone away'
1658
        ||
1659 26
        $errorNumber === 2006
1660
    ) {
1661 2
      static $RECONNECT_COUNTER;
1662
1663
      // exit if we have more then 3 "DB server has gone away"-errors
1664 2
      if ($RECONNECT_COUNTER > 3) {
1665
        $this->_debug->mailToAdmin('DB-Fatal-Error', $errorMessage . '(' . $errorNumber . ') ' . ":\n<br />" . $sql, 5);
1666
        throw new DBGoneAwayException($errorMessage);
1667
      }
1668
1669 2
      $this->_debug->mailToAdmin('DB-Error', $errorMessage . '(' . $errorNumber . ') ' . ":\n<br />" . $sql);
1670
1671
      // reconnect
1672 2
      $RECONNECT_COUNTER++;
1673 2
      $this->reconnect(true);
1674
1675
      // re-run the current (non multi) query
1676 2
      if ($sqlMultiQuery === false) {
1677 2
        return $this->query($sql, $sqlParams);
1678
      }
1679
1680
      return false;
1681
    }
1682
1683 24
    $this->_debug->mailToAdmin('SQL-Error', $errorMessage . '(' . $errorNumber . ') ' . ":\n<br />" . $sql);
1684
1685 24
    $force_exception_after_error = null; // auto
1686 24
    if ($this->_in_transaction === true) {
1687 8
      $force_exception_after_error = false;
1688
    }
1689
    // this query returned an error, we must display it (only for dev) !!!
1690
1691 24
    $this->_debug->displayError($errorMessage . '(' . $errorNumber . ') ' . ' | ' . $sql, $force_exception_after_error);
1692
1693 24
    return false;
1694
  }
1695
1696
  /**
1697
   * Quote && Escape e.g. a table name string.
1698
   *
1699
   * @param mixed $str
1700
   *
1701
   * @return string
1702
   */
1703 61
  public function quote_string($str): string
1704
  {
1705 61
    $str = \str_replace(
1706 61
        '`',
1707 61
        '``',
1708 61
        \trim(
1709 61
            (string)$this->escape($str, false),
1710 61
            '`'
1711
        )
1712
    );
1713
1714 61
    return '`' . $str . '`';
1715
  }
1716
1717
  /**
1718
   * Reconnect to the MySQL-Server.
1719
   *
1720
   * @param bool $checkViaPing
1721
   *
1722
   * @return bool
1723
   */
1724 5
  public function reconnect(bool $checkViaPing = false): bool
1725
  {
1726 5
    $ping = false;
1727 5
    if ($checkViaPing === true) {
1728 4
      $ping = $this->ping();
1729
    }
1730
1731 5
    if ($ping !== true) {
1732 5
      $this->connected = false;
1733 5
      $this->connect();
1734
    }
1735
1736 5
    return $this->isReady();
1737
  }
1738
1739
  /**
1740
   * Execute a "replace"-query.
1741
   *
1742
   * @param string      $table
1743
   * @param array       $data
1744
   * @param null|string $databaseName <p>Use <strong>null</strong> if you will use the current database.</p>
1745
   *
1746
   * @return false|int <p>false on error</p>
1747
   *
1748
   * @throws QueryException
1749
   */
1750 2
  public function replace(string $table, array $data = [], string $databaseName = null)
1751
  {
1752
    // init
1753 2
    $table = \trim($table);
1754
1755 2
    if ($table === '') {
1756 2
      $this->_debug->displayError('Invalid table name, table name in empty.', false);
1757
1758 2
      return false;
1759
    }
1760
1761 2
    if (\count($data) === 0) {
1762 2
      $this->_debug->displayError('Invalid data for REPLACE, data is empty.', false);
1763
1764 2
      return false;
1765
    }
1766
1767
    // extracting column names
1768 2
    $columns = \array_keys($data);
1769 2
    foreach ($columns as $k => $_key) {
1770
      /** @noinspection AlterInForeachInspection */
1771 2
      $columns[$k] = $this->quote_string($_key);
1772
    }
1773
1774 2
    $columns = \implode(',', $columns);
1775
1776
    // extracting values
1777 2
    foreach ($data as $k => $_value) {
1778
      /** @noinspection AlterInForeachInspection */
1779 2
      $data[$k] = $this->secure($_value);
1780
    }
1781 2
    $values = \implode(',', $data);
1782
1783 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...
1784
      $databaseName = $this->quote_string(\trim($databaseName)) . '.';
1785
    }
1786
1787 2
    $sql = 'REPLACE INTO ' . $databaseName . $this->quote_string($table) . " ($columns) VALUES ($values)";
1788
1789 2
    return $this->query($sql);
1790
  }
1791
1792
  /**
1793
   * Rollback in a transaction and end the transaction.
1794
   *
1795
   * @return bool <p>Boolean true on success, false otherwise.</p>
1796
   */
1797 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...
1798
  {
1799 8
    if ($this->_in_transaction === false) {
1800
      $this->_debug->displayError('Error: mysql server is not in transaction!', false);
1801
1802
      return false;
1803
    }
1804
1805 8
    $return = \mysqli_rollback($this->link);
1806 8
    \mysqli_autocommit($this->link, true);
1807 8
    $this->_in_transaction = false;
1808
1809 8
    return $return;
1810
  }
1811
1812
  /**
1813
   * Try to secure a variable, so can you use it in sql-queries.
1814
   *
1815
   * <p>
1816
   * <strong>int:</strong> (also strings that contains only an int-value)<br />
1817
   * 1. parse into "int"
1818
   * </p><br />
1819
   *
1820
   * <p>
1821
   * <strong>float:</strong><br />
1822
   * 1. return "float"
1823
   * </p><br />
1824
   *
1825
   * <p>
1826
   * <strong>string:</strong><br />
1827
   * 1. check if the string isn't a default mysql-time-function e.g. 'CURDATE()'<br />
1828
   * 2. trim '<br />
1829
   * 3. escape the string (and remove non utf-8 chars)<br />
1830
   * 4. trim ' again (because we maybe removed some chars)<br />
1831
   * 5. add ' around the new string<br />
1832
   * </p><br />
1833
   *
1834
   * <p>
1835
   * <strong>array:</strong><br />
1836
   * 1. return null
1837
   * </p><br />
1838
   *
1839
   * <p>
1840
   * <strong>object:</strong><br />
1841
   * 1. return false
1842
   * </p><br />
1843
   *
1844
   * <p>
1845
   * <strong>null:</strong><br />
1846
   * 1. return null
1847
   * </p>
1848
   *
1849
   * @param mixed $var
1850
   *
1851
   * @return mixed
1852
   */
1853 71
  public function secure($var)
1854
  {
1855 71
    if ($var === '') {
1856 4
      return "''";
1857
    }
1858
1859 71
    if ($var === "''") {
1860 2
      return "''";
1861
    }
1862
1863 71
    if ($var === null) {
1864 3
      if ($this->_convert_null_to_empty_string === true) {
1865 2
        return "''";
1866
      }
1867
1868 3
      return 'NULL';
1869
    }
1870
1871 70
    if (\in_array($var, $this->mysqlDefaultTimeFunctions, true)) {
1872 2
      return $var;
1873
    }
1874
1875 70
    if (\is_string($var)) {
1876 59
      $var = \trim($var, "'");
1877
    }
1878
1879 70
    $var = $this->escape($var, false, false, null);
1880
1881 68
    if (\is_string($var)) {
1882 59
      $var = "'" . \trim($var, "'") . "'";
1883
    }
1884
1885 68
    return $var;
1886
  }
1887
1888
  /**
1889
   * Execute a "select"-query.
1890
   *
1891
   * @param string       $table
1892
   * @param string|array $where
1893
   * @param string|null  $databaseName <p>Use <strong>null</strong> if you will use the current database.</p>
1894
   *
1895
   * @return false|Result <p>false on error</p>
1896
   *
1897
   * @throws QueryException
1898
   */
1899 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...
1900
  {
1901
    // init
1902 43
    $table = \trim($table);
1903
1904 43
    if ($table === '') {
1905 2
      $this->_debug->displayError('Invalid table name, table name in empty.', false);
1906
1907 2
      return false;
1908
    }
1909
1910 43
    if (\is_string($where)) {
1911 16
      $WHERE = $this->escape($where, false);
1912 29
    } elseif (\is_array($where)) {
1913 29
      $WHERE = $this->_parseArrayPair($where, 'AND');
1914
    } else {
1915 2
      $WHERE = '';
1916
    }
1917
1918 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...
1919
      $databaseName = $this->quote_string(\trim($databaseName)) . '.';
1920
    }
1921
1922 43
    $sql = 'SELECT * FROM ' . $databaseName . $this->quote_string($table) . " WHERE ($WHERE)";
1923
1924 43
    return $this->query($sql);
1925
  }
1926
1927
  /**
1928
   * Selects a different database than the one specified on construction.
1929
   *
1930
   * @param string $database <p>Database name to switch to.</p>
1931
   *
1932
   * @return bool <p>Boolean true on success, false otherwise.</p>
1933
   */
1934
  public function select_db(string $database): bool
1935
  {
1936
    if (!$this->isReady()) {
1937
      return false;
1938
    }
1939
1940
    return mysqli_select_db($this->link, $database);
1941
  }
1942
1943
  /**
1944
   * @param array $extra_config           <p>
1945
   *                                      'session_to_db' => false|true<br>
1946
   *                                      'socket' => 'string (path)'<br>
1947
   *                                      'ssl' => 'bool'<br>
1948
   *                                      'clientkey' => 'string (path)'<br>
1949
   *                                      'clientcert' => 'string (path)'<br>
1950
   *                                      'cacert' => 'string (path)'<br>
1951
   *                                      </p>
1952
   */
1953 65
  public function setConfigExtra(array $extra_config)
1954
  {
1955 65
    if (isset($extra_config['session_to_db'])) {
1956
      $this->session_to_db = (boolean)$extra_config['session_to_db'];
1957
    }
1958
1959
    if (
1960 65
        isset($extra_config['doctrine'])
1961
        &&
1962 65
        $extra_config['doctrine'] instanceof \Doctrine\DBAL\Connection
1963
    ) {
1964 54
      $this->_doctrine_connection = $extra_config['doctrine'];
1965
    }
1966
1967 65
    if (isset($extra_config['socket'])) {
1968
      $this->socket = $extra_config['socket'];
1969
    }
1970
1971 65
    if (isset($extra_config['ssl'])) {
1972
      $this->_ssl = $extra_config['ssl'];
1973
    }
1974
1975 65
    if (isset($extra_config['clientkey'])) {
1976
      $this->_clientkey = $extra_config['clientkey'];
1977
    }
1978
1979 65
    if (isset($extra_config['clientcert'])) {
1980
      $this->_clientcert = $extra_config['clientcert'];
1981
    }
1982
1983 65
    if (isset($extra_config['cacert'])) {
1984
      $this->_cacert = $extra_config['cacert'];
1985
    }
1986 65
  }
1987
1988
  /**
1989
   * Set the current charset.
1990
   *
1991
   * @param string $charset
1992
   *
1993
   * @return bool
1994
   */
1995 62
  public function set_charset(string $charset): bool
1996
  {
1997 62
    $charsetLower = strtolower($charset);
1998 62
    if ($charsetLower === 'utf8' || $charsetLower === 'utf-8') {
1999 60
      $charset = 'utf8';
2000
    }
2001 62
    if ($charset === 'utf8' && Helper::isUtf8mb4Supported($this) === true) {
2002 60
      $charset = 'utf8mb4';
2003
    }
2004
2005 62
    $this->charset = $charset;
2006
2007 62
    $return = mysqli_set_charset($this->link, $charset);
2008
    /** @noinspection PhpUsageOfSilenceOperatorInspection */
2009 62
    @\mysqli_query($this->link, 'SET CHARACTER SET ' . $charset);
2010
    /** @noinspection PhpUsageOfSilenceOperatorInspection */
2011 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...
2012
2013 62
    return $return;
2014
  }
2015
2016
  /**
2017
   * Set the option to convert null to "''" (empty string).
2018
   *
2019
   * Used in secure() => select(), insert(), update(), delete()
2020
   *
2021
   * @deprecated It's not recommended to convert NULL into an empty string!
2022
   *
2023
   * @param bool $bool
2024
   *
2025
   * @return self
2026
   */
2027 2
  public function set_convert_null_to_empty_string(bool $bool): self
2028
  {
2029 2
    $this->_convert_null_to_empty_string = $bool;
2030
2031 2
    return $this;
2032
  }
2033
2034
  /**
2035
   * Enables or disables internal report functions
2036
   *
2037
   * @link http://php.net/manual/en/function.mysqli-report.php
2038
   *
2039
   * @param int $flags <p>
2040
   *                   <table>
2041
   *                   Supported flags
2042
   *                   <tr valign="top">
2043
   *                   <td>Name</td>
2044
   *                   <td>Description</td>
2045
   *                   </tr>
2046
   *                   <tr valign="top">
2047
   *                   <td><b>MYSQLI_REPORT_OFF</b></td>
2048
   *                   <td>Turns reporting off</td>
2049
   *                   </tr>
2050
   *                   <tr valign="top">
2051
   *                   <td><b>MYSQLI_REPORT_ERROR</b></td>
2052
   *                   <td>Report errors from mysqli function calls</td>
2053
   *                   </tr>
2054
   *                   <tr valign="top">
2055
   *                   <td><b>MYSQLI_REPORT_STRICT</b></td>
2056
   *                   <td>
2057
   *                   Throw <b>mysqli_sql_exception</b> for errors
2058
   *                   instead of warnings
2059
   *                   </td>
2060
   *                   </tr>
2061
   *                   <tr valign="top">
2062
   *                   <td><b>MYSQLI_REPORT_INDEX</b></td>
2063
   *                   <td>Report if no index or bad index was used in a query</td>
2064
   *                   </tr>
2065
   *                   <tr valign="top">
2066
   *                   <td><b>MYSQLI_REPORT_ALL</b></td>
2067
   *                   <td>Set all options (report all)</td>
2068
   *                   </tr>
2069
   *                   </table>
2070
   *                   </p>
2071
   *
2072
   * @return bool
2073
   */
2074
  public function set_mysqli_report(int $flags): bool
2075
  {
2076
    return \mysqli_report($flags);
2077
  }
2078
2079
  /**
2080
   * Show config errors by throw exceptions.
2081
   *
2082
   * @return bool
2083
   *
2084
   * @throws \InvalidArgumentException
2085
   */
2086 65
  public function showConfigError(): bool
2087
  {
2088
    // check if a doctrine connection is already open
2089
    if (
2090 65
        $this->_doctrine_connection
2091
        &&
2092 65
        $this->_doctrine_connection->isConnected()
2093
    ) {
2094 54
      return true;
2095
    }
2096
2097
    if (
2098 17
        !$this->hostname
2099
        ||
2100 15
        !$this->username
2101
        ||
2102 17
        !$this->database
2103
    ) {
2104
2105 6
      if (!$this->hostname) {
2106 2
        throw new \InvalidArgumentException('no-sql-hostname');
2107
      }
2108
2109 4
      if (!$this->username) {
2110 2
        throw new \InvalidArgumentException('no-sql-username');
2111
      }
2112
2113 2
      if (!$this->database) {
2114 2
        throw new \InvalidArgumentException('no-sql-database');
2115
      }
2116
2117
      return false;
2118
    }
2119
2120 11
    return true;
2121
  }
2122
2123
  /**
2124
   * alias: "beginTransaction()"
2125
   */
2126 2
  public function startTransaction(): bool
2127
  {
2128 2
    return $this->beginTransaction();
2129
  }
2130
2131
  /**
2132
   * Determine if database table exists
2133
   *
2134
   * @param string $table
2135
   *
2136
   * @return bool
2137
   */
2138 2
  public function table_exists(string $table): bool
2139
  {
2140 2
    $check = $this->query('SELECT 1 FROM ' . $this->quote_string($table));
2141
2142 2
    return $check !== false
2143
           &&
2144 2
           $check instanceof Result
2145
           &&
2146 2
           $check->num_rows > 0;
2147
  }
2148
2149
  /**
2150
   * Execute a callback inside a transaction.
2151
   *
2152
   * @param callback $callback <p>The callback to run inside the transaction, if it's throws an "Exception" or if it's
2153
   *                           returns "false", all SQL-statements in the callback will be rollbacked.</p>
2154
   *
2155
   * @return bool <p>Boolean true on success, false otherwise.</p>
2156
   */
2157 2
  public function transact($callback): bool
2158
  {
2159
    try {
2160
2161 2
      $beginTransaction = $this->beginTransaction();
2162 2
      if ($beginTransaction === false) {
2163 2
        $this->_debug->displayError('Error: transact -> can not start transaction!', false);
2164
2165 2
        return false;
2166
      }
2167
2168 2
      $result = $callback($this);
2169 2
      if ($result === false) {
2170
        /** @noinspection ThrowRawExceptionInspection */
2171 2
        throw new \Exception('call_user_func [' . $callback . '] === false');
2172
      }
2173
2174 2
      return $this->commit();
2175
2176 2
    } catch (\Exception $e) {
2177
2178 2
      $this->rollback();
2179
2180 2
      return false;
2181
    }
2182
  }
2183
2184
  /**
2185
   * Execute a "update"-query.
2186
   *
2187
   * @param string       $table
2188
   * @param array        $data
2189
   * @param array|string $where
2190
   * @param null|string  $databaseName <p>Use <strong>null</strong> if you will use the current database.</p>
2191
   *
2192
   * @return false|int <p>false on error</p>
2193
   *
2194
   * @throws QueryException
2195
   */
2196 14
  public function update(string $table, array $data = [], $where = '1=1', string $databaseName = null)
2197
  {
2198
    // init
2199 14
    $table = \trim($table);
2200
2201 14
    if ($table === '') {
2202 2
      $this->_debug->displayError('Invalid table name, table name in empty.', false);
2203
2204 2
      return false;
2205
    }
2206
2207 14
    if (\count($data) === 0) {
2208 4
      $this->_debug->displayError('Invalid data for UPDATE, data is empty.', false);
2209
2210 4
      return false;
2211
    }
2212
2213 14
    $SET = $this->_parseArrayPair($data);
2214
2215 14
    if (\is_string($where)) {
2216 4
      $WHERE = $this->escape($where, false);
2217 12
    } elseif (\is_array($where)) {
2218 10
      $WHERE = $this->_parseArrayPair($where, 'AND');
2219
    } else {
2220 2
      $WHERE = '';
2221
    }
2222
2223 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...
2224
      $databaseName = $this->quote_string(\trim($databaseName)) . '.';
2225
    }
2226
2227 14
    $sql = 'UPDATE ' . $databaseName . $this->quote_string($table) . " SET $SET WHERE ($WHERE)";
2228
2229 14
    return $this->query($sql);
2230
  }
2231
2232
}
2233